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

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

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

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

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

我注意到在启动条件 window 中,我可以设置注册表搜索启动条件,它根据特定注册表项设置安装程序 属性。我可以使用它来检索在文件 window 中使用的密钥的实际值,还是仅出于启动条件的目的设置 true/false 值?

啊,终于在MSDN documentation找到答案了。没有自定义操作也是可能的!

总结:

  1. 在启动条件 window 的 "Search Target Machine" 节点下,添加注册表搜索操作。配置 "RegKey" 和 "Value" 属性以指定包含插件需要安装到的安装路径的注册表项值的名称。将注册表搜索操作的 "Property" 属性 设置为合理的名称,例如"ProductInstallPath"
  2. (可选)在 Launch Conditions 节点下,添加 Launch Condition 并将其 Condition 属性 设置为 [ProductInstallPath]。我认为这将在安装程序运行时检查注册表项值是否存在且非空。
  3. 在安装项目的 "File System" window 中,右键单击 "File System on Target Machine" 和 select "Add Special Folder"、"Custom Folder"
  4. 将新文件夹的默认位置 属性 设置为 [ProductInstallPath]

编辑:叹息。由于 Visual Studio 安装项目中的一个错误,这在 x64 上不起作用,该错误至少自 VS2008 以来一直存在,并且仍然存在于 VS2015 中。即使安装项目平台设置为 x64,注册表搜索操作始终搜索 x86 注册表配置单元并且看不到 x64 HKLM\SOFTWARE 键值。

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

Bug Citation 1

Bug Citation 2

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

但是,在完全切换到 WiX 之前,我创建了一个小型 C# 控制台应用程序,它可以作为 post-build 事件调用,以编辑由 Visual Studio 安装项目生成的 MSI 文件。控制台应用程序基于 WiX Toolset 中包含的 Deployment Tools Foundation (DTF)。 DTF 提供了一个 C# API 用于编辑 MSI 文件。这是它的主要内容,可能对未来的用户有用。

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();
            }
        }
    }
}