简体   繁体   English

如何在 C# 中找到已安装应用程序的升级代码?

[英]How can I find the upgrade code for an installed application in C#?

I am using the C# wrapper for the Windows Installer API from the WIX Toolset .我正在使用WIX 工具集中的 Windows Installer API 的 C# 包装器。 I use the ProductInstallation class to get information about the installed products such as the product code and product name.我使用ProductInstallation类来获取有关已安装产品的信息,例如产品代码和产品名称。

For example例如

  • Product Name - "My Test Application"产品名称 - “我的测试应用程序”
  • Product Code - {F46BA620-C027-4E68-9069-5D5D4E1FF30A}产品代码 - {F46BA620-C027-4E68-9069-5D5D4E1FF30A}
  • Product Version - 1.4.0产品版本 - 1.4.0

Internally this wrapper uses the MsiGetProductInfo function.此包装器在内部使用MsiGetProductInfo函数。 Unfortunately this function does not return the product's upgrade code.不幸的是,此函数不会返回产品的升级代码。

How can I retrieve the upgrade code for an installed application using C#?如何使用 C# 检索已安装应用程序的升级代码?

I have discovered the upgrade codes are stored in the following registry location.我发现升级代码存储在以下注册表位置。

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

The registry key name is the upgrade code and the registry key value name is the product code.注册表项名称是升级代码,注册表项值名称是产品代码。 I can easily extract these values however the codes are stored in a different format.我可以轻松提取这些值,但是代码以不同的格式存储。 The red circle shows the formatted upgrade code, the blue circle shows the formatted product code when viewing it in regedit.exe .红圈是格式化后的升级代码,蓝圈是在regedit.exe查看时格式化的产品代码。

红圈为格式化升级码,蓝圈为格式化产品码

The hyphens are stripped out of the Guid and then a series of string reversals are done.连字符从Guid中剥离,然后完成一系列字符串反转。 The first 8 characters are reversed, then the next 4, then the following 4 and then the rest of the string is reversed in sets of 2 characters.前 8 个字符被反转,然后是接下来的 4 个,然后是接下来的 4 个,然后字符串的其余部分以 2 个字符为一组反转。 Normally when reversing a string we need to take care in making sure control and special characters are handled correctly ( see Jon Skeet's aricle here ) but as we are, in this case, dealing with a Guid string we can be confident the string will be reversed correctly.通常,在反转字符串时,我们需要注意确保正确处理控制和特殊字符( 请参阅此处的 Jon Skeet 的文章),但正如我们一样,在这种情况下,处理Guid字符串我们可以确信该字符串将被反转正确。

Below is the complete code I used to extract the upgrade code for a known product code from the registry.下面是我用来从注册表中提取已知产品代码的升级代码的完整代码。

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

This is the opposite method to get the ProductCode from an UpgradeCode.这是从 UpgradeCode 获取 ProductCode 的相反方法。 Could be useful for somebody.可能对某人有用。

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

The InstallPackage class has a property called LocalPackage. InstallPackage 类有一个名为 LocalPackage 的属性。 You can use this to query the MSI database that's cached in C:\\Windows\\Installer and get anything you could possibly want to know about it.您可以使用它来查询缓存在 C:\\Windows\\Installer 中的 MSI 数据库,并获取您可能想了解的任何信息。

Just commenting in case this comes in handy in the future for anyone!只是评论以防万一这在未来对任何人都派上用场!

In the case that you only have the GUID or code available, the following site can be used to convert between the two:如果您只有 GUID 或代码可用,则可以使用以下站点在两者之间进行转换:

https://daysoff.io/flipguid https://daysoff.io/flipguid

Hopefully this can save some future headaches!希望这可以节省一些未来的头痛!

Here is a much more simple way to get the GUID formatted in registry format (which basically is just a raw byte representation)这是一种以注册表格式(基本上只是原始字节表示)获取 GUID 格式的更简单的方法

First is to get raw bytes:首先是获取原始字节:

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

Then just flipp the endianness of BitConverter.ToString() result然后只需翻转 BitConverter.ToString() 结果的字节序

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

And here is your helper modified in such a way that it also works in .Net3.5 32 bit applications.这是您的助手,它以这样的方式修改,它也适用于 .Net3.5 32 位应用程序。 They need special treatement because .net 3.5 has no awareness about registry being splitted between 32 and 64 bit entries.它们需要特殊处理,因为 .net 3.5 不知道注册表被分成 32 位和 64 位条目。 My solution is only using To64BitPath to browse 64 bit part of it.我的解决方案仅使用To64BitPath来浏览其中的 64 位部分。 There is also a great tutorial that uses DllImports for that: https://www.rhyous.com/2011/01/24/how-read-the-64-bit-registry-from-a-32-bit-application-or-vice-versa/还有一个使用 DllImports 的很棒的教程: https : //www.rhyous.com/2011/01/24/how-read-the-64-bit-registry-from-a-32-bit-application-反之亦然/

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM