Skip to content

Commit

Permalink
(GH-755) Track more MSI information
Browse files Browse the repository at this point in the history
Look up MSI in cryptic GUID location to gather more information when
those things may be empty in uninstaller keys. Add localpackage
location to know where the cached MSI is located.
  • Loading branch information
ferventcoder committed May 30, 2016
1 parent 50da177 commit 7242532
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class RegistryApplicationKey : IEquatable<RegistryApplicationKey>
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; }

/// <summary>
/// Is an application listed in ARP (Programs and Features)?
Expand Down
147 changes: 141 additions & 6 deletions src/chocolatey/infrastructure.app/services/RegistryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -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);
Expand All @@ -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(@"\{(?<ReverseFull1>\w*)\-(?<ReverseFull2>\w*)\-(?<ReverseFull3>\w*)\-(?<ReversePairs1>\w*)\-(?<ReversePairs2>\w*)\}", RegexOptions.Compiled);
private Regex _programFilesRegex = new Regex(@"(?<Drive>\w)[\:\?]\\(?<ProgFiles>[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[^\\]*|(?<Name>[^\\]+))\\", 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();
Expand Down Expand Up @@ -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
{
Expand Down

0 comments on commit 7242532

Please sign in to comment.