Как я могу найти код обновления для установленного приложения в C#?

Я использую оболочку C# для API установщика Windows из набора инструментов WIX. Я использую ProductInstallation класс для получения информации об установленных продуктах, таких как код продукта и название продукта.

Например

  • Название продукта - "Моя тестовая заявка"
  • Код продукта - {F46BA620-C027-4E68-9069-5D5D4E1FF30A}
  • Версия продукта - 1.4.0

Внутренне эта оболочка использует функцию MsiGetProductInfo. К сожалению, эта функция не возвращает код обновления продукта.

Как я могу получить код обновления для установленного приложения, используя C#?

6 ответов

Решение

Я обнаружил, что коды обновления хранятся в следующем разделе реестра.

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

Имя раздела реестра - это код обновления, а значение параметра реестра - это код продукта. Я могу легко извлечь эти значения, однако коды хранятся в другом формате. Красный кружок показывает отформатированный код обновления, синий кружок показывает отформатированный код продукта при его просмотре в regedit.exe,

Красный круг - это отформатированный код обновления, синий круг - отформатированный код продукта

Дефисы удаляются из Guid и затем выполняется последовательность перестановок строк. Первые 8 символов меняются местами, затем следующие 4, затем следующие 4, а затем остальная часть строки переворачивается в наборах по 2 символа. Обычно при обращении строки мы должны следить за тем, чтобы управляющие и специальные символы обрабатывались правильно ( см. Статью Джона Скита здесь), но в данном случае мы имеем дело с Guid string мы можем быть уверены, что строка будет полностью изменена.

Ниже приведен полный код, который я использовал для извлечения кода обновления для известного кода продукта из реестра.

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

Это противоположный метод для получения ProductCode из UpgradeCode. Может быть полезным для кого-то.

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

Класс InstallPackage имеет свойство LocalPackage. Вы можете использовать это для запроса к базе данных MSI, которая кэшируется в C:\Windows\Installer, и получить все, что вы, возможно, захотите узнать об этом.

Просто комментирую на случай, если это пригодится в будущем кому-нибудь!

В случае, если у вас есть только GUID или код, следующий сайт можно использовать для преобразования между ними:

https://daysoff.io/flipguid

Надеюсь, это избавит от головной боли в будущем!

Вот гораздо более простой способ получить GUID, отформатированный в формате реестра (который в основном представляет собой необработанное байтовое представление)

Во-первых, получить необработанные байты:

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

Затем просто измените порядок байтов результата BitConverter.ToString()

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

А вот ваш помощник, модифицированный таким образом, что он также работает в 32-разрядных приложениях.Net3.5. Они нуждаются в особой обработке, потому что.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;
    }
}
Другие вопросы по тегам