diff --git a/dotnet/src/Devolutions.Pinget.Core.Tests/CoreTests.cs b/dotnet/src/Devolutions.Pinget.Core.Tests/CoreTests.cs index 261c998..28ec4e6 100644 --- a/dotnet/src/Devolutions.Pinget.Core.Tests/CoreTests.cs +++ b/dotnet/src/Devolutions.Pinget.Core.Tests/CoreTests.cs @@ -2203,6 +2203,16 @@ public void RegistryEntryMatchesInstalledPackage_UsesLocalIdentityInsteadOfCorre Assert.False(InstallerDispatch.RegistryEntryMatchesInstalledPackage("ShareX.ShareX", "ShareX.ShareX", null, installed)); } + [Theory] + [InlineData(null, false)] + [InlineData(0, false)] + [InlineData(1, true)] + public void IsMsiArpEntry_RequiresWindowsInstallerDword(int? windowsInstaller, bool expected) + { + // A GUID-shaped Burn/EXE key must not be treated as an MSI ProductCode. + Assert.Equal(expected, InstallerDispatch.IsMsiArpEntry(windowsInstaller)); + } + [Fact] public void CorrelateInstalledPackage_TiebreaksOnUnnormalizedNamePrefix() { diff --git a/dotnet/src/Devolutions.Pinget.Core/InstallerDispatch.cs b/dotnet/src/Devolutions.Pinget.Core/InstallerDispatch.cs index a4b2a47..36de9b6 100644 --- a/dotnet/src/Devolutions.Pinget.Core/InstallerDispatch.cs +++ b/dotnet/src/Devolutions.Pinget.Core/InstallerDispatch.cs @@ -83,7 +83,8 @@ private static bool TryUninstallArp(ListMatch installed, UninstallRequest reques if (!RegistryEntryMatchesInstalledPackage(subkeyName, displayName, productCode, installed)) continue; - if (TryRunMsiUninstall(installed, subkeyName, productCode, request, out exitCode)) + if (IsMsiArpEntry(subkey.GetValue("WindowsInstaller")) && + TryRunMsiUninstall(installed, subkeyName, productCode, request, out exitCode)) return true; var quietUninstallCmd = subkey.GetValue("QuietUninstallString") as string; @@ -1013,7 +1014,15 @@ internal static string BuildMsixUninstallScript(string? packageFullName, IReadOn "$targets | Remove-AppxPackage"; } - private static bool TryRunMsiUninstall(ListMatch installed, string subkeyName, string? productCode, UninstallRequest request, out int exitCode) + internal static bool IsMsiArpEntry(object? windowsInstaller) + => windowsInstaller is int value && value == 1; + + private static bool TryRunMsiUninstall( + ListMatch installed, + string subkeyName, + string? productCode, + UninstallRequest request, + out int exitCode) { exitCode = 0; var uninstallCode = installed.ProductCodes diff --git a/rust/crates/pinget-core/src/lib.rs b/rust/crates/pinget-core/src/lib.rs index bd6ba2c..f9f56d0 100644 --- a/rust/crates/pinget-core/src/lib.rs +++ b/rust/crates/pinget-core/src/lib.rs @@ -9944,8 +9944,9 @@ fn try_uninstall_arp(installed: &ListMatch, request: &UninstallRequest) -> Resul continue; } - if let Some(exit_code) = - try_run_msi_uninstall(installed, &subkey_name, product_code.as_deref(), request)? + if is_msi_arp_entry(read_reg_dword(&subkey, "WindowsInstaller")) + && let Some(exit_code) = + try_run_msi_uninstall(installed, &subkey_name, product_code.as_deref(), request)? { return Ok(Some(exit_code)); } @@ -10195,6 +10196,11 @@ fn build_msix_uninstall_script(package_full_name: Option<&str>, package_family_n ) } +#[cfg(any(windows, test))] +fn is_msi_arp_entry(windows_installer: Option) -> bool { + windows_installer == Some(1) +} + #[cfg(windows)] fn try_run_msi_uninstall( installed: &ListMatch, @@ -15272,6 +15278,14 @@ Installers: )); } + #[test] + fn msi_arp_dispatch_requires_windows_installer_dword() { + // A GUID-shaped Burn/EXE key must not be treated as an MSI ProductCode. + assert!(!is_msi_arp_entry(None)); + assert!(!is_msi_arp_entry(Some(0))); + assert!(is_msi_arp_entry(Some(1))); + } + #[test] #[cfg(windows)] fn build_uninstall_command_only_appends_silent_flag_when_needed() {