Sometimes we need to perform the installation of another program in the program, which requires us to customize the implementation of the MSI installation package.
Demand
For example, I want to do a setup management program, can be installed according to the user's choice of different sub-products. When the user selects three products, it is obviously inappropriate to display the interactive UI for the installation of the three products. We expect a unified custom UI to replace each product's own UI.
Implementation Ideas
Usually use Msiexec.exe, so the most direct idea is to execute in a sub-process:
msiexec.exe/qn
This is to be able to complete the task, but is not too simple? What do we do if we want to cancel this installation after the installation begins? Or we want to get some information about the installation progress.
This can actually be done easily by calling three Windows APIs! The following C # Demo uses a custom Form to indicate the installation process for multiple MSI files. A scroll bar is placed on the Form and is matched with a label that is constantly updated. Let's see what the demo looks like.
The following is the UI in the installation process:
Click the Cancel button to cancel the installed UI:
Main Interface Introduction
Let's take a look at some of the Win32 APIs that are used primarily.
The first is the Msisetinternalui method:
[DllImport ("msi.dll", CharSet = CharSet.Auto)] Internal Static extern int Msisetinternalui (int dwuilevel, IntPtr Phwnd);
When calling Msiexec.exe, we let the installation process display a different UI by specifying the/q parameter. If the UI is not displayed, the parameter/qn will be used. The Msisetinternalui method is to do the same thing. Using the following call, you can remove the UI that comes with the MSI:
Nativemethods.msisetinternalui (2, IntPtr.Zero)
Next is the Msisetexternalui method:
[DllImport ( msi.dll ", CharSet = CharSet.Auto)] internal static extern Msiinstalluihandler Msisetexternalui ([MarshalAs (unmanagedtype.functionptr)] Msiinstalluihandler PuiHandler, Nativemethods.installlogmode Dwmessagefilter, IntPtr pvcontext);
The Msisetexternalui function allows you to specify a user-defined external UI handler to handle messages generated during installation. This external UI handler is called before the internal UI handler is called. If a value other than 0 is returned in the external UI handler, the message has already been processed.
This external UI handler is the first parameter of the Msisetexternalui method, and we implement this handler to deal with the messages we are interested in, such as updating the progress bar after the installation progress has changed. or pass our message through it to the MSI, say to MSI, stop the installation, and perform the cancel operation. The use of this method should be noted that when you complete the installation must be set back to the original handler. Otherwise, executing the MSI installation package may cause problems later.
MSDN has a Msiinstalluihandler demo, interested students can go to see.
Here is the MsiInstallProduct method:
[DllImport ("msi.dll", CharSet = CharSet.Auto)] Internal Static extern UINT string string szcommandline);
As its name is, this is the real way to do it.
I can't help but introduce the fourth method, although it is optional for implementing the current function, but for a product, it is used to help.
[DllImport ("msi.dll", CharSet = CharSet.Auto)] Internal Static extern UINT string uint dwlogattributes);
This method will save the installation log to the file path you passed to it. With it life will be happy many, many ... Otherwise, you will be mad when the user tells you that the installation has failed.
Main code
Okay, here's the main code for MyInstaller demo:
InstallProcessForm.cs Public Partial classinstallprocessform:form{PrivateMyInstaller _installer =NULL; PrivateBackgroundWorker _installerbgworker =NewBackgroundWorker (); InternalInstallprocessform () {InitializeComponent (); _installer=NewMyInstaller (); _installerbgworker.workerreportsprogress=true; _installerbgworker.workersupportscancellation=true; _installerbgworker.dowork+=_installerbgworker_dowork; _installerbgworker.runworkercompleted+=_installerbgworker_runworkercompleted; _installerbgworker.progresschanged+=_installerbgworker_progresschanged; This. Shown + =Installprocessform_shown; } Private voidInstallprocessform_shown (Objectsender, EventArgs e) { //start the background installation when the window is open_installerbgworker.runworkerasync (); } Private void_installerbgworker_progresschanged (Objectsender, ProgressChangedEventArgs e) { //The message is passed back through E.userstate and is displayed on the window by means of a label stringMessage =e.userstate.tostring (); This. Label1. Text =message; if(Message = ="Canceling installation ...") { This. cancelbutton.enabled =false; } } Private void_installerbgworker_runworkercompleted (Objectsender, Runworkercompletedeventargs e) { //end of installation process } Private void_installerbgworker_dowork (Objectsender, DoWorkEventArgs e) {BackgroundWorker Bgworker= Sender asBackgroundWorker; //start execution of installation methods_installer =NewMyInstaller (); stringMsifilepath ="Xxx.msi";//MSI file path_installer. Install (Bgworker, Msifilepath); } Private voidCancelbutton_click (Objectsender, EventArgs e) {_installer. Canceled=true; _installerbgworker.cancelasync (); }}myinstaller.csInternal classmyinstaller{PrivateBackgroundWorker _bgworker =NULL; Public BOOLCanceled {Get;Set; } Public voidInstall (BackgroundWorker Bgworker,stringmsifilename) {_bgworker=Bgworker; Nativemethods.mymsiinstalluihandler Oldhandler=NULL; Try { stringLogPath ="Test.log"; Nativemethods.msienablelog (NativeMethods.LogMode.Verbose, LogPath,0u); Nativemethods.msisetinternalui (2, IntPtr.Zero); Oldhandler= Nativemethods.msisetexternalui (NewNativemethods.mymsiinstalluihandler (Msiprogresshandler), NativeMethods . Logmode.externalui, IntPtr.Zero); stringparam ="Action=install"; _bgworker.reportprogress (0,"Installing XXX ..."); Nativemethods.msiinstallproduct (Msifilename, param); } Catch(Exception e) {//Todo } finally { //Be sure to set the default handler back. if(Oldhandler! =NULL) {Nativemethods.msisetexternalui (Oldhandler, NativeMethods.LogMode.None, IntPtr.Zero); } } } //The most important is this method, which shows only how to cancel an installation, for more details please refer to the MSDN documentation Private intMsiprogresshandler (INTPTR context,intMessageType,stringmessage) { if( This. Canceled) {if(_bgworker! =NULL) {_bgworker.reportprogress (0,"Canceling installation ..."); } //This return value tells the MSI, cancel the current installation return 2; } return 1; }}Internal Static classnativemethods{[DllImport ("Msi.dll", CharSet =CharSet.Auto)]Internal Static extern intMsisetinternalui (intDwuilevel, IntPtr Phwnd); [DllImport ("Msi.dll", CharSet =CharSet.Auto)]Internal Static externMymsiinstalluihandler Msisetexternalui ([MarshalAs (unmanagedtype.functionptr)] Mymsiinstalluihandler Puihandler, Nativemethods.logmode dwmessagefilter, IntPtr pvcontext); [DllImport ("Msi.dll", CharSet =CharSet.Auto)]Internal Static extern UINTMsiInstallProduct ([MarshalAs (UNMANAGEDTYPE.LPWSTR)]stringSzpackagepath, [MarshalAs (UNMANAGEDTYPE.LPWSTR)]stringszcommandline); [DllImport ("Msi.dll", CharSet =CharSet.Auto)]Internal Static extern UINTMsienablelog (Nativemethods.logmode dwlogmode, [MarshalAs (UNMANAGEDTYPE.LPWSTR)]stringSzlogfile,UINTdwlogattributes); Internal Delegate intMymsiinstalluihandler (INTPTR context,intMessageType, [MarshalAs (UNMANAGEDTYPE.LPWSTR)]stringmessage); [Flags]Internal enumLogMode:UINT{None=0u, Verbose=4096u, Externalui=20239u }}
Briefly, the user-defined UI runs in the main thread and uses BackgroundWorker to perform the installation task. During installation, the cancel message can be passed to Msiprogresshandler, and when the cancel information is detected by Msiprogresshandler, the execution engine of the MSI is told by the return value, and the cancel operation is performed ( MSI installation process is quite rigorous, can not simply kill the installation process! )。
In this way, an installation control program for the custom UI that supports Cancel is ok (demo ha). If you want to install more than one MSI, you just need to loop through the install method.
Summary
By invoking several Windows APIs, we can implement control of the MSI installation process. This is more flexible than calling Msiexec.exe and lays the groundwork for adding new functionality later in the program.
Performing an MSI installation in C #