【问题标题】:How can I find the upgrade code for an installed application in C#?如何在 C# 中找到已安装应用程序的升级代码?
【发布时间】:2013-07-29 22:52:14
【问题描述】:

我正在使用来自WIX Toolset 的 Windows Installer API 的 C# 包装器。我使用ProductInstallation 类来获取有关已安装产品的信息,例如产品代码和产品名称。

例如

  • 产品名称 - “我的测试应用程序”
  • 产品代码 - {F46BA620-C027-4E68-9069-5D5D4E1FF30A}
  • 产品版本 - 1.4.0

这个包装器在内部使用MsiGetProductInfo 函数。不幸的是,这个函数不返回产品的升级代码。

如何使用 C# 检索已安装应用程序的升级代码?

【问题讨论】:

  • 不要使用下面建议的注册表,我建议您使用此答案中描述的 WMIHow can I find the Upgrade Code for an installed MSI file?。这将确保您检索到正确的升级代码,并且不需要任何转换或解释。 您将以正确的格式恢复真正的升级代码

标签: c# wix registry windows-installer


【解决方案1】:

我发现升级代码存储在以下注册表位置。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes

注册表项名称是升级代码,注册表项值名称是产品代码。我可以轻松提取这些值,但是代码以不同的格式存储。红圈为格式化后的升级码,蓝圈为在regedit.exe查看时格式化后的产品码。

连字符从Guid 中去除,然后完成一系列字符串反转。前 8 个字符被反转,然后是接下来的 4 个,然后是后面的 4 个,然后字符串的其余部分以 2 个字符为一组进行反转。通常在反转字符串时,我们需要注意确保正确处理控制和特殊字符(see Jon Skeet's aricle here),但在这种情况下,处理Guid 字符串时,我们可以确信字符串将被反转正确。

以下是我用来从注册表中提取已知产品代码的升级代码的完整代码。

internal static class RegistryHelper
{
    private const string UpgradeCodeRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes";

    private static readonly int[] GuidRegistryFormatPattern = new[] { 8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2 };

    public static Guid? GetUpgradeCode(Guid productCode)
    {
        // Convert the product code to the format found in the registry
        var productCodeSearchString = ConvertToRegistryFormat(productCode);

        // Open the upgrade code registry key
        var localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
        var upgradeCodeRegistryRoot = localMachine.OpenSubKey(UpgradeCodeRegistryKey);

        if (upgradeCodeRegistryRoot == null)
            return null;

        // Iterate over each sub-key
        foreach (var subKeyName in upgradeCodeRegistryRoot.GetSubKeyNames())
        {
            var subkey = upgradeCodeRegistryRoot.OpenSubKey(subKeyName);

            if (subkey == null)
                continue;

            // Check for a value containing the product code
            if (subkey.GetValueNames().Any(s => s.IndexOf(productCodeSearchString, StringComparison.OrdinalIgnoreCase) >= 0))
            {
                // Extract the name of the subkey from the qualified name
                var formattedUpgradeCode = subkey.Name.Split('\\').LastOrDefault();

                // Convert it back to a Guid
                return ConvertFromRegistryFormat(formattedUpgradeCode);
            }
        }

        return null;
    }

    private static string ConvertToRegistryFormat(Guid productCode)
    {
        return Reverse(productCode, GuidRegistryFormatPattern);
    }

    private static Guid ConvertFromRegistryFormat(string upgradeCode)
    {
        if (upgradeCode == null || upgradeCode.Length != 32)
            throw new FormatException("Product code was in an invalid format");

        upgradeCode = Reverse(upgradeCode, GuidRegistryFormatPattern);

        return Guid.Parse(upgradeCode);
    }

    private static string Reverse(object value, params int[] pattern)
    {
        // Strip the hyphens
        var inputString = value.ToString().Replace("-", "");

        var returnString = new StringBuilder();

        var index = 0;

        // Iterate over the reversal pattern
        foreach (var length in pattern)
        {
            // Reverse the sub-string and append it
            returnString.Append(inputString.Substring(index, length).Reverse().ToArray());

            // Increment our posistion in the string
            index += length;
        }

        return returnString.ToString();
    }
}

【讨论】:

  • 上述程序的 Alex 正在为相应的产品代码返回 NULL。每个升级代码是否都与产品代码一起输入?我已经从我的产品代码中删除了 backets,但没有删除连字符 (-)。还是不行。
  • 调试后我发现注册表存在于给定位置,但 upgradeCodeRegistryRoot 正在获取 NULL 值
  • "注册表键名是升级码"错了!注册表项不等于升级代码!
  • @AmirMahdiNassiri 在 Windows 10 x64 上适合我。您是否阅读了我所说的格式不同的部分并且需要转换它?当我提到注册表项名称时,我指的是直接在 UpgradeCodes 项下的实际键名(在 regedit 中它显示为一个文件夹)。不是该键中的注册表值名称(即产品代码)。
  • @Keshav 如果您在 64 位操作系统上的 32 位进程中运行此代码,则会出现问题。请查看我测试过的更新代码现在可以在 64 位 Windows 10 上正常运行。
【解决方案2】:

InstallPackage 类有一个名为 LocalPackage 的属性。您可以使用它来查询缓存在 C:\Windows\Installer 中的 MSI 数据库,并获取您可能想知道的任何信息。

【讨论】:

  • 是的,这是正确的,但它并不可靠。只要 MSI 软件包仍然存在,它才有用。在对几台 PC 进行快速测试期间,我发现 MSI 不存在是很常见的(测试的 593 个软件包中缺少 27 个或约 5%)。在这些测试中,所有升级代码仍然可以从注册表中获得。
  • 那些 MSI 应该始终存在,否则您将遇到修复、卸载、广告快捷方式等问题。 blogs.msdn.com/b/sqlserverfaq/archive/2013/04/30/…
  • 我同意;他们应该在那里。不幸的是,有时他们不是。我应该详细说明这是针对将安装在客户端 PC 上的库存样式服务,因此我需要一种更可靠的方法。
  • 作为一个库存工具,我想这是你想要捕捉的东西。此外,FWIW,UpgradeCode 不是 MSI 的必需属性。强烈推荐,但不是必需的。
  • 顺便说一句,如果您想获得最大的可靠性,您可以使用 BOTH 这些方法中的任何一种,并在这种情况下采用哪种方法为您提供答案。如果有人愚蠢到可以删除 MSI 文件,他们也可以愚蠢到删除注册表项。
【解决方案3】:

这是从 UpgradeCode 获取 ProductCode 的相反方法。可能对某人有用。

using Microsoft.Win32;
using System;
using System.IO;
using System.Linq;
using System.Text;

internal static class RegistryHelper
{
    private const string UpgradeCodeRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes";

    private static readonly int[] GuidRegistryFormatPattern = new[] { 8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2 };


    public static Guid? GetProductCode(Guid upgradeCode)
    {
        // Convert the product code to the format found in the registry
        var productCodeSearchString = ConvertToRegistryFormat(upgradeCode);

        // Open the upgrade code registry key
        var upgradeCodeRegistryRoot = GetRegistryKey(Path.Combine(UpgradeCodeRegistryKey, productCodeSearchString));

        if (upgradeCodeRegistryRoot == null)
            return null;

        var uninstallCode = upgradeCodeRegistryRoot.GetValueNames().FirstOrDefault();
        if (string.IsNullOrEmpty(uninstallCode))
        {
            return null;
        }

        // Convert it back to a Guid
        return ConvertFromRegistryFormat(uninstallCode);
    }





    private static string ConvertToRegistryFormat(Guid code)
    {
        return Reverse(code, GuidRegistryFormatPattern);
    }

    private static Guid ConvertFromRegistryFormat(string code)
    {
        if (code == null || code.Length != 32)
            throw new FormatException("Product code was in an invalid format");

        code = Reverse(code, GuidRegistryFormatPattern);

        return Guid.Parse(code);
    }

    private static string Reverse(object value, params int[] pattern)
    {
        // Strip the hyphens
        var inputString = value.ToString().Replace("-", "");

        var returnString = new StringBuilder();

        var index = 0;

        // Iterate over the reversal pattern
        foreach (var length in pattern)
        {
            // Reverse the sub-string and append it
            returnString.Append(inputString.Substring(index, length).Reverse().ToArray());

            // Increment our posistion in the string
            index += length;
        }

        return returnString.ToString();
    }

    static RegistryKey GetRegistryKey(string registryPath)
    {
        var hklm64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
        var registryKey64 = hklm64.OpenSubKey(registryPath);
        if (((bool?)registryKey64?.GetValueNames()?.Any()).GetValueOrDefault())
        {
            return registryKey64;
        }

        var hklm32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
        return hklm32.OpenSubKey(registryPath);
    }
}

【讨论】:

  • @Alex Wiese 代码对我来说很好用。这段代码没有。但是,它通过查找给定 UpgradeCode 的 ProductCode 来执行相反的查找。
  • @harlam357 感谢您的评论。你说的对。我已更新答案以补充已接受的答案。
【解决方案4】:

这是您的帮助程序,其修改方式使其也适用于 .Net3.5 32 位应用程序。它们需要特殊处理,因为 .net 3.5 不知道注册表被分为 32 位和 64 位条目。我的解决方案是只使用To64BitPath 来浏览它的 64 位部分。还有一个很棒的教程使用 DllImports:https://www.rhyous.com/2011/01/24/how-read-the-64-bit-registry-from-a-32-bit-application-or-vice-versa/

class RegistryHelper
{
    private const string UpgradeCodeRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes";
    private const string UninstallRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";

    private static readonly int[] GuidRegistryFormatPattern = new[] { 8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2 };



    public static string To64BitPath(string path)
    { 
        return path.Replace("SOFTWARE\\Microsoft", "SOFTWARE\\WOW6432Node\\Microsoft");
    }

    private static RegistryKey GetLocalMachineRegistryKey(string path)
    {
        return RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, string.Empty).OpenSubKey(path);
    }

    public static IEnumerable<Guid> GetUpgradeCodes()
    {
        var list = new List<Guid>();

        var key = GetRegistryKey(UpgradeCodeRegistryKey);
        if (key != null)
        {
            list.AddRange(key.GetSubKeyNames().Select(ConvertFromRegistryFormat));
        }

        return list;
    }

    public static Guid? GetProductCode(Guid upgradeCode)
    {
        // Convert the product upgradeCode to the format found in the registry
        var productCodeSearchString = ConvertToRegistryFormat(upgradeCode);

        // Open the upgradeCode upgradeCode registry key
        var upgradeCodeRegistryRoot = GetRegistryKey(Path.Combine(UpgradeCodeRegistryKey, productCodeSearchString));

        if (upgradeCodeRegistryRoot == null)
            return null;

        var uninstallCode = upgradeCodeRegistryRoot.GetValueNames().FirstOrDefault();
        if (string.IsNullOrEmpty(uninstallCode))
        {
            return null;
        }

        // Convert it back to a Guid
        return ConvertFromRegistryFormat(uninstallCode);
    }

    public static string ConvertToRegistryFormat(Guid code)
    {
        return Reverse(code, GuidRegistryFormatPattern);
    }

    private static Guid ConvertFromRegistryFormat(string code)
    {
        if (code == null || code.Length != 32)
            throw new FormatException("Product upgradeCode was in an invalid format");

        code = Reverse(code, GuidRegistryFormatPattern);

        return new Guid(code);
    }

    private static string Reverse(object value, params int[] pattern)
    {
        // Strip the hyphens
        var inputString = value.ToString().Replace("-", "");

        var returnString = new StringBuilder();

        var index = 0;

        // Iterate over the reversal pattern
        foreach (var length in pattern)
        {
            // Reverse the sub-string and append it
            returnString.Append(inputString.Substring(index, length).Reverse().ToArray());

            // Increment our posistion in the string
            index += length;
        }

        return returnString.ToString();
    }

    static RegistryKey GetRegistryKey(string registryPath)
    {
        var registryKey64 = GetLocalMachineRegistryKey(To64BitPath(registryPath));
        if (((bool?)registryKey64?.GetValueNames()?.Any()).GetValueOrDefault())
        {
            return registryKey64;
        }

        return GetLocalMachineRegistryKey(registryPath);
    }


    public static Guid? GetUpgradeCode(Guid productCode)
    {
        var productCodeSearchString = ConvertToRegistryFormat(productCode);
        var upgradeCodeRegistryRoot = GetRegistryKey(UpgradeCodeRegistryKey);

        if (upgradeCodeRegistryRoot == null)
        {
            return null;
        }

        // Iterate over each sub-key
        foreach (var subKeyName in upgradeCodeRegistryRoot.GetSubKeyNames())
        {
            var subkey = upgradeCodeRegistryRoot.OpenSubKey(subKeyName);

            if (subkey == null)
                continue;

            // Check for a value containing the product upgradeCode
            if (subkey.GetValueNames().Any(s => s.IndexOf(productCodeSearchString, StringComparison.OrdinalIgnoreCase) >= 0))
            {
                // Extract the name of the subkey from the qualified name
                var formattedUpgradeCode = subkey.Name.Split('\\').LastOrDefault();

                // Convert it back to a Guid
                return ConvertFromRegistryFormat(formattedUpgradeCode);
            }
        }

        return null;
    }
}

【讨论】:

    【解决方案5】:

    只是评论一下,以防将来对任何人都派上用场!

    如果您只有 GUID 或代码可用,可以使用以下站点在两者之间进行转换:

    https://daysoff.io/flipguid

    希望这可以避免一些未来的麻烦!

    【讨论】:

    • 不幸的是,该 URL 已失效。
    【解决方案6】:

    这是一种更简单的方法,可以将 GUID 格式化为注册表格式(基本上只是原始字节表示)

    首先是获取原始字节:

    var guidBytes = Guid.Parse(productCode).ToByteArray();
    

    然后只需翻转 BitConverter.ToString() 结果的字节序

    var convertedString = String.Concat(BitConverter.ToString(guidBytes).Split('-').SelectMany(s => s.Reverse()));
    

    【讨论】:

      猜你喜欢
      • 2018-03-20
      • 2018-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-15
      • 2013-03-18
      • 2018-08-20
      相关资源
      最近更新 更多