天天看點

自定義msi安裝包的執行過程

有時候我們需要在程式中執行另一個程式的安裝,這就需要我們去自定義msi安裝包的執行過程。

比如我要做一個安裝管理程式,可以根據使用者的選擇安裝不同的子産品。當使用者選擇了三個産品時,如果分别顯示這三個産品的安裝互動UI顯然是不恰當的。我們期望用一個統一的自定義UI去取代每個産品各自的UI。

平時使用msiexec.exe習慣了,是以最直接的想法就是在一個子程序中執行:

         msiexec.exe /qn

這樣固然是能夠完成任務,但是不是太簡陋了? 安裝開始後我們想取消這次安裝怎麼辦? 或者我們還想要拿到一些安裝進度的資訊。

其實可以通過調用三個windowsAPI 輕松搞定這個事兒!下面的C# demo用一個自定義Form來訓示多個MSI檔案的安裝過程。Form上放的是一個滾動條,并且配合一個不斷更新的label。

下面是安裝過程中的UI:

自定義msi安裝包的執行過程

點選Cancel按鈕取消安裝後的UI:

自定義msi安裝包的執行過程

先看一下這三個API:

[DllImport("msi.dll", CharSet = CharSet.Auto)]

在調用msiexec.exe時,我們通過指定 /q參數讓安裝過程顯示不同的UI。如果不顯示UI的話就要使用參數 /qn 。MsiSetInternalUI方法就是幹這個事兒的。通過下面的調用就可以去掉msi中自帶的UI:

NativeMethods.MsiSetInternalUI(2, IntPtr.Zero)

MsiSetExternalUI 函數允許指定一個使用者定義的外部UI handler用來處理安裝過程中産生的消息。這個外部的UI handler會在内部的UI handler被調用前調用。 如果在外部的UI handler中傳回非0的值,就說明這個消息已經被處理。

這個外部的UI handler就是MsiSetExternalUI方法的第一個參數,我們通過實作這個handler來處理自己感興趣的消息, 比如當安裝進度變化後去更新進度條。或者通過它傳遞我們的消息給msi,比如說告訴msi,停止安裝,執行cancel操作。使用這個方法需要注意的是,當你完成安裝後一定要把原來的handler設回去。否則以後執行msi安裝包可能會出問題。

正如其名,這個是真正幹活兒的方法。

實在忍不住要介紹第四個方法,雖然它對實作目前的功能來說是可選的,但對一個産品來說,它卻是用來救命的。

這個方法會把安裝log儲存到你傳遞給它的檔案路徑。有了它生活就會happy很多,很多… 否則當使用者告訴你安裝失敗時,你一定會抓狂的。

好了,下面是MyInstaller demo的主要代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

<code>InstallProcessForm.cs</code>

<code>publicpartialclassInstallProcessForm : Form</code>

<code>    </code><code>{</code>

<code>        </code><code>privateMyInstaller _installer = </code><code>null</code><code>;</code>

<code>        </code><code>privateBackgroundWorker _installerBGWorker = newBackgroundWorker();</code>

<code>        </code><code>internalInstallProcessForm()</code>

<code>        </code><code>{</code>

<code>           </code><code>InitializeComponent();</code>

<code> </code> 

<code>            </code><code>_installer = newMyInstaller();</code>

<code>           </code><code>_installerBGWorker.WorkerReportsProgress = </code><code>true</code><code>;</code>

<code>           </code><code>_installerBGWorker.WorkerSupportsCancellation = </code><code>true</code><code>;</code>

<code>           </code><code>_installerBGWorker.DoWork += _installerBGWorker_DoWork;</code>

<code>            </code><code>_installerBGWorker.RunWorkerCompleted+= _installerBGWorker_RunWorkerCompleted;</code>

<code>           </code><code>_installerBGWorker.ProgressChanged +=_installerBGWorker_ProgressChanged;</code>

<code>            </code><code>this</code><code>.Shown+= InstallProcessForm_Shown;</code>

<code>        </code><code>}</code>

<code>        </code><code>privatevoidInstallProcessForm_Shown(</code><code>object</code> <code>sender, EventArgs e)</code>

<code>            </code><code>// 當視窗打開後就開始背景的安裝</code>

<code>           </code><code>_installerBGWorker.RunWorkerAsync();</code>

<code>        </code><code>privatevoid_installerBGWorker_ProgressChanged(</code><code>object</code> <code>sender, ProgressChangedEventArgs e)</code>

<code>            </code><code>// 消息通過 e.UserState 傳回,并通過label顯示在視窗上</code>

<code>            </code><code>string</code> <code>message= e.UserState.ToString();</code>

<code>            </code><code>this</code><code>.label1.Text= message;</code>

<code>            </code><code>if</code><code>(message == </code><code>"正在取消安裝..."</code><code>)</code>

<code>            </code><code>{</code>

<code>                </code><code>this</code><code>.CancelButton.Enabled= </code><code>false</code><code>;</code>

<code>            </code><code>}</code>

<code>        </code><code>privatevoid_installerBGWorker_RunWorkerCompleted(</code><code>object</code> <code>sender, RunWorkerCompletedEventArgs e)</code>

<code>            </code><code>// 安裝過程結束</code>

<code>        </code><code>privatevoid_installerBGWorker_DoWork(</code><code>object</code> <code>sender, DoWorkEventArgs e)</code>

<code>            </code><code>BackgroundWorker bgWorker = sender asBackgroundWorker;</code>

<code>            </code><code>// 開始執行安裝方法</code>

<code>           </code><code>_installer = newMyInstaller();</code>

<code>            </code><code>stringmsiFilePath = </code><code>"xxx.msi"</code><code>; </code><code>// msifile path</code>

<code>           </code><code>_installer.Install(bgWorker, msiFilePath);</code>

<code>        </code><code>privatevoidCancelButton_Click(</code><code>object</code> <code>sender, EventArgs e)</code>

<code>           </code><code>_installer.Canceled = </code><code>true</code><code>;</code>

<code>     </code><code>_installerBGWorker.CancelAsync();</code>

<code>}</code>

<code>MyInstaller.cs</code>

<code>internalclassMyInstaller</code>

<code>        </code><code>privateBackgroundWorker _bgWorker = </code><code>null</code><code>;</code>

<code>        </code><code>publicboolCanceled { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

<code>        </code><code>publicvoidInstall(BackgroundWorker bgWorker, </code><code>string</code> <code>msiFileName)</code>

<code>           </code><code>_bgWorker = bgWorker;</code>

<code>            </code><code>NativeMethods.MyMsiInstallUIHandler oldHandler = </code><code>null</code><code>;</code>

<code>            </code><code>try</code>

<code>                </code><code>string</code> <code>logPath= </code><code>"test.log"</code><code>;</code>

<code>                </code><code>NativeMethods.MsiEnableLog(NativeMethods.LogMode.Verbose, logPath, 0u);</code>

<code>                </code><code>NativeMethods.MsiSetInternalUI(2, IntPtr.Zero);</code>

<code>               </code><code>oldHandler = NativeMethods.MsiSetExternalUI(newNativeMethods.MyMsiInstallUIHandler(MsiProgressHandler),</code>

<code>                                               </code><code>NativeMethods.LogMode.ExternalUI,</code>

<code>                                               </code><code>IntPtr.Zero);</code>

<code>                </code><code>string</code> <code>param =</code><code>"ACTION=INSTALL"</code><code>;</code>

<code>               </code><code>_bgWorker.ReportProgress(0, </code><code>"正在安裝 xxx..."</code><code>);</code>

<code>                </code><code>NativeMethods.MsiInstallProduct(msiFileName, param);</code>

<code>            </code><code>catch</code><code>(Exception e)</code>

<code>                </code><code>// todo</code>

<code>            </code><code>finally</code>

<code>                </code><code>// 一定要把預設的handler設回去。</code>

<code>                </code><code>if</code><code>(oldHandler!= </code><code>null</code><code>)</code>

<code>                </code><code>{</code>

<code>                    </code><code>NativeMethods.MsiSetExternalUI(oldHandler, NativeMethods.LogMode.None, IntPtr.Zero);</code>

<code>                </code><code>}</code>

<code>        </code><code>//最重要的就是這個方法了,這裡僅示範了如何cancel一個安裝,更多詳情請參考MSDN文檔</code>

<code>        </code><code>privateintMsiProgressHandler(IntPtr context, </code><code>int</code> <code>messageType, stringmessage)</code>

<code>            </code><code>if</code> <code>(</code><code>this</code><code>.Canceled)</code>

<code>                </code><code>if</code><code>(_bgWorker != </code><code>null</code><code>)</code>

<code>                   </code><code>_bgWorker.ReportProgress(0, </code><code>"正在取消安裝..."</code><code>);</code>

<code>                </code><code>// 這個傳回值會告訴msi, cancel目前的安裝</code>

<code>                </code><code>return</code> <code>2;</code>

<code>            </code><code>return</code> <code>1;</code>

<code>    </code><code>}</code>

<code>    </code><code>internalstaticclassNativeMethods</code>

<code>        </code><code>[DllImport(</code><code>"msi.dll"</code><code>, CharSet = CharSet.Auto)]</code>

<code>        </code><code>internalstaticexternintMsiSetInternalUI(</code><code>int</code> <code>dwUILevel, IntPtr phWnd);</code>

<code>        </code><code>internalstaticexternMyMsiInstallUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] MyMsiInstallUIHandler puiHandler, NativeMethods.LogMode dwMessageFilter, IntPtrpvContext);</code>

<code>        </code><code>internalstaticexternuint MsiInstallProduct([MarshalAs(UnmanagedType.LPWStr)] </code><code>string</code> <code>szPackagePath, [MarshalAs(UnmanagedType.LPWStr)] </code><code>string</code> <code>szCommandLine);</code>

<code>        </code><code>internalstaticexternuintMsiEnableLog(NativeMethods.LogMode dwLogMode, [MarshalAs(UnmanagedType.LPWStr)] </code><code>string</code> <code>szLogFile, uintdwLogAttributes);</code>

<code>        </code><code>internaldelegateintMyMsiInstallUIHandler(IntPtr context, </code><code>int</code> <code>messageType, [MarshalAs(UnmanagedType.LPWStr)] </code><code>string</code> <code>message);</code>

<code>        </code><code>[Flags]</code>

<code>        </code><code>internalenumLogMode : </code><code>uint</code>

<code>            </code><code>None =0u,</code>

<code>            </code><code>Verbose= 4096u,</code>

<code>           </code><code>ExternalUI = 20239u</code>

 簡單說明一下,使用者定義的UI運作在主線程中,使用BackgroundWorker執行安裝任務。在安裝進行的過程中可以把cancel資訊傳遞給MsiProgressHandler,當MsiProgressHandler檢測到cancel資訊後通過傳回值告訴msi的執行引擎,執行cancel操作(msi的安裝過程是相當嚴謹的,可不能簡單的殺掉安裝程序了事!)。

這樣,一個支援cancel的自定義UI的安裝控制程式就OK了(demo哈)。如果要安裝多個msi隻需在Install方法中循環就可以了。

 總結一下,通過調用幾個windows API,我們可以實作對msi安裝過程的控制。這比調用msiexec.exe更靈活,也為程式日後添加新的功能打下了基礎。

本文轉自 powertoolsteam 51CTO部落格,原文連結:http://blog.51cto.com/powertoolsteam/1754834,如需轉載請自行聯系原作者

繼續閱讀