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 ,如需转载请自行联系原作者



相关文章
|
开发框架 前端开发 .NET
C#编程与Web开发
【4月更文挑战第21天】本文探讨了C#在Web开发中的应用,包括使用ASP.NET框架、MVC模式、Web API和Entity Framework。C#作为.NET框架的主要语言,结合这些工具,能创建动态、高效的Web应用。实际案例涉及企业级应用、电子商务和社交媒体平台。尽管面临竞争和挑战,但C#在Web开发领域的前景将持续拓展。
507 3
|
SQL 开发框架 安全
C#编程与多线程处理
【4月更文挑战第21天】探索C#多线程处理,提升程序性能与响应性。了解C#中的Thread、Task类及Async/Await关键字,掌握线程同步与安全,实践并发计算、网络服务及UI优化。跟随未来发展趋势,利用C#打造高效应用。
378 3
|
2月前
|
XML 前端开发 C#
C#编程实践:解析HTML文档并执行元素匹配
通过上述步骤,可以在C#中有效地解析HTML文档并执行元素匹配。HtmlAgilityPack提供了一个强大而灵活的工具集,可以处理各种HTML解析任务。
173 19
|
3月前
|
监控 算法 C#
C#与Halcon联合编程实现鼠标控制图像缩放、拖动及ROI绘制
C#与Halcon联合编程实现鼠标控制图像缩放、拖动及ROI绘制
506 0
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
245 3
|
11月前
|
存储 安全 编译器
学懂C#编程:属性(Property)的概念定义及使用详解
通过深入理解和使用C#的属性,可以编写更清晰、简洁和高效的代码,为开发高质量的应用程序奠定基础。
809 12
|
12月前
|
设计模式 C# 图形学
Unity 游戏引擎 C# 编程:一分钟浅谈
本文介绍了在 Unity 游戏开发中使用 C# 的基础知识和常见问题。从 `MonoBehavior` 类的基础用法,到变量和属性的管理,再到空引用异常、资源管理和性能优化等常见问题的解决方法。文章还探讨了单例模式、事件系统和数据持久化等高级话题,旨在帮助开发者避免常见错误,提升游戏开发效率。
473 4
|
安全 程序员 编译器
C#一分钟浅谈:泛型编程基础
在现代软件开发中,泛型编程是一项关键技能,它使开发者能够编写类型安全且可重用的代码。C# 自 2.0 版本起支持泛型编程,本文将从基础概念入手,逐步深入探讨 C# 中的泛型,并通过具体实例帮助理解常见问题及其解决方法。泛型通过类型参数替代具体类型,提高了代码复用性和类型安全性,减少了运行时性能开销。文章详细介绍了如何定义泛型类和方法,并讨论了常见的易错点及解决方案,帮助读者更好地掌握这一技术。
228 11
|
API C#
C# 一分钟浅谈:文件系统编程
在软件开发中,文件系统操作至关重要。本文将带你快速掌握C#中文件系统编程的基础知识,涵盖基本概念、常见问题及解决方法。文章详细介绍了`System.IO`命名空间下的关键类库,并通过示例代码展示了路径处理、异常处理、并发访问等技巧,还提供了异步API和流压缩等高级技巧,帮助你写出更健壮的代码。
159 2

热门文章

最新文章