diff --git a/src/chocolatey/infrastructure.app/domain/RegistryApplicationKey.cs b/src/chocolatey/infrastructure.app/domain/RegistryApplicationKey.cs index 5c36883024..f194ebe0ab 100644 --- a/src/chocolatey/infrastructure.app/domain/RegistryApplicationKey.cs +++ b/src/chocolatey/infrastructure.app/domain/RegistryApplicationKey.cs @@ -63,6 +63,7 @@ public class RegistryApplicationKey : IEquatable public bool NoRepair { get; set; } public string ReleaseType { get; set; } //hotfix, securityupdate, update rollup, servicepack public string ParentKeyName { get; set; } + public string LocalPackage { get; set; } /// /// Is an application listed in ARP (Programs and Features)? diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index d69805a368..e7e841ba30 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -16,12 +16,13 @@ namespace chocolatey.infrastructure.app.services { using System; - using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.AccessControl; using System.Security.Principal; + using System.Text; + using System.Text.RegularExpressions; using Microsoft.Win32; using domain; using filesystem; @@ -37,8 +38,10 @@ public sealed class RegistryService : IRegistryService private readonly IXmlService _xmlService; private readonly IFileSystem _fileSystem; private readonly bool _logOutput = false; + //public RegistryService() {} private const string UNINSTALLER_KEY_NAME = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; + private const string UNINSTALLER_MSI_MACHINE_KEY_NAME = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData"; private const string USER_ENVIRONMENT_REGISTRY_KEY_NAME = "Environment"; private const string MACHINE_ENVIRONMENT_REGISTRY_KEY_NAME = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; @@ -213,22 +216,27 @@ public void evaluate_keys(RegistryKey key, Registry snapshot) appKey.InstallerType = InstallerType.Custom; } + if (appKey.InstallerType == InstallerType.Msi) + { + get_msi_information(appKey, key); + } + if (_logOutput) { - if (appKey.is_in_programs_and_features() && appKey.InstallerType == InstallerType.Unknown) - { + //if (appKey.is_in_programs_and_features() && appKey.InstallerType == InstallerType.Unknown) + //{ foreach (var name in key.GetValueNames()) { //var kind = key.GetValueKind(name); var value = key.GetValue(name); if (name.is_equal_to("QuietUninstallString") || name.is_equal_to("UninstallString")) { - Console.WriteLine("key - {0}|{1}={2}|Type detected={3}".format_with(key.Name, name, value.to_string(), appKey.InstallerType.to_string())); + Console.WriteLine("key - {0}|{1}={2}|Type detected={3}|install location={4}".format_with(key.Name, name, value.to_string(), appKey.InstallerType.to_string(),appKey.InstallLocation.to_string())); } //Console.WriteLine("key - {0}, name - {1}, kind - {2}, value - {3}".format_with(key.Name, name, kind, value.to_string())); } - } + //} } snapshot.RegistryKeys.Add(appKey); @@ -238,6 +246,133 @@ public void evaluate_keys(RegistryKey key, Registry snapshot) key.Dispose(); } + private int _componentLoopCount = 0; + + private void get_msi_information(RegistryApplicationKey appKey, RegistryKey key) + { + _componentLoopCount = 0; + + var userDataProductKeyId = get_msi_user_data_key(key.Name); + if (string.IsNullOrWhiteSpace(userDataProductKeyId)) return; + + var hklm = open_key(RegistryHive.LocalMachine, RegistryView.Default); + if (Environment.Is64BitOperatingSystem) + { + hklm = open_key(RegistryHive.LocalMachine, RegistryView.Registry64); + } + + FaultTolerance.try_catch_with_logging_exception( + () => + { + var msiRegistryKey = hklm.OpenSubKey(UNINSTALLER_MSI_MACHINE_KEY_NAME, RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey); + if (msiRegistryKey == null) return; + + foreach (var subKeyName in msiRegistryKey.GetSubKeyNames()) + { + var msiProductKey = FaultTolerance.try_catch_with_logging_exception( + () => msiRegistryKey.OpenSubKey("{0}\\Products\\{1}\\InstallProperties".format_with(subKeyName, userDataProductKeyId), RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey), + "Failed to open subkey named '{0}' for '{1}', likely due to permissions".format_with(subKeyName, msiRegistryKey.Name), + logWarningInsteadOfError: true); + if (msiProductKey == null) continue; + + appKey.InstallLocation = set_if_empty(appKey.InstallLocation, msiProductKey.GetValue("InstallLocation").to_string()); + // informational + appKey.Publisher = set_if_empty(appKey.Publisher, msiProductKey.GetValue("Publisher").to_string()); + appKey.InstallDate = set_if_empty(appKey.InstallDate, msiProductKey.GetValue("InstallDate").to_string()); + appKey.InstallSource = set_if_empty(appKey.InstallSource, msiProductKey.GetValue("InstallSource").to_string()); + appKey.Language = set_if_empty(appKey.Language, msiProductKey.GetValue("Language").to_string()); + appKey.LocalPackage = set_if_empty(appKey.LocalPackage, msiProductKey.GetValue("LocalPackage").to_string()); + + // Version + appKey.DisplayVersion = set_if_empty(appKey.DisplayVersion, msiProductKey.GetValue("DisplayVersion").to_string()); + appKey.Version = set_if_empty(appKey.Version, msiProductKey.GetValue("Version").to_string()); + appKey.VersionMajor = set_if_empty(appKey.VersionMajor, msiProductKey.GetValue("VersionMajor").to_string()); + appKey.VersionMinor = set_if_empty(appKey.VersionMinor, msiProductKey.GetValue("VersionMinor").to_string()); + + // search components for install location if still empty + // the performance of this is very bad - without this the query is sub-second + // with this it takes about 15 seconds with around 200 apps installed + //if (string.IsNullOrWhiteSpace(appKey.InstallLocation) && !appKey.Publisher.contains("Microsoft")) + //{ + // var msiComponentsKey = FaultTolerance.try_catch_with_logging_exception( + // () => msiRegistryKey.OpenSubKey("{0}\\Components".format_with(subKeyName), RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey), + // "Failed to open subkey named '{0}' for '{1}', likely due to permissions".format_with(subKeyName, msiRegistryKey.Name), + // logWarningInsteadOfError: true); + // if (msiComponentsKey == null) continue; + + // foreach (var msiComponentKeyName in msiComponentsKey.GetSubKeyNames()) + // { + // var msiComponentKey = FaultTolerance.try_catch_with_logging_exception( + // () => msiComponentsKey.OpenSubKey(msiComponentKeyName, RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey), + // "Failed to open subkey named '{0}' for '{1}', likely due to permissions".format_with(subKeyName, msiRegistryKey.Name), + // logWarningInsteadOfError: true); + + // if (msiComponentKey.GetValueNames().Contains(userDataProductKeyId, StringComparer.OrdinalIgnoreCase)) + // { + // _componentLoopCount++; + // appKey.InstallLocation = set_if_empty(appKey.InstallLocation, get_install_location_estimate(msiComponentKey.GetValue(userDataProductKeyId).to_string())); + // if (!string.IsNullOrWhiteSpace(appKey.InstallLocation)) break; + // if (_componentLoopCount >= 10) break; + // } + // } + //} + } + }, + "Failed to open subkeys for '{0}', likely due to permissions".format_with(hklm.Name), + logWarningInsteadOfError: true); + } + + private string set_if_empty(string current, string proposed) + { + if (string.IsNullOrWhiteSpace(current)) return proposed; + + return current; + } + + private Regex _guidRegex = new Regex(@"\{(?\w*)\-(?\w*)\-(?\w*)\-(?\w*)\-(?\w*)\}", RegexOptions.Compiled); + private Regex _programFilesRegex = new Regex(@"(?\w)[\:\?]\\(?[Pp]rogram\s[Ff]iles|[Pp]rogram\s[Ff]iles\s\(x86\))\\(?:[Mm]icrosoft[^\\]*|[Cc]ommon\s[Ff]iles|IIS|MSBuild|[Rr]eference\s[Aa]ssemblies|[Ww]indows[^\\]*|(?[^\\]+))\\", RegexOptions.Compiled); + private StringBuilder _userDataKey = new StringBuilder(); + + private string get_install_location_estimate(string componentPath) + { + var match = _programFilesRegex.Match(componentPath); + if (!match.Success) return string.Empty; + if (string.IsNullOrWhiteSpace(match.Groups["Name"].Value)) return string.Empty; + + return "{0}:\\{1}\\{2}".format_with(match.Groups["Drive"].Value, match.Groups["ProgFiles"].Value, match.Groups["Name"].Value); + } + + private string get_msi_user_data_key(string name) + { + _userDataKey.Clear(); + var match = _guidRegex.Match(name); + if (!match.Success) return string.Empty; + + for (int i = 0; i < 3; i++) + { + var fullGroup = match.Groups["ReverseFull{0}".format_with(i + 1)]; + if (fullGroup != null) + { + _userDataKey.Append(fullGroup.Value.ToCharArray().Reverse().ToArray()); + } + } + for (int i = 0; i < 2; i++) + { + var pairsGroup = match.Groups["ReversePairs{0}".format_with(i + 1)]; + if (pairsGroup != null) + { + var pairValue = pairsGroup.Value; + for (int j = 0; j < pairValue.Length - 1; j++) + { + _userDataKey.Append("{1}{0}".format_with(pairValue[j], pairValue[j + 1])); + j++; + } + } + } + + return _userDataKey.to_string(); + } + public Registry get_installer_key_differences(Registry before, Registry after) { //var difference = after.RegistryKeys.Where(r => !before.RegistryKeys.Contains(r)).ToList(); @@ -368,7 +503,7 @@ public static GenericRegistryValue get_value(RegistryHiveType hive, string subKe value = FaultTolerance.try_catch_with_logging_exception( () => { - if (key.GetValueNames().Contains(registryValue,StringComparer.InvariantCultureIgnoreCase)) + if (key.GetValueNames().Contains(registryValue, StringComparer.InvariantCultureIgnoreCase)) { return new GenericRegistryValue {