/FormatMsi

从msi安装包文件中获取属性数据实现安装包的重命名

Primary LanguageC#MIT LicenseMIT

从msi安装包文件中获取属性数据实现安装包的重命名

写这篇文章的起因是,女朋友在实现CI时,最后一步是生成一个安装包归档。生成安装包使用的工具是Visual Studio Installer,这个工具在VS2017之后已经不再默认提供,当然还是可以以VS插件的形式加载进来使用,可以通过“工具-扩展和更新”在线下载或者离线下载再安装进来。使用这个工具制作的安装包,可以指定安装包名称和版本号,但是有一个蛋疼的地方在于,生成的安装包的名字没有版本宏可以附加,导致的结果就是生成的安装包的名字没法带版本号。人工去改安装包名加上版本号就体现不出CI的优势了。她和我分享了这么一个故事,她的解决思路是用python之类的脚本文件解析生成安装包的工程文件.vdproj文件,然后找出版本对应的字段,在生成安装包后用这个字段改名。我从另一个角度去想了这个问题,于是,就有了这篇文章。

如何使用

假设制作的安装包中,给定的安装包名称为InstallerDemo,版本号为2.0.0.100,用Visual Studio Installer生成的安装包为InstallerDemo.msi。

  • 执行命令FormatMsi.exe InstallerDemo.msi后该安装包将被重命名为InstallerDemo_2.0.0.100.msi。
  • 或直接运行FormatMsi.exe,然后输入安装包名InstallerDemo.msi,之后该安装包也会被重命名为InstallerDemo_2.0.0.100.msi。

代码在GitHub上的FormatMsi中开源,有相应的Release包可以下载,也可以参考本文。代码依赖.Net Framework 4.5.2。

关键源码

代码很简单,C#写的,结合注释看看就懂了。核心部分在GetPropertyFromMsi函数中,通过WindowsInstaller解析msi文件,读出属性数据,再根据属性改名。

如果有自定义的需求,可以参考Main中formatMsiFileName的上下文修改。

////////////////////////////////////////////////////////////////////////////////
//
// MIT License
//
// Copyright (c) 2021 kongdeyou(https://tis.ac.cn/blog/kongdeyou/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

using System;
using System.IO;
using WindowsInstaller;

namespace FormatMsi
{
    class Program
    {
        static void Main(string[] args)
        {
            String inputFile = null;

            if (args.Length == 1)
            {
                inputFile = args[0];
            }
            else
            {
                Console.WriteLine("Please enter the msi file:");
                inputFile = Console.ReadLine();
            }

            String productName = null;
            String productVersion = null;
            String formatMsiFileName = null;
            try
            {
                if (inputFile.EndsWith(".msi", StringComparison.OrdinalIgnoreCase))
                {
                    productName = GetPropertyFromMsi(inputFile, "ProductName");
                    productVersion = GetPropertyFromMsi(inputFile, "ProductVersion");
                }
                else
                {
                    Console.WriteLine("Error: Invalid input file!");
                    return;
                }

                formatMsiFileName = String.Format("{0}_{1}.msi", productName, productVersion);
                Console.WriteLine("Format msi file name: " + formatMsiFileName);

                File.Copy(inputFile, formatMsiFileName);
                File.Delete(inputFile);
            }
            catch (Exception exception)
            {
                Console.WriteLine("Exception: " + exception.Message);
            }
        }

        static String GetPropertyFromMsi(String msi, String property)
        {
            String ret = null;

            // WindowsInstaller from [SYSTEM]:\Windows\System32\msi.dll
            Type classType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
            Installer installer = Activator.CreateInstance(classType) as Installer;

            // Open the msi file for reading, 0 means read, 1 means read and write
            Database database = installer.OpenDatabase(msi, 0);

            // The requested property fetching command
            String sql = String.Format(
                "SELECT Value FROM Property WHERE Property='{0}'", property);

            // Open the database view and then execute SQL command
            View view = database.OpenView(sql);
            view.Execute(null);

            // Read from the fetched record
            Record record = view.Fetch();
            if (record != null)
            {
                ret = record.get_StringData(1);
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(record);
            }

            // Close the database view
            view.Close();

            // Release the view's and the database's COM object
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(view);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(database);

            return ret;
        }
    }
}

添加引用

除了以上的代码外,我们在代码中使用了一个关于msi的库,即WindowsInstaller,它在C#项目模板中默认是不带的,我们需要把它加进来。

在解决方案资源管理器中找到对应的项目,这里就是FormatMsi,然后在“引用”上右键,选择“添加引用”,然后会弹出一个对话框,选择最下方的“浏览”,找到系统目录下的Windows\System32\msi.dll文件,添加进来。

协议

本文以上内容遵循CC BY-ND 4.0协议,署名-禁止演绎。

本文中的源代码遵循MIT开源协议。 代码托管于:https://github.com/KondeU/FormatMsi

转载请注明出处:https://tis.ac.cn/blog/kongdeyou/format_msi/

作者:kongdeyou(https://tis.ac.cn/blog/author/kongdeyou/)