Как найти код обновления для установленного приложения на С#?
Я использую оболочку С# для API установщика Windows из набора инструментов WIX. Я использую класс ProductInstallation
для получения информации о установленных продуктах, таких как код продукта и название продукта.
Например
- Название продукта - "Моя тестовая заявка"
- Код продукта - {F46BA620-C027-4E68-9069-5D5D4E1FF30A}
- Версия продукта - 1.4.0
Внутри этой оболочки используется функция MsiGetProductInfo. К сожалению, эта функция не возвращает код обновления продукта.
Как получить код обновления для установленного приложения с помощью С#?
Ответы
Ответ 1
Я обнаружил, что коды обновления хранятся в следующем разделе реестра.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes
Имя ключа реестра - это код обновления, а имя ключа реестра - код продукта. Я могу легко извлечь эти значения, однако коды хранятся в другом формате. Красный круг показывает форматированный код обновления, синий круг показывает форматированный код продукта при просмотре в regedit.exe
.
![Красный круг - это форматированный код обновления, синий круг - форматированный код продукта]()
Дефисы удаляются из Guid
, а затем выполняется серия разворота строк. Первые 8 символов меняются на противоположные, затем следующие 4, затем следующие 4, а затем остальные строки перевернуты в наборах из двух символов. Обычно, когда вы меняете строчку, нам нужно следить за тем, чтобы управление и специальные символы обрабатывались правильно (см. Здесь Jon Skeet aricle), но поскольку мы в этом случае, имея дело с строкой 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();
}
}
Ответ 2
Класс InstallPackage имеет свойство LocalPackage. Вы можете использовать это для запроса базы данных MSI, которая кэшируется в C:\Windows\Installer, и получить все, что вы, возможно, захотите узнать об этом.
Ответ 3
Это противоположный метод получения 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);
}
}
Ответ 4
И вот ваш помощник модифицирован таким образом, что он также работает в 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;
}
}