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

有时候我们需要在程序中执行另一个程序的安装,这就需要我们去自定义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的主要代码:

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 { get; set; }

        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)] MyMsiInstallUIHandler puiHandler, NativeMethods.LogMode dwMessageFilter, IntPtr pvContext);

        [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(NativeMethods.LogMode dwLogMode, [MarshalAs(UnmanagedType.LPWStr)] string szLogFile, uint dwLogAttributes);

        internal delegate int MyMsiInstallUIHandler(IntPtr context, int messageType, [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更灵活,也为程序日后添加新的功能打下了基础。

感谢葡萄哥Nick 投稿

时间: 2024-11-05 22:57:23

绝对干货:自定义msi安装包的执行过程的相关文章

PowerShell远程安装MSI安装包、EXE可执行程序的方法_PowerShell

尽管对IT管理员或者开发者来讲,这是一个很常见的任务,但是在网上能找到"远程安装一个msi包或者exe应用程序"的相关文档还是比较少的.甚至有人在一些论坛的评论中写道,这不可能实现.事实上还是可以的.我会在本文提供两段代码,分别负责远程安装MSI包和EXE可执行应用程序. 一.安装MSI包 使用PowerShell调用WMI对象,你可以执行下面的脚本来安装你的MSI安装包: 复制代码 代码如下: $box="deviis01" #this is the name o

php自定义apk安装包实例_php技巧

本文实例讲述了php自定义apk安装包的方法,分享给大家供大家参考.具体实现方法如下: 众所周知,apk格式安装文件是android智能系统的安装文件,下面我们来看一个利用php实现自定义apk安装包实例. 一.需求: 需要实现对产品进行一次推荐好友安装的活动,每个会员下载自己的专属安装包(里面记录会员的相关信息). 二.思路: 经过了解,发现apk安装包原来只是zip的一个马甲,使用php的ZipArchive类可以对文件进行操作. 三.实现代码: 复制代码 代码如下: // 源文件 $apk

Perl(CGI)默认的MSI安装包安装失败的处理方式_星外虚拟主机

正常性况下,可以直接下载MSI安装包就可以安装成功,如果因为操作系统权限修改过的原因造成无法安装完成,可以用ZIP包中的install.bat运行来安装,下载地址是: http://downloads.activestate.com/ActivePerl/Windows/5.8/ActivePerl-5.8.7.815-MSWin32-x86-211909.zip

封装MSI安装包实战解析

今天朋友在部署域的软件分发遇到一个难题 软件不支持EXE格式,只支持MSI格式! 但是常见的软件,基本都是EXE格式的! 这里我说一下,我平时是如何封装把EXE格式封装成MSI格式的! 这里我用到的软件是 Advanced Installer 点击导入,选择重新封装安装 选择启动新的安装捕获

从创建一个应用程序到制作一个安装包的详细过程

创建 Windows 应用程序在"文件"菜单上指向"新建",然后选择"项目". 在"新建项目"对话框中,选择"项目类型"窗格中的"Visual Basic 项目",然后选择"模板"窗格中的"Windows 应用程序".在"名称"框中,键入"我的记事本". 此项目被添加到解决方案资源管理器中,并且窗体设计器打开

安装包打包:如何捕获msi升级事件?(System.Configuration.Install)

问题描述 导入System.Configuration.Install类,用于打包时,重写Install方法.在安装的时候,也确实调用了自定义方法.但是当msi版本升级的时候,比如1.0.0升级到1.0.1,并不能捕获Install等任何事件.只有当安装或者卸载的时候,才能捕获Install或者Uninstall等事件.当msi版本升级时,有什么事件能捕获呢,也就说,怎么才能加入自定义操作. 解决方案 解决方案二:或者说思路是错的?根本是无法捕获?解决方案三:CSDN如此萧条了么,怎么没人顶啊解

InstallShield安装包中集成第三方安装包的方案选择

原文:InstallShield安装包中集成第三方安装包的方案选择[转]     我们在制作安装包时,有些情况下会涉及第三方安装的集成,这里将讨论如何调用安装第三方包,以及需要注意的事项.   第三方安装包的介质类型有很多,主要有:单独的一个Setup.exe,单独的一个msi包,或者是类似光盘结构的一组文件及文件夹的安装包. 首先,如何在InstallShield中添加第三方安装包: 如果是单独的Setup.exe或单独的msi包,可以将他们添加到[Behavior and logic] ->

Ubuntu系统如何清理无用的安装包?

Ubuntu系统如何清理无用的安装包? 执行如下代码: sudo apt-get autoremovesudo apt-get install gtkorphan 找到 系统->系统管理->Remove orphaned packages,然后清理不需要的包(注意别误删).

msi-想做一个让用户选择装服务端或客户端的安装包

问题描述 想做一个让用户选择装服务端或客户端的安装包 我现在想做一个MSI安装包,让用户选择安装服务端或者客户端,然后根据用户的选择安装不同的文件,这个功能用VS2010如何实现?哪位高手能指点一下?不胜感谢.