有时候我们需要在程序中执行另一个程序的安装,这就需要我们去自定义msi安装包的执行过程。

比如我要做一个安装管理程序,可以根据用户的选择安装不同的子产品。当用户选择了三个产品时,如果分别显示这三个产品的安装交互UI显然是不恰当的。我们期望用一个统一的自定义UI去取代每个产品各自的UI。

平时使用msiexec.exe习惯了,所以最直接的想法就是在一个子进程中执行:

    msiexec.exe /qn

这样固然是能够完成任务,但是不是太简陋了? 安装开始后我们想取消这次安装怎么办? 或者我们还想要拿到一些安装进度的信息。

其实可以通过调用三个windowsAPI 轻松搞定这个事儿!下面的C# demo用一个自定义Form来指示多个MSI文件的安装过程。Form上放的是一个滚动条,并且配合一个不断更新的label。

下面是安装过程中的UI:

点击Cancel按钮取消安装后的UI:

先看一下这三个API:

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

internal static extern int MsiSetInternalUI(int dwUILevel, IntPtr phWnd);

在调用msiexec.exe时,我们通过指定 /q参数让安装过程显示不同的UI。如果不显示UI的话就要使用参数 /qn 。MsiSetInternalUI方法就是干这个事儿的。通过下面的调用就可以去掉msi中自带的UI:

NativeMethods.MsiSetInternalUI(2, IntPtr.Zero)

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

internal static extern MsiInstallUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] MsiInstallUIHandlerpuiHandler, NativeMethods.InstallLogMode dwMessageFilter, IntPtr pvContext);

MsiSetExternalUI 函数允许指定一个用户定义的外部UI handler用来处理安装过程中产生的消息。这个外部的UI handler会在内部的UI handler被调用前调用。 如果在外部的UI handler中返回非0的值,就说明这个消息已经被处理。

这个外部的UI handler就是MsiSetExternalUI方法的第一个参数,我们通过实现这个handler来处理自己感兴趣的消息, 比如当安装进度变化后去更新进度条。或者通过它传递我们的消息给msi,比如说告诉msi,停止安装,执行cancel操作。使用这个方法需要注意的是,当你完成安装后一定要把原来的handler设回去。否则以后执行msi安装包可能会出问题。

MSDN上有一个MsiInstallUIHandler 的demo,感兴趣的同学可以看看。

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

internal static extern uint MsiInstallProduct([MarshalAs(UnmanagedType.LPWStr)] string szPackagePath,[MarshalAs(UnmanagedType.LPWStr)] string szCommandLine);

正如其名,这个是真正干活儿的方法。

实在忍不住要介绍第四个方法,虽然它对实现当前的功能来说是可选的,但对一个产品来说,它却是用来救命的。

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

internal static extern uint MsiEnableLog(GcMsiUtil.NativeMethods.InstallLogMode dwLogMode,[MarshalAs(UnmanagedType.LPWStr)] string szLogFile, uint dwLogAttributes);

这个方法会把安装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
InstallProcessForm.cs
public partial class InstallProcessForm : Form
    {
        private MyInstaller _installer = null;
        private BackgroundWorker _installerBGWorker = new BackgroundWorker();
        internal InstallProcessForm()
        {
            InitializeComponent();
 
            _installer = new MyInstaller();
 
            _installerBGWorker.WorkerReportsProgress = true;
            _installerBGWorker.WorkerSupportsCancellation = true;
 
            _installerBGWorker.DoWork += _installerBGWorker_DoWork;
            _installerBGWorker.RunWorkerCompleted += _installerBGWorker_RunWorkerCompleted;
            _installerBGWorker.ProgressChanged += _installerBGWorker_ProgressChanged;
 
            this.Shown += InstallProcessForm_Shown;
        }
 
        private void InstallProcessForm_Shown(object sender, EventArgs e)
        {
            // 当窗口打开后就开始后台的安装
            _installerBGWorker.RunWorkerAsync();
        }
 
        private void _installerBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // 消息通过 e.UserState 传回,并通过label显示在窗口上
            string message = e.UserState.ToString();
            this.label1.Text = message;
            if (message == "正在取消安装 ...")
            {
                this.CancelButton.Enabled = false;
            }
        }
 
        private void _installerBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // 安装过程结束
        }
 
        private void _installerBGWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgWorker = sender as BackgroundWorker;
 
            // 开始执行安装方法
            _installer = new MyInstaller();
            string msiFilePath = "xxx.msi"// msi file path
            _installer.Install(bgWorker, msiFilePath);
        }
 
        private void CancelButton_Click(object sender, EventArgs e)
        {
            _installer.Canceled = true;
     _installerBGWorker.CancelAsync();
        }
}
MyInstaller.cs
internal class MyInstaller
    {
        private BackgroundWorker _bgWorker = null;
 
        public bool Canceled { getset; }
 
        public void Install(BackgroundWorker bgWorker, string msiFileName)
        {
            _bgWorker = bgWorker;
 
            NativeMethods.MyMsiInstallUIHandler oldHandler = null;
            try
            {
                string logPath = "test.log";
                NativeMethods.MsiEnableLog(NativeMethods.LogMode.Verbose, logPath, 0u);
                NativeMethods.MsiSetInternalUI(2, IntPtr.Zero);
 
      oldHandler = NativeMethods.MsiSetExternalUI(new NativeMethods.MyMsiInstallUIHandler(MsiProgressHandler),
                                                NativeMethods.LogMode.ExternalUI,
                                                IntPtr.Zero);
                string param = "ACTION=INSTALL";
                _bgWorker.ReportProgress(0, "正在安装 xxx ...");
                NativeMethods.MsiInstallProduct(msiFileName, param);
            }
            catch(Exception e)
            {
                // todo
            }
            finally
            {
                // 一定要把默认的handler设回去。
                if(oldHandler != null)
                {
                    NativeMethods.MsiSetExternalUI(oldHandler, NativeMethods.LogMode.None, IntPtr.Zero);
                }
            }
        }
 
        //最重要的就是这个方法了,这里仅演示了如何cancel一个安装,更多详情请参考MSDN文档
        private int MsiProgressHandler(IntPtr context, int messageType, string message)
        {
            if (this.Canceled)
            {
                if (_bgWorker != null)
                {
                    _bgWorker.ReportProgress(0, "正在取消安装 ...");
                }
                // 这个返回值会告诉msi, cancel当前的安装
                return 2;
            }
            return 1;
        }
    }
 
    internal static class NativeMethods
    {
        [DllImport("msi.dll", CharSet = CharSet.Auto)]
        internal static extern int MsiSetInternalUI(int dwUILevel, IntPtr phWnd);
 
        [DllImport("msi.dll", CharSet = CharSet.Auto)]
        internal static extern MyMsiInstallUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] <br>MyMsiInstallUIHandler puiHandler,<br> NativeMethods.LogMode dwMessageFilter, IntPtr pvContext);
 
        [DllImport("msi.dll", CharSet = CharSet.Auto)]
        internal static extern uint MsiInstallProduct([MarshalAs(UnmanagedType.LPWStr)] string szPackagePath, <br>[MarshalAs(UnmanagedType.LPWStr)]<br> string szCommandLine);
 
        [DllImport("msi.dll", CharSet = CharSet.Auto)]
        internal static extern uint MsiEnableLog(NativeMethods.LogMode dwLogMode, <br>[MarshalAs(UnmanagedType.LPWStr)] string szLogFile, <br>uint dwLogAttributes);
 
        internal delegate int MyMsiInstallUIHandler(IntPtr context, int messageType, <br>[MarshalAs(UnmanagedType.LPWStr)] string message);
 
        [Flags]
        internal enum LogMode : uint
        {
            None = 0u,
            Verbose = 4096u,
            ExternalUI = 20239u
        }
    }

  

简单说明一下,用户定义的UI运行在主线程中,使用BackgroundWorker执行安装任务。在安装进行的过程中可以把cancel信息传递给MsiProgressHandler,当MsiProgressHandler检测到cancel信息后通过返回值告诉msi的执行引擎,执行cancel操作(msi的安装过程是相当严谨的,可不能简单的杀掉安装进程了事!)。

这样,一个支持cancel的自定义UI的安装控制程序就OK了(demo哈)。如果要安装多个msi只需在Install方法中循环就可以了。

总结一下,通过调用几个windows API,我们可以实现对msi安装过程的控制。这比调用msiexec.exe更灵活,也为程序日后添加新的功能打下了基础。

转载:http://www.cnblogs.com/powertoolsteam/p/5316042.html

自定义msi安装包的执行过程的更多相关文章

  1. 绝对干货:自定义msi安装包的执行过程

    有时候我们需要在程序中执行另一个程序的安装,这就需要我们去自定义msi安装包的执行过程. 比如我要做一个安装管理程序,可以根据用户的选择安装不同的子产品.当用户选择了三个产品时,如果分别显示这三个产品 ...

  2. 自己定义msi安装包的运行过程

    有时候我们须要在程序中运行还有一个程序的安装.这就须要我们去自己定义msi安装包的运行过程. 比方我要做一个安装管理程序,能够依据用户的选择安装不同的子产品.当用户选择了三个产品时,假设分别显示这三个 ...

  3. 电脑运行msi安装包提示the error code is 2503/2502如何解决

    当在电脑中运行msi安装包时,出现the error code is 2503或者2502错误提示,其实是由于没有运行的权限导致的,但是又不能右击以管理员身份运行,那么应该如何操作呢?对于这样的问题, ...

  4. 找回MSI安装包Win7/Win8管理员身份功能

    找回MSI安装包Win7/Win8管理员身份功能 从Vista开始,系统引入了UAC用户控制功能,即普通用户运行exe软件安装程序,支持使用普通账户/管理员身份分别进行安装,但是msi安装包只支持默认 ...

  5. arcgis安装msi安装包提示"在未标记为正在运行时,调用了RunScript”解决办法

    安装msi安装包提示"在未标记为正在运行时,调用了RunScript”解决办法   windows/temp目录相关权限不对,右击temp文件夹,选择管理员获取所有权限.

  6. 在Win10中,在安装msi安装包的时候常常会出现代码为2502、2503的错误。

    前言:在Win10中,在安装msi安装包的时候常常会出现代码为2502.2503的错误.其实这种错误是由于安装权限不足造成的,可以这种msi的安装包不像其他exe的安装程序,在安装包上点击“右键”之后 ...

  7. Elasticsearch各版本的MSI安装包和Kibana各版本的zip包(Windows)

    elastic各产品下载列表页:https://www.elastic.co/cn/downloads/ 打开Elasticsearch的下载页后看到的是当前最新版本的安装界面,现在最新的版本是7.4 ...

  8. HOWTO - Basic MSI安装包在安装运行过程中如何获取完整源路径

    有朋友问到如何在一个Windows Installer安装包中获取安装包源路径,就是在安装包运行过程中动态获取*.msi所在完整路径. 这个问题分两类,如果我们的安装包只是一个*.msi安装文件,那么 ...

  9. 利用RTE创建自定义软件安装包(一)

    说明:鉴于MDK5.0推出的新功能,安富莱电子顺势推出几期MDK5.0新功能的使用方法.MDK5.0提供的RTE功能还是很不错的,这个功能一方面方便用户创建自己常用的驱动文件包,还有一个很重要的功能就 ...

随机推荐

  1. python2,3区别

      Python2 Python3 default charset ascii(can change) utf-8 print 可不加括号 必须加 range 有xrange()生成器 可转换为ran ...

  2. javaWeb文件上传与下载

    文件上传与下载在项目中运用的使用频率很大 今天也花时间整理了一下 多文件上传图片回显 和文件下载  1.多文件上传 这里会涉及到几个属性 fileSizeThreshold:缓冲区文件的大小 如果上传 ...

  3. linux命令详解——eval

    shell中的eval 功能说明:重新运算求出参数的内容. 语 法:eval [参数] 补充说明:eval可读取一连串的参数,然后再依参数本身的特性来执行. 参 数:参数不限数目,彼此之间用分号分开. ...

  4. flash多进程写操作

    1 应用场景介绍   硬件条件:使用stm32 MCU   软件条件:协议栈应用   协议栈简单介绍如下:   类似于OSI七层模型,所涉及的协议栈包括应用层,网络层,链路层,物理层,如下图:   在 ...

  5. 通用分页model封装pageList

    package selfimpr.page; import java.util.List; /** * 分页模型 * @param <T> 数据泛型 * @author selfimpr ...

  6. 三道习题(1、将单词表中由相同字母组成的单词归成一类,每类单词按照单词的首字母排序,并按 #每类中第一个单词字典序由大到小排列输出各个类别。 #输入格式:按字典序由小到大输入若干个单词,每个单词占一行,以end结束输入。)

    #coding=gbk ''' 1.将单词表中由相同字母组成的单词归成一类,每类单词按照单词的首字母排序,并按 #每类中第一个单词字典序由大到小排列输出各个类别. #输入格式:按字典序由小到大输入若干 ...

  7. 计算广告(4)----搜索广告召回(也叫match、触发)

    一.搜索广告形态 1.特征工程 特征主要有用户画像(user profile).用户行为(user behavior).广告(ad)和上下文(context)四部分组成,如下所示: 2.平台算法主要分 ...

  8. Java&Selenium 模拟鼠标方法封装

    Java&Selenium 模拟鼠标方法封装 package util; import org.openqa.selenium.By; import org.openqa.selenium.W ...

  9. Java核心技术 卷一 复习笔记(丁

    面向对象1.面向对象设计概述 1.1.面向对象是什么 面向对象是一种程序设计范型(简称OOP),是针对对象进行开发,简化开发过程的一种设计方式 1.2.类 类是构造对象的模板,相当于一个烘焙模板,而对 ...

  10. BZOJ 3576: [Hnoi2014]江南乐 (SG函数)

    题意 有nnn堆石子,给定FFF,每次操作可以把一堆石子数不小于FFF的石子平均分配成若干堆(堆数>1>1>1). 平均分配即指分出来的石子数中最大值减最小值不超过111.不能进行操 ...