使用 Visual Studio 安装项目将文件安装到现有程序的安装路径

Install files to existing program's install path with Visual Studio Setup project

我正在制作一个 Visual Studio 安装项目,它会为目标机器上的现有程序安装一个插件。我的文件需要进入该应用程序的安装目录。我希望能够干净地安装和卸载我的插件,而不会对应用程序本身产生不必要的影响。

现有程序的安装路径可以在注册表项中找到,并且可能因安装而异。

我能否将 Visual Studio 安装项目配置为从该注册表项读取值,然后将插件文件放入注册表指定的目录(或其子目录)中?我需要使用自定义操作还是可以使用标准的安装项目功能来实现?

我注意到在启动条件窗口中,我可以设置一个注册表搜索启动条件,它根据特定的注册表项设置安装程序属性。我可以使用它来检索密钥的实际值以在"文件"窗口中使用,还是仅为启动条件设置真/假值?


啊,毕竟在 MSDN 文档中找到了答案。没有自定义操作也是可能的!

总结:

  • 在"启动条件"窗口中的"搜索目标机器"节点下,添加注册表搜索操作。配置 "RegKey" 和 "Value" 属性以指定包含插件需要安装到的安装路径的注册表项值的名称。将注册表搜索操作的 "Property" 属性设置为一个合理的名称,例如"产品安装路径"
  • (可选)在 Launch Conditions 节点下,添加 Launch Condition 并将其 Condition 属性设置为 [ProductInstallPath]。我认为这将在安装程序运行时检查注册表项值是否存在并且是否为非空。
  • 在安装项目的"文件系统"窗口中,右键单击"目标机器上的文件系统"并选择"添加特殊文件夹"、"自定义文件夹"
  • 将新文件夹的默认位置属性设置为 [ProductInstallPath]
  • 编辑:叹息。由于 Visual Studio 安装项目中的一个错误,这在 x64 上不起作用,至少自 VS2008 以来一直很突出,并且在 VS2015 中仍然存在。即使安装项目平台设置为 x64,注册表搜索操作始终搜索 x86 注册表配置单元,并且看不到 x64 HKLM\\\\\\\\SOFTWARE 键值。

    详细信息:生成的 MSI 文件中的 RegLocator 表包含用于注册表搜索的数据。 Type 字段包含导致搜索为 64 位本机注册表的 msidbLocatorType64bit 值。添加此值以更正问题。手动(使用 Orca)是测试功能的快速方法。 RegLocator 表

    错误引用 1

    错误引用 2

    我获得工作安装程序的最终解决方案是使用 WiX 创建一个基本安装程序,并完全放弃 Visual Studio 安装项目。

    但是,在完全切换到 WiX 之前,我创建了一个小型 C# 控制台应用程序,该应用程序可以作为构建后事件调用,以编辑由 Visual Studio 安装项目生成的 MSI 文件。控制台应用程序基于 WiX 工具集随附的部署工具基础 (DTF)。 DTF 提供了一个用于编辑 MSI 文件的 C# API。这是它的主要内容,可能对未来的用户有用。

    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
    using System;
    using System.IO;
    using Microsoft.Deployment.WindowsInstaller;

    /// <summary>
    /// This program patches the registry search key action in the MSI file produced by the Visual Studio Setup project,
    /// to correct x64 compatibility bugs in Visual Studio Setup Projects.
    /// </summary>
    /// <remarks>
    /// The two bugs are:
    /// 1) The Visual Studio setup project incorporates the 32-bit version of InstallUtilLib.dll, which can't load x64 assemblies for reflection
    /// See https://blogs.msdn.microsoft.com/heaths/2006/02/01/64-bit-managed-custom-actions-with-visual-studio/
    /// 2) Registry search actions don't set the x64 bit and therefore only search the 32-bit registry
    /// See https://social.msdn.microsoft.com/Forums/windows/en-US/40a2c1ee-7dd4-4289-a7d2-30b97239ae25/vs2005-setup-project-launch-conditions-registry-problem-on-x64-operating-systems
    /// </remarks>
    class SetupPatcher
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("ERROR: Specify the name of the MSI file as the first parameter when calling this exe");
                Environment.Exit(1);
            }

            String msiName = args[0];

            using (var db = new Database(msiName, DatabaseOpenMode.Direct))
            {
                PatchInstallUtilLib(db);
                PatchRegLocator(db);
            }
        }

        /// <summary>
        /// Replace 32 bit InstallUtilLib.dll with x64 version
        /// </summary>
        /// <param name="db"></param>
        private static void PatchInstallUtilLib(Database db)
        {
            using (View view = db.OpenView(@"UPDATE `Binary` SET `Data` = ? WHERE `Name` = 'InstallUtil'"))
            {
                using (Record rec = new Record(1))
                {
                    String path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows),
                        @"Microsoft.NET\\Framework64\\v4.0.30319\\InstallUtilLib.dll");
                    rec.SetStream(1, path);
                    view.Execute(rec);
                    db.Commit();
                }
            }
        }

        private static void PatchRegLocator(Database db)
        {
            // MSI SQL syntax documented here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa372021.aspx
            // Schema of RegLocator table given at https://msdn.microsoft.com/EN-US/library/aa371171.aspx
            // Look for reg search actions of the Raw type in the HKLM registry root
            String registryKey = @"SOFTWARE\\VendorName\\ProductName";

            using (View view =
                db.OpenView(
                    @"UPDATE `RegLocator` SET `Type` = ? WHERE `Type` = {0} AND `Root` = {1} AND `Key` = '{2}'",
                    (Int32) LocatorTypes.RawValue, (Int32) RegistryRoot.LocalMachine, registryKey))
            {
                using (Record rec = new Record(1))
                {
                    rec.SetInteger(1, (Int32) (LocatorTypes.SixtyFourBit | LocatorTypes.RawValue));
                    view.Execute(rec);
                    db.Commit();
                }
            }
        }
    }