diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdf41d6..2eeb394 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: version: description: 'Build & package version' required: true - default: 0.1.5 + default: 0.2.0 type: string jobs: Build: diff --git a/MsiZapEx/ComponentInfo.cs b/MsiZapEx/ComponentInfo.cs index abcc039..850a52e 100644 --- a/MsiZapEx/ComponentInfo.cs +++ b/MsiZapEx/ComponentInfo.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; +using System.Xml.Linq; namespace MsiZapEx { @@ -114,7 +115,7 @@ internal static List GetAllComponents() return _components; } - internal static List GetComponents(Guid productCode, bool machineContext) + internal static List GetComponents(Guid productCode) { GetAllComponents(); List components = new List(); @@ -122,6 +123,24 @@ internal static List GetComponents(Guid productCode, bool machine return components; } + internal static List GetByKeyPath(string keyPath) + { + GetAllComponents(); + keyPath = keyPath.Replace("?", ""); + List components = new List(); + try + { + if (Path.IsPathRooted(keyPath)) + { + keyPath = Path.GetFullPath(keyPath); + } + } + catch { } + + components.AddRange(_components.FindAll(ci => ci.ProductsKeyPath.Any(p => p.KeyPath.Replace("?", "").Equals(keyPath, StringComparison.InvariantCultureIgnoreCase)))); + return components; + } + internal static bool ResolveScope(Guid componentCode) { string obfuscatedGuid = GuidEx.MsiObfuscate(componentCode); @@ -244,6 +263,7 @@ private static bool ValidateKeyPath(string keyPath) { return true; } + keyPath = keyPath.Replace("?", ""); Match regMatch = registryKeyPath_.Match(keyPath); if (regMatch.Success) @@ -252,7 +272,7 @@ private static bool ValidateKeyPath(string keyPath) if (int.TryParse(root, out int kr)) { RegistryHive hive; - RegistryView view = (kr > 20) ? RegistryView.Registry64 : RegistryView.Registry32; + RegistryView view = (kr >= 20) ? RegistryView.Registry64 : RegistryView.Registry32; switch (kr % 10) { case 0: @@ -272,6 +292,17 @@ private static bool ValidateKeyPath(string keyPath) } string path = regMatch.Groups["path"].Value; + if (path.EndsWith($"{Path.DirectorySeparatorChar}") || path.EndsWith($"{Path.AltDirectorySeparatorChar}")) + { + using (RegistryKey rk = RegistryKey.OpenBaseKey(hive, view)) + { + using (RegistryKey hk = rk.OpenSubKey(path, false)) + { + return (hk != null); + } + } + } + string key = Path.GetDirectoryName(path); string name = Path.GetFileName(path); using (RegistryKey rk = RegistryKey.OpenBaseKey(hive, view)) diff --git a/MsiZapEx/ProductInfo.cs b/MsiZapEx/ProductInfo.cs index 5252b5a..cb0a97e 100644 --- a/MsiZapEx/ProductInfo.cs +++ b/MsiZapEx/ProductInfo.cs @@ -319,7 +319,7 @@ private void Read(string obfuscatedGuid, bool? machineScope) } else { - Components = ComponentInfo.GetComponents(ProductCode, MachineScope); + Components = ComponentInfo.GetComponents(ProductCode); if (Components.Count > 0) { Status |= StatusFlags.Components; diff --git a/MsiZapEx/Program.cs b/MsiZapEx/Program.cs index 61756ce..d8edad2 100644 --- a/MsiZapEx/Program.cs +++ b/MsiZapEx/Program.cs @@ -137,6 +137,21 @@ static void Main(string[] args) ComponentInfo component = new ComponentInfo(componentCode); component.PrintProducts(); } + if (!string.IsNullOrEmpty(Settings.Instance.KeyPath)) + { + List components = ComponentInfo.GetByKeyPath(Settings.Instance.KeyPath); + if (components != null && components.Count > 0) + { + foreach (ComponentInfo component in components) + { + component.PrintProducts(); + } + } + else + { + Console.WriteLine($"No components found with key path '{Settings.Instance.KeyPath}'"); + } + } if (Settings.Instance.DetectOrphanProducts) { List orphan = ProductInfo.GetOrphanProducts(); diff --git a/MsiZapEx/Settings.cs b/MsiZapEx/Settings.cs index 19fa8d3..99d8857 100644 --- a/MsiZapEx/Settings.cs +++ b/MsiZapEx/Settings.cs @@ -26,6 +26,9 @@ public class Settings [Option("component-code", Required = false, HelpText = "Detect products by ComponentCode", Group = "codes")] public string ComponentCode { get; set; } + [Option("key-path", Required = false, HelpText = "Detect components by key path", Group = "codes")] + public string KeyPath { get; set; } + [Option("detect-orphan-products", Required = false, HelpText = "List all products that can't be normally uninstalled due to missing registration data", Group = "codes")] public bool DetectOrphanProducts { get; set; }