From 60e63e5f26ad84d4dd6b761254fe3cae87026096 Mon Sep 17 00:00:00 2001 From: Nir Bar Date: Wed, 25 Sep 2024 11:17:33 +0300 Subject: [PATCH] Delete files while respecting dry-run, verbosity --- .github/workflows/build.yml | 2 +- .gitignore | 1 + MsiZapEx/BundleInfo.cs | 48 +++---- MsiZapEx/FileSystemModifier.cs | 80 ++++++++++++ MsiZapEx/ProductInfo.cs | 28 ++--- MsiZapEx/Program.cs | 220 ++++++++++++++++----------------- MsiZapEx/UpgradeInfo.cs | 44 ++++--- 7 files changed, 252 insertions(+), 171 deletions(-) create mode 100644 MsiZapEx/FileSystemModifier.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc13506..b4660cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ on: version: description: 'Build & package version' required: true - default: 0.2.3 + default: 0.2.4 type: string jobs: Build: diff --git a/.gitignore b/.gitignore index 6c64cb1..1e70cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -260,3 +260,4 @@ paket-files/ __pycache__/ *.pyc /build/ +launchSettings.json diff --git a/MsiZapEx/BundleInfo.cs b/MsiZapEx/BundleInfo.cs index 6f10a3e..05f1434 100644 --- a/MsiZapEx/BundleInfo.cs +++ b/MsiZapEx/BundleInfo.cs @@ -108,37 +108,37 @@ private static List FindByUpgradeCode(Guid bundleUpgradeCode, Regist public void Prune() { - using (RegistryModifier modifier = new RegistryModifier()) + using (RegistryModifier registryModifier = new RegistryModifier()) { - if (!BundleProductCode.Equals(Guid.Empty)) + using (FileSystemModifier fileSystemModifier = new FileSystemModifier()) { - modifier.DeferDeleteKey(RegistryHive.LocalMachine, RegistryView, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{BundleProductCode.ToString("B")}"); - - foreach (string d in Dependents) - { - modifier.DeferDeleteKey(RegistryHive.ClassesRoot, RegistryView.Registry64, $@"Installer\Dependencies\{d}\Dependents\{BundleProductCode.ToString("B")}"); - } - } - if (!string.IsNullOrEmpty(BundleProviderKey)) - { - modifier.DeferDeleteKey(RegistryHive.ClassesRoot, RegistryView.Registry64, $@"Installer\Dependencies\{BundleProviderKey}"); + Prune(fileSystemModifier, registryModifier); } + } + } - // Remove bundle from PendingFileRenameOperations - if (!string.IsNullOrEmpty(BundleCachePath)) - { - //TODO Use FileSystemModifier - try - { - File.Delete(BundleCachePath); - } - catch (Exception ex) - { + internal void Prune(FileSystemModifier fileSystemModifier, RegistryModifier registryModifier) + { + if (!BundleProductCode.Equals(Guid.Empty)) + { + registryModifier.DeferDeleteKey(RegistryHive.LocalMachine, RegistryView, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{BundleProductCode.ToString("B")}"); - } - modifier.DeferRemoveFromPendingOperations(BundleCachePath); + foreach (string d in Dependents) + { + registryModifier.DeferDeleteKey(RegistryHive.ClassesRoot, RegistryView.Registry64, $@"Installer\Dependencies\{d}\Dependents\{BundleProductCode.ToString("B")}"); } } + if (!string.IsNullOrEmpty(BundleProviderKey)) + { + registryModifier.DeferDeleteKey(RegistryHive.ClassesRoot, RegistryView.Registry64, $@"Installer\Dependencies\{BundleProviderKey}"); + } + + // Remove bundle from PendingFileRenameOperations + if (!string.IsNullOrEmpty(BundleCachePath)) + { + registryModifier.DeferRemoveFromPendingOperations(BundleCachePath); + fileSystemModifier.DeferDeleteFolder(Path.GetDirectoryName(BundleCachePath)); + } } internal void PrintState() diff --git a/MsiZapEx/FileSystemModifier.cs b/MsiZapEx/FileSystemModifier.cs new file mode 100644 index 0000000..6255d94 --- /dev/null +++ b/MsiZapEx/FileSystemModifier.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace MsiZapEx +{ + class FileSystemModifier : IDisposable + { + private List _deleteFolders = new List(); + private List _deleteFiles = new List(); + + public void Dispose() + { + foreach (string folder in _deleteFolders) + { + DeleteFolder(folder); + } + _deleteFolders.Clear(); + foreach (string file in _deleteFiles) + { + DeleteFile(file); + } + _deleteFiles.Clear(); + } + + private void DeleteFile(string path) + { + if (!File.Exists(path)) + { + return; + } + + if ((Settings.Instance?.DryRun == true) || (Settings.Instance?.Verbose == true)) + { + Console.WriteLine($"Delete file '{path}'"); + if (Settings.Instance?.DryRun == true) + { + return; + } + } + + File.SetAttributes(path, FileAttributes.Normal); + File.Delete(path); + } + + private void DeleteFolder(string path) + { + if (!Directory.Exists(path)) + { + return; + } + + if ((Settings.Instance?.DryRun == true) || (Settings.Instance?.Verbose == true)) + { + Console.WriteLine($"Delete folder '{path}'"); + if (Settings.Instance?.DryRun == true) + { + return; + } + } + + IEnumerable files = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories); + foreach (string file in files) + { + DeleteFile(file); + } + Directory.Delete(path, true); + } + + public void DeferDeleteFile(string path) + { + _deleteFiles.Add(path); + } + + public void DeferDeleteFolder(string path) + { + _deleteFolders.Add(path); + } + } +} diff --git a/MsiZapEx/ProductInfo.cs b/MsiZapEx/ProductInfo.cs index 853b254..0105598 100644 --- a/MsiZapEx/ProductInfo.cs +++ b/MsiZapEx/ProductInfo.cs @@ -341,45 +341,37 @@ private void Read(string obfuscatedGuid, bool? machineScope) } } - internal void Prune(RegistryModifier modifier) + internal void Prune(FileSystemModifier fileSystemModifier, RegistryModifier registryModifier) { foreach (ComponentInfo c in Components) { - c.Prune(ProductCode, modifier); + c.Prune(ProductCode, registryModifier); } foreach (PatchInfo p in Patches) { - p.Prune(ProductCode, modifier); + p.Prune(ProductCode, registryModifier); } string obfuscatedProductCode = GuidEx.MsiObfuscate(ProductCode); - modifier.DeferDeleteKey(RegistryHive.LocalMachine, RegistryView.Registry64, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\{UserSID}\Products\{obfuscatedProductCode}"); - modifier.DeferDeleteKey(RegistryHive.LocalMachine, View, $@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProductCode.ToString("B")}"); + registryModifier.DeferDeleteKey(RegistryHive.LocalMachine, RegistryView.Registry64, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\{UserSID}\Products\{obfuscatedProductCode}"); + registryModifier.DeferDeleteKey(RegistryHive.LocalMachine, View, $@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProductCode.ToString("B")}"); string keyBase = MachineScope ? @"SOFTWARE\Classes" : @"Software\Microsoft"; RegistryHive hiveBase = MachineScope ? RegistryHive.LocalMachine : RegistryHive.CurrentUser; - modifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\Products\{obfuscatedProductCode}"); - modifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\Features\{obfuscatedProductCode}"); + registryModifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\Products\{obfuscatedProductCode}"); + registryModifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\Features\{obfuscatedProductCode}"); // Dependencies - modifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"SOFTWARE\Classes\Installer\Dependencies\{ProductCode.ToString("B")}"); + registryModifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"SOFTWARE\Classes\Installer\Dependencies\{ProductCode.ToString("B")}"); foreach (string d in Dependants) { - modifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"SOFTWARE\Classes\Installer\Dependencies\{d}\Dependents\{ProductCode.ToString("B")}"); + registryModifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"SOFTWARE\Classes\Installer\Dependencies\{d}\Dependents\{ProductCode.ToString("B")}"); } - //TODO Use FileSystemModifier if (!string.IsNullOrEmpty(LocalPackage)) { - try - { - File.Delete(LocalPackage); - } - catch (Exception ex) - { - - } + fileSystemModifier.DeferDeleteFile(LocalPackage); } } } diff --git a/MsiZapEx/Program.cs b/MsiZapEx/Program.cs index d8edad2..da93a1b 100644 --- a/MsiZapEx/Program.cs +++ b/MsiZapEx/Program.cs @@ -39,142 +39,142 @@ static void Main(string[] args) try { - Settings.Instance = cmdLine.Value; - if (!string.IsNullOrEmpty(Settings.Instance.BundleUpgradeCode)) + using (RegistryModifier registryModifier = new RegistryModifier()) { - List bundles = BundleInfo.FindByUpgradeCode(new Guid(Settings.Instance.BundleUpgradeCode)); - if (bundles.Count == 0) + using (FileSystemModifier fileSystemModifier = new FileSystemModifier()) { - Console.WriteLine($"No BundleUpgradeCode '{Settings.Instance.BundleUpgradeCode}' was found"); - } - foreach (BundleInfo bi in bundles) - { - bi.PrintState(); - } - if ((Settings.Instance.ForceClean && (bundles.Count == 1)) || Settings.Instance.ForceCleanAllRelated) - { - foreach (BundleInfo bi in bundles) + Settings.Instance = cmdLine.Value; + if (!string.IsNullOrEmpty(Settings.Instance.BundleUpgradeCode)) { - bi.Prune(); + List bundles = BundleInfo.FindByUpgradeCode(new Guid(Settings.Instance.BundleUpgradeCode)); + if (bundles.Count == 0) + { + Console.WriteLine($"No BundleUpgradeCode '{Settings.Instance.BundleUpgradeCode}' was found"); + } + foreach (BundleInfo bi in bundles) + { + bi.PrintState(); + } + if ((Settings.Instance.ForceClean && (bundles.Count == 1)) || Settings.Instance.ForceCleanAllRelated) + { + foreach (BundleInfo bi in bundles) + { + bi.Prune(fileSystemModifier, registryModifier); + } + } } - } - } - if (!string.IsNullOrEmpty(Settings.Instance.BundleProductCode)) - { - if (!Guid.TryParse(Settings.Instance.BundleProductCode, out Guid productCode)) - { - Console.WriteLine($"BundleProductCode '{Settings.Instance.BundleUpgradeCode}' is not a UUID"); - Environment.Exit(1); - } - - BundleInfo bundle = new BundleInfo(productCode); - bundle.PrintState(); - if (Settings.Instance.ForceClean) - { - bundle.Prune(); - } - } - if (!string.IsNullOrEmpty(Settings.Instance.UpgradeCode)) - { - Guid upgradeCode = Settings.Instance.Obfuscated ? GuidEx.MsiObfuscate(Settings.Instance.UpgradeCode) : new Guid(Settings.Instance.UpgradeCode); - - UpgradeInfo upgrade = new UpgradeInfo(upgradeCode, Settings.Instance.Shallow); - if (upgrade != null) - { - upgrade.PrintState(); - if ((Settings.Instance.ForceClean && (upgrade.RelatedProducts.Count == 1)) || Settings.Instance.ForceCleanAllRelated) + if (!string.IsNullOrEmpty(Settings.Instance.BundleProductCode)) { - foreach (ProductInfo pi in upgrade.RelatedProducts) + if (!Guid.TryParse(Settings.Instance.BundleProductCode, out Guid productCode)) { - upgrade.Prune(pi); + Console.WriteLine($"BundleProductCode '{Settings.Instance.BundleUpgradeCode}' is not a UUID"); + Environment.Exit(1); } - } - } - else - { - Console.WriteLine($"No UpgradeCode '{upgradeCode}' was found"); - } - } - if (!string.IsNullOrEmpty(Settings.Instance.ProductCode)) - { - Guid productCode = Settings.Instance.Obfuscated ? GuidEx.MsiObfuscate(Settings.Instance.ProductCode) : new Guid(Settings.Instance.ProductCode); - UpgradeInfo upgrade = UpgradeInfo.FindByProductCode(productCode, Settings.Instance.Shallow); - if (upgrade != null) - { - ProductInfo product = upgrade.RelatedProducts.First(p => p.ProductCode.Equals(productCode)); - product.PrintState(); - if (Settings.Instance.ForceClean && (product != null)) - { - upgrade.Prune(product); + BundleInfo bundle = new BundleInfo(productCode); + bundle.PrintState(); + if (Settings.Instance.ForceClean) + { + bundle.Prune(fileSystemModifier, registryModifier); + } } - } - else - { - Console.WriteLine($"Product '{productCode}' is not related to any UpgradeCode"); - ProductInfo product = new ProductInfo(productCode); - if (product != null) + if (!string.IsNullOrEmpty(Settings.Instance.UpgradeCode)) { - product.PrintState(); - if (Settings.Instance.ForceClean) + Guid upgradeCode = Settings.Instance.Obfuscated ? GuidEx.MsiObfuscate(Settings.Instance.UpgradeCode) : new Guid(Settings.Instance.UpgradeCode); + + UpgradeInfo upgrade = new UpgradeInfo(upgradeCode, Settings.Instance.Shallow); + if (upgrade != null) { - using (RegistryModifier modifier = new RegistryModifier()) + upgrade.PrintState(); + if ((Settings.Instance.ForceClean && (upgrade.RelatedProducts.Count == 1)) || Settings.Instance.ForceCleanAllRelated) { - product.Prune(modifier); + foreach (ProductInfo pi in upgrade.RelatedProducts) + { + upgrade.Prune(pi, fileSystemModifier, registryModifier); + } } } + else + { + Console.WriteLine($"No UpgradeCode '{upgradeCode}' was found"); + } } - else + if (!string.IsNullOrEmpty(Settings.Instance.ProductCode)) { - Console.WriteLine($"No ProductCode '{productCode}' was found"); - } - } - } - if (!string.IsNullOrEmpty(Settings.Instance.ComponentCode)) - { - Guid componentCode = Settings.Instance.Obfuscated ? GuidEx.MsiObfuscate(Settings.Instance.ComponentCode) : new Guid(Settings.Instance.ComponentCode); + Guid productCode = Settings.Instance.Obfuscated ? GuidEx.MsiObfuscate(Settings.Instance.ProductCode) : new Guid(Settings.Instance.ProductCode); - 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) + UpgradeInfo upgrade = UpgradeInfo.FindByProductCode(productCode, Settings.Instance.Shallow); + if (upgrade != null) + { + ProductInfo product = upgrade.RelatedProducts.First(p => p.ProductCode.Equals(productCode)); + product.PrintState(); + if (Settings.Instance.ForceClean && (product != null)) + { + upgrade.Prune(product, fileSystemModifier, registryModifier); + } + } + else + { + Console.WriteLine($"Product '{productCode}' is not related to any UpgradeCode"); + ProductInfo product = new ProductInfo(productCode); + if (product != null) + { + product.PrintState(); + if (Settings.Instance.ForceClean) + { + product.Prune(fileSystemModifier, registryModifier); + } + } + else + { + Console.WriteLine($"No ProductCode '{productCode}' was found"); + } + } + } + if (!string.IsNullOrEmpty(Settings.Instance.ComponentCode)) { + Guid componentCode = Settings.Instance.Obfuscated ? GuidEx.MsiObfuscate(Settings.Instance.ComponentCode) : new Guid(Settings.Instance.ComponentCode); + + ComponentInfo component = new ComponentInfo(componentCode); component.PrintProducts(); } - } - else - { - Console.WriteLine($"No components found with key path '{Settings.Instance.KeyPath}'"); - } - } - if (Settings.Instance.DetectOrphanProducts) - { - List orphan = ProductInfo.GetOrphanProducts(); - Console.WriteLine($"{orphan.Count} orphan product(s) detected"); - using (RegistryModifier modifier = new RegistryModifier()) - { - foreach (ProductInfo pi in orphan) + 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) { - pi.PrintState(); - if (Settings.Instance.ForceCleanAllRelated) + List orphan = ProductInfo.GetOrphanProducts(); + Console.WriteLine($"{orphan.Count} orphan product(s) detected"); + foreach (ProductInfo pi in orphan) { - pi.Prune(modifier); + pi.PrintState(); + if (Settings.Instance.ForceCleanAllRelated) + { + pi.Prune(fileSystemModifier, registryModifier); + } } } + if (!string.IsNullOrEmpty(Settings.Instance.ObfuscateGuid)) + { + Guid obfuscated = GuidEx.MsiObfuscate(Settings.Instance.ObfuscateGuid); + Console.WriteLine($"Supplied UUID: {Settings.Instance.ObfuscateGuid}"); + Console.WriteLine($"Obfuscated UUID (N format): {obfuscated.ToString("N")}"); + Console.WriteLine($"Obfuscated UUID (B format): {obfuscated.ToString("B")}"); + } } } - if (!string.IsNullOrEmpty(Settings.Instance.ObfuscateGuid)) - { - Guid obfuscated = GuidEx.MsiObfuscate(Settings.Instance.ObfuscateGuid); - Console.WriteLine($"Supplied UUID: {Settings.Instance.ObfuscateGuid}"); - Console.WriteLine($"Obfuscated UUID (N format): {obfuscated.ToString("N")}"); - Console.WriteLine($"Obfuscated UUID (B format): {obfuscated.ToString("B")}"); - } } catch (Exception ex) { diff --git a/MsiZapEx/UpgradeInfo.cs b/MsiZapEx/UpgradeInfo.cs index 0ea581b..4140bb6 100644 --- a/MsiZapEx/UpgradeInfo.cs +++ b/MsiZapEx/UpgradeInfo.cs @@ -223,29 +223,37 @@ public void Prune(ProductInfo product) throw new FileNotFoundException(); } - using (RegistryModifier modifier = new RegistryModifier()) + using (RegistryModifier registryModifier = new RegistryModifier()) { - product.Prune(modifier); + using (FileSystemModifier fileSystemModifier = new FileSystemModifier()) + { + Prune(product, fileSystemModifier, registryModifier); + } + } + } - string obfuscatedUpgradeCode = GuidEx.MsiObfuscate(UpgradeCode); - string obfuscatedProductCode = GuidEx.MsiObfuscate(product.ProductCode); + internal void Prune(ProductInfo product, FileSystemModifier fileSystemModifier, RegistryModifier registryModifier) + { + product.Prune(fileSystemModifier, registryModifier); - if (RelatedProducts.Count > 1) - { - modifier.DeferDeleteValue(RegistryHive.LocalMachine, RegistryView.Registry64, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\{obfuscatedUpgradeCode}", obfuscatedProductCode); + string obfuscatedUpgradeCode = GuidEx.MsiObfuscate(UpgradeCode); + string obfuscatedProductCode = GuidEx.MsiObfuscate(product.ProductCode); - string keyBase = MachineScope ? @"SOFTWARE\Classes" : @"Software\Microsoft"; - RegistryHive hiveBase = MachineScope ? RegistryHive.LocalMachine : RegistryHive.CurrentUser; - modifier.DeferDeleteValue(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\UpgradeCodes\{obfuscatedUpgradeCode}", obfuscatedProductCode); - } - else - { - modifier.DeferDeleteKey(RegistryHive.LocalMachine, RegistryView.Registry64, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\{obfuscatedUpgradeCode}"); + if (RelatedProducts.Count > 1) + { + registryModifier.DeferDeleteValue(RegistryHive.LocalMachine, RegistryView.Registry64, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\{obfuscatedUpgradeCode}", obfuscatedProductCode); - string keyBase = MachineScope ? @"SOFTWARE\Classes" : @"Software\Microsoft"; - RegistryHive hiveBase = MachineScope ? RegistryHive.LocalMachine : RegistryHive.CurrentUser; - modifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\UpgradeCodes\{obfuscatedUpgradeCode}"); - } + string keyBase = MachineScope ? @"SOFTWARE\Classes" : @"Software\Microsoft"; + RegistryHive hiveBase = MachineScope ? RegistryHive.LocalMachine : RegistryHive.CurrentUser; + registryModifier.DeferDeleteValue(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\UpgradeCodes\{obfuscatedUpgradeCode}", obfuscatedProductCode); + } + else + { + registryModifier.DeferDeleteKey(RegistryHive.LocalMachine, RegistryView.Registry64, $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\{obfuscatedUpgradeCode}"); + + string keyBase = MachineScope ? @"SOFTWARE\Classes" : @"Software\Microsoft"; + RegistryHive hiveBase = MachineScope ? RegistryHive.LocalMachine : RegistryHive.CurrentUser; + registryModifier.DeferDeleteKey(hiveBase, RegistryView.Registry64, $@"{keyBase}\Installer\UpgradeCodes\{obfuscatedUpgradeCode}"); } } }