From fafdaa522ff2a3888dfceb0ab56911c4f8cdf48d Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 21 Jan 2026 21:20:40 -0500 Subject: Allow patched package to be uninstalled. Normally, a patched package is detected as superseded and therefore a normal uninstall takes no action. This change looks for applied patches and allows a normal uninstall to remove the package. Fixes https://github.com/wixtoolset/issues/issues/6350 --- src/burn/engine/msiengine.cpp | 52 ++++++++++++++++++++-- .../BundleAv1_0_1/BundleAv1_0_1.wixproj | 19 ++++++++ .../BundleAv1_0_1/BundleAv1_0_1.wxs | 12 +++++ .../PatchAMinor/PatchAMinor.wixproj | 12 +++++ .../SlipstreamTests/PatchAMinor/PatchAMinor.wxs | 23 ++++++++++ .../burn/WixToolsetTest.BurnE2E/SlipstreamTests.cs | 24 +++++++++- src/test/sandbox/TestSandbox.wsb | 5 +++ src/test/sandbox/startup.bat | 8 ++++ 8 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wixproj create mode 100644 src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wxs create mode 100644 src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wixproj create mode 100644 src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wxs diff --git a/src/burn/engine/msiengine.cpp b/src/burn/engine/msiengine.cpp index 86fb0d9d..a2a29bda 100644 --- a/src/burn/engine/msiengine.cpp +++ b/src/burn/engine/msiengine.cpp @@ -50,7 +50,9 @@ static void RegisterSourceDirectory( __in BURN_PACKAGE* pPackage, __in_z LPCWSTR wzCacheDirectory ); - +static BOOL PackageHasAppliedPatch( + __in BURN_PACKAGE* pPackage + ); // function definitions @@ -904,7 +906,9 @@ extern "C" HRESULT MsiEnginePlanCalculatePackage( else if ((BOOTSTRAPPER_REQUEST_STATE_ABSENT == pPackage->requested || BOOTSTRAPPER_REQUEST_STATE_CACHE == pPackage->requested) && !pPackage->fPermanent) // removing a package that should be removed. { - execute = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED == pPackage->currentState ? BOOTSTRAPPER_ACTION_STATE_NONE : BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + BOOL fPackageHasAppliedPatch = PackageHasAppliedPatch(pPackage); + + execute = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED == pPackage->currentState && !fPackageHasAppliedPatch ? BOOTSTRAPPER_ACTION_STATE_NONE : BOOTSTRAPPER_ACTION_STATE_UNINSTALL; } else if (BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT == pPackage->requested) { @@ -2265,6 +2269,7 @@ LExit: ReleaseStr(sczMspPath); ReleaseStr(sczCachedDirectory); ReleaseStr(sczPatches); + return hr; } @@ -2289,6 +2294,47 @@ static void RegisterSourceDirectory( LExit: ReleaseStr(sczMsiDirectory); +} + +static BOOL PackageHasAppliedPatch( + __in BURN_PACKAGE* pPackage +) +{ + HRESULT hr = S_OK; + BOOL fPatched = FALSE; + UINT er = ERROR_SUCCESS; + DWORD iPatch = 0; + WCHAR wzPatchCode[MAX_GUID_CHARS + 1] = {}; + WCHAR wzTransforms[MAX_PATH] = {}; + DWORD cchTransforms = countof(wzTransforms); + WCHAR wzPatchState[2] = {}; + DWORD cchPatchState = countof(wzPatchState); + + for (;;) + { + er = ::MsiEnumPatchesW(pPackage->Msi.sczProductCode, iPatch, wzPatchCode, wzTransforms, &cchTransforms); + + if (ERROR_NO_MORE_ITEMS == er) + { + ExitFunction(); + } + ExitOnWin32Error(er, hr, "Failed to enumerate patches for package %ls, product code %ls.", pPackage->sczId, pPackage->Msi.sczProductCode); + + er = ::MsiGetPatchInfoExW(wzPatchCode, pPackage->Msi.sczProductCode, NULL, pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED + , INSTALLPROPERTY_PATCHSTATE, wzPatchState, &cchPatchState); + ExitOnWin32Error(er, hr, "Failed to get patch info for patch %ls.", wzPatchCode); + + if ('1' == wzPatchState[0]) + { + fPatched = TRUE; + + ExitFunction(); + } + + ++iPatch; + } + +LExit: + return fPatched; - return; } diff --git a/src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wixproj b/src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wixproj new file mode 100644 index 00000000..ac9cf71e --- /dev/null +++ b/src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wixproj @@ -0,0 +1,19 @@ + + + + Bundle + {62C28DAF-A13E-4F55-ACA1-FB843630789C} + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wxs b/src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wxs new file mode 100644 index 00000000..a14383d3 --- /dev/null +++ b/src/test/burn/TestData/SlipstreamTests/BundleAv1_0_1/BundleAv1_0_1.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wixproj b/src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wixproj new file mode 100644 index 00000000..3deb2263 --- /dev/null +++ b/src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wixproj @@ -0,0 +1,12 @@ + + + + Patch + .msp + 1079 + + + + + + diff --git a/src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wxs b/src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wxs new file mode 100644 index 00000000..0f4abd2b --- /dev/null +++ b/src/test/burn/TestData/SlipstreamTests/PatchAMinor/PatchAMinor.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/WixToolsetTest.BurnE2E/SlipstreamTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/SlipstreamTests.cs index d07e80f1..d4cf447d 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/SlipstreamTests.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/SlipstreamTests.cs @@ -18,7 +18,7 @@ namespace WixToolsetTest.BurnE2E private const string V101 = "1.0.1.0"; [RuntimeFact] - public void CanInstallBundleWithSlipstreamedPatchThenRemoveIt() + public void CanInstallBundleWithSlipstreamedSmallUpdatePatchThenRemoveIt() { var testRegistryValue = "PackageA"; @@ -39,6 +39,28 @@ namespace WixToolsetTest.BurnE2E packageAv1.VerifyTestRegistryRootDeleted(); } + [RuntimeFact] + public void CanInstallBundleWithSlipstreamedMinorUpgradePatchThenRemoveIt() + { + var testRegistryValue = "PackageA"; + + var packageAv1 = this.CreatePackageInstaller("PackageAv1"); + var bundleA = this.CreateBundleInstaller("BundleAv1_0_1"); + + var packageAv1SourceCodeInstalled = packageAv1.GetInstalledFilePath("Package.wxs"); + Assert.False(File.Exists(packageAv1SourceCodeInstalled), $"PackageAv1 payload should not be there on test start: {packageAv1SourceCodeInstalled}"); + + bundleA.Install(); + bundleA.VerifyRegisteredAndInPackageCache(); + Assert.True(File.Exists(packageAv1SourceCodeInstalled), String.Concat("Should have found PackageAv1 payload installed at: ", packageAv1SourceCodeInstalled)); + packageAv1.VerifyTestRegistryValue(testRegistryValue, V101); + + bundleA.Uninstall(); + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + Assert.False(File.Exists(packageAv1SourceCodeInstalled), String.Concat("PackageAv1 payload should have been removed by uninstall from: ", packageAv1SourceCodeInstalled)); + packageAv1.VerifyTestRegistryRootDeleted(); + } + /// /// BundleA installs PackageA with slipstreamed PatchA. /// BundleOnlyPatchA is installed which contains PatchA (which should be a no-op). diff --git a/src/test/sandbox/TestSandbox.wsb b/src/test/sandbox/TestSandbox.wsb index 01e11e6b..97c3c759 100644 --- a/src/test/sandbox/TestSandbox.wsb +++ b/src/test/sandbox/TestSandbox.wsb @@ -4,6 +4,11 @@ ..\..\..\build C:\build true + + + ..\..\..\build\logs + C:\logs + false .\ diff --git a/src/test/sandbox/startup.bat b/src/test/sandbox/startup.bat index 52f550ee..90f04f5f 100644 --- a/src/test/sandbox/startup.bat +++ b/src/test/sandbox/startup.bat @@ -1,5 +1,13 @@ @setlocal @echo off + + +::// Fix Sandbox glacial perf (24H2-???) +::// +REG ADD HKLM\SYSTEM\CurrentControlSet\Control\CI\Policy /v VerifiedAndReputablePolicyState /t REG_DWORD /d 0 /f +CITOOL -r -j + + SET DOTNET_VERSION=8.0 SET SANDBOX_FILES=C:\sandbox -- cgit v1.2.3-55-g6feb