2013-07-29 14 views
10

Używam wrapper C# dla interfejsu API Instalatora Windows z WIX Toolset. Używam klasy ProductInstallation, aby uzyskać informacje na temat zainstalowanych produktów, takich jak kod produktu i nazwa produktu.Jak znaleźć kod aktualizacji dla zainstalowanej aplikacji w języku C#?

Na przykład

  • Nazwa produktu - "Mój testowy Aplikacja"
  • Kod produktu - {F46BA620-C027-4E68-9069-5D5D4E1FF30A}
  • wersja produktu - 1.4.0

Ta wewnętrzna strona korzysta z funkcji MsiGetProductInfo. Niestety ta funkcja nie zwraca kodu aktualizacji produktu.

Jak mogę pobrać kod aktualizacji dla zainstalowanej aplikacji przy użyciu C#?

+0

Zamiast korzystania z rejestru zgodnie z sugestiami poniżej ** proponuję użyć WMI, jak opisano w tej odpowiedzi **: [** Jak mogę znaleźć kod Upgrade dla zainstalowanego pliku MSI? **] (https: // stackoverflow.com/questions/46637094/how-can-i-find-the-upgrade-code-for-an-installed-msi-file/46637095 # 46637095). Zapewni to pobranie poprawnego kodu aktualizacji i nie będzie wymagać żadnej konwersji ani interpretacji. ** Otrzymasz prawdziwy kod aktualizacji z powrotem w odpowiednim formacie **. –

Odpowiedz

22

Odkryłem, że kody aktualizacji są przechowywane w następującej lokalizacji rejestru.

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

Nazwa klucza rejestru to kod aktualizacji, a nazwa klucza rejestru to kod produktu. Mogę łatwo wyodrębnić te wartości, jednak kody są przechowywane w innym formacie. Czerwone kółko pokazuje sformatowany kod aktualizacji, niebieskie kółko pokazuje sformatowany kod produktu podczas oglądania go w regedit.exe.

Red circle is the formatted upgrade code, the blue circle the formatted product code

myślników są usuwane z Guid a następnie seria odwrócenia ciągu są zrobione. Pierwsze 8 znaków jest odwróconych, następne 4, następne 4, a reszta łańcucha jest odwrócona w zestawach po 2 znaki. Zwykle podczas cofania łańcucha musimy zachować ostrożność, upewniając się, że znaki kontrolne i specjalne są obsługiwane poprawnie (see Jon Skeet's aricle here), ale tak jak my, w tym przypadku, traktując ciąg znaków Guid możemy być pewni, że ciąg znaków zostanie poprawnie odwrócony.

Poniżej znajduje się pełny kod użyty do wyodrębnienia kodu aktualizacji znanego kodu produktu z rejestru.

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

Alex powyżej programu zwraca NULL dla odpowiedniego kodu produktu. Czy każdy kod aktualizacji wprowadza do niego kod produktu? Usunąłem backets z mojego kodu produktu, ale nie myślniki (-). Nadal nie działa. – Keshav

+0

Po debugowaniu dowiedziałem się, że rejestr jest obecny w danej lokalizacji, ale upgradeCodeRegistryRoot uzyskuje wartość NULL – Keshav

+0

"Nazwa klucza rejestru to kod aktualizacji" jest źle! Klucze rejestru nie są równe kodom aktualizacji! –

4

Klasa InstallPackage ma właściwość o nazwie LocalPackage. Możesz użyć tego do wysłania zapytania do bazy danych MSI, która jest zapisana w pamięci podręcznej w C: \ Windows \ Installer i uzyskać wszystko, co możesz chcieć o tym wiedzieć.

+0

Tak, to jest poprawne, ale nie jest tak niezawodne. Jest to pomocne tylko wtedy, gdy pakiet MSI nadal istnieje. Podczas szybkiego testu na kilku komputerach odkryłem, że jest dość powszechny, że MSI nie istnieje (27 brakowało w 593 przetestowanych pakietach lub ~ 5%). W tych testach wszystkie kody aktualizacji były nadal dostępne w rejestrze. –

+1

Te MSI powinny być zawsze tam, w przeciwnym razie będziesz mieć problemy z naprawą, odinstalowaniem, reklamowanymi skrótami i tak dalej. http://blogs.msdn.com/b/sqlserverfaq/archive/2013/04/30/do-not-delete-files-from-the-windows-installer-folder.aspx –

+0

Zgadzam się; oni tam będą. Niestety czasami tak nie jest. Powinienem wyjaśnić, że chodzi o usługę stylu inwentaryzacji, która zostanie zainstalowana na komputerach klienckich, więc potrzebowałem bardziej niezawodnej metody. –

1

Jest to metoda odwrotna do uzyskania kodu produktu z kodu UpgradeCode. Może być przydatne dla kogoś.

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

@Alex Kod Wiese działa dobrze dla mnie. Ten kod nie jest. Wykonuje jednak odwrotne wyszukiwanie, znajdując Kod Produktu dla danej UpgradeCode. – harlam357

+0

@ harlam357 dziękuję za komentarz. Masz rację. Zaktualizowałem odpowiedź, aby uzupełnić przyjętą odpowiedź. –

0

A oto Twój pomocnik zmodyfikowany w taki sposób, że działa również w aplikacjach 32. bitowych w .Net3.5. Potrzebują specjalnego traktowania, ponieważ .NET 3.5 nie ma świadomości, że rejestr jest dzielony między wpisy 32- i 64-bitowe. Moje rozwiązanie wykorzystuje tylko To64BitPath do przeglądania 64-bitowej części. Jest to również świetny poradnik, który wykorzystuje DllImports za to: 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; 
    } 
}