C# 中执行 msi 安装

简介:

实现思路

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

msiexec.exe /qn

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

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

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

主要接口介绍

我们先来了解一下主要用到的几个 win32 API。

首先是 MsiSetInternalUI 方法:

[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)

 

接下来是 MsiSetExternalUI 方法:

[DllImport("msi.dll", CharSet = CharSet.Auto)]internal static extern MsiInstallUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] MsiInstallUIHandler puiHandler, 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,感兴趣的同学可以去看看。

 

下面是 MsiInstallProduct 方法:

[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.cspublic 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.csinternal 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 方法中循环就可以了。
















本文转自xmgdc51CTO博客,原文链接:http://blog.51cto.com/12953214/1942277 ,如需转载请自行联系原作者



相关文章
|
C语言 C++ Windows
编译Windows版本ffmpeg:msys2方式失败
编译Windows版本ffmpeg:msys2方式失败
135 0
编译Windows版本ffmpeg:msys2方式失败
|
Linux C语言 Windows
编译Windows版本ffmpeg:MingW方式失败
编译Windows版本ffmpeg:MingW方式失败
93 0
编译Windows版本ffmpeg:MingW方式失败
|
Windows
WIN7安装FreeSwitch,1.8版本无法安装,1.6版本成功
WIN7安装FreeSwitch,1.8版本无法安装,1.6版本成功
103 0
|
Linux Windows
win10下WSL2更新内核
win10下WSL2更新内核
4676 0
win10下WSL2更新内核
|
Windows
“inno setup打包,win7下安装没有桌面快捷方式,xp下安装正常”
修改桌面的快捷键为选中就行了:Flags: checkablealone;在[Tasks]下面修改代码如下:Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIco...
995 0
|
程序员 Linux Windows
win7系统安装 cygwin 的详细步骤
它是用来干什么的呢?简单来说,它提供了能在Windows下完成一些Linux的操作。如果你是一个Linux的狂热爱好者,必然对Windows有着一些偏见,但是由于太多Windows才能使用的软件,或者是家里人不会使用Linux等原因,导致你不能放弃Windows,这时候该怎么办呢?Cygwin就是你的最佳选择!   Cygwin只是提供了类似于Linux的环境,实际使用上还是有一些差异的。
1958 0