From 98c369a92891244bde76448ae4a2b623b3ab394c Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 14 Jun 2022 15:10:07 -0500 Subject: Allow BundlePackage to fallback to QuietUninstallString to uninstall. Partial implementation of 6756 --- src/burn/engine/apply.cpp | 2 +- src/burn/engine/bundlepackageengine.cpp | 123 ++++++++++++++++++++- src/burn/engine/bundlepackageengine.h | 1 + src/burn/engine/elevation.cpp | 13 ++- src/burn/engine/exeengine.cpp | 66 ++++------- src/burn/engine/package.h | 1 + src/burn/engine/plan.cpp | 12 +- src/burn/test/BurnUnitTest/PlanTest.cpp | 123 ++++++++++++++++++++- .../BundlePackageUninstallFailureBundle.wixproj | 19 ++++ .../BundlePackageUninstallFailureBundle.wxs | 15 +++ .../PackageFail/PackageFail.wixproj | 12 ++ src/test/burn/WixTestTools/BundleVerifier.cs | 11 +- .../WixToolsetTest.BurnE2E/BundlePackageTests.cs | 112 +++++++++++++++++++ 13 files changed, 449 insertions(+), 61 deletions(-) create mode 100644 src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wixproj create mode 100644 src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wxs create mode 100644 src/test/burn/TestData/BundlePackageTests/PackageFail/PackageFail.wixproj (limited to 'src') diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp index d215cfe5..e5b978f1 100644 --- a/src/burn/engine/apply.cpp +++ b/src/burn/engine/apply.cpp @@ -2781,7 +2781,7 @@ static HRESULT ExecuteBundlePackage( } else { - hrExecute = BundlePackageEngineExecutePackage(pExecuteAction, pContext->pCache, &pEngineState->variables, fRollback, GenericExecuteMessageHandler, pContext, pRestart); + hrExecute = BundlePackageEngineExecutePackage(pExecuteAction, pContext->pCache, &pEngineState->variables, fRollback, SUCCEEDED(pExecuteAction->bundlePackage.pPackage->hrCacheResult), GenericExecuteMessageHandler, pContext, pRestart); ExitOnFailure(hrExecute, "Failed to configure per-user BUNDLE package."); } diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp index 81279cf6..7ae12a1e 100644 --- a/src/burn/engine/bundlepackageengine.cpp +++ b/src/burn/engine/bundlepackageengine.cpp @@ -18,6 +18,7 @@ static HRESULT ExecuteBundle( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, __in BOOL fRollback, + __in BOOL fCacheAvailable, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __in BOOTSTRAPPER_ACTION_STATE action, @@ -30,6 +31,11 @@ static HRESULT ExecuteBundle( __in_z_opt LPCWSTR wzEngineWorkingDirectory, __out BOOTSTRAPPER_APPLY_RESTART* pRestart ); +static HRESULT DetectArpEntry( + __in BURN_PACKAGE* pPackage, + __out BOOL* pfRegistered, + __out LPWSTR* psczQuietUninstallString + ); static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType( __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType ); @@ -197,6 +203,7 @@ extern "C" void BundlePackageEnginePackageUninitialize( ) { ReleaseStr(pPackage->Bundle.sczBundleId); + ReleaseStr(pPackage->Bundle.sczArpKeyPath); ReleaseVerutilVersion(pPackage->Bundle.pVersion); ReleaseStr(pPackage->Bundle.sczRegistrationKey); ReleaseStr(pPackage->Bundle.sczInstallArguments); @@ -600,6 +607,7 @@ extern "C" HRESULT BundlePackageEngineExecutePackage( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, __in BOOL fRollback, + __in BOOL fCacheAvailable, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -613,7 +621,7 @@ extern "C" HRESULT BundlePackageEngineExecutePackage( BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_CHAIN_PACKAGE; BURN_PACKAGE* pPackage = pExecuteAction->bundlePackage.pPackage; - return ExecuteBundle(pCache, pVariables, fRollback, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, FALSE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart); + return ExecuteBundle(pCache, pVariables, fRollback, fCacheAvailable, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, FALSE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart); } extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle( @@ -635,7 +643,7 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle( BOOTSTRAPPER_RELATION_TYPE relationType = ConvertRelationType(pRelatedBundle->planRelationType); BURN_PACKAGE* pPackage = &pRelatedBundle->package; - return ExecuteBundle(pCache, pVariables, fRollback, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, TRUE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart); + return ExecuteBundle(pCache, pVariables, fRollback, TRUE, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, TRUE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart); } extern "C" void BundlePackageEngineUpdateInstallRegistrationState( @@ -678,10 +686,10 @@ static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback( BOOTSTRAPPER_RELATION_TYPE relationType = RelatedBundleConvertRelationType(pBundle->relationType); BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext; - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pPackage->Bundle.sczBundleId, -1)) + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pPackage->Bundle.sczBundleId, -1) && + pPackage->Bundle.fWin64 == (REG_KEY_64BIT == pBundle->regBitness)) { Assert(BOOTSTRAPPER_RELATION_UPGRADE == relationType); - Assert(pPackage->Bundle.fWin64 == (REG_KEY_64BIT == pBundle->regBitness)); pContext->fSelfFound = TRUE; } @@ -727,6 +735,7 @@ static HRESULT ExecuteBundle( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, __in BOOL fRollback, + __in BOOL fCacheAvailable, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __in BOOTSTRAPPER_ACTION_STATE action, @@ -749,6 +758,10 @@ static HRESULT ExecuteBundle( LPWSTR sczUserArgs = NULL; LPWSTR sczUserArgsObfuscated = NULL; LPWSTR sczCommandObfuscated = NULL; + LPWSTR sczArpUninstallString = NULL; + int argcArp = 0; + LPWSTR* argvArp = NULL; + BOOL fRegistered = FALSE; HANDLE hExecutableFile = INVALID_HANDLE_VALUE; STARTUPINFOW si = { }; PROCESS_INFORMATION pi = { }; @@ -772,6 +785,51 @@ static HRESULT ExecuteBundle( hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); ExitOnFailure(hr, "Failed to get cached path for related bundle: %ls", pPackage->sczId); } + else if (!fCacheAvailable) + { + ExitOnNull(BOOTSTRAPPER_ACTION_STATE_UNINSTALL == action, hr, E_INVALIDARG, "The only supported action when the cache is not available is UNINSTALL."); + + hr = DetectArpEntry(pPackage, &fRegistered, &sczArpUninstallString); + ExitOnFailure(hr, "Failed to query ARP for uninstall."); + + if (!fRegistered) + { + if (fRollback) + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pPackage->sczId, LoggingActionStateToString(action), LoggingPackageStateToString(BOOTSTRAPPER_PACKAGE_STATE_ABSENT)); + } + else + { + LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pPackage->sczId); + } + + ExitFunction(); + } + + ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null."); + + hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp); + ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString); + + ExitOnNull(argcArp, hr, E_INVALIDARG, "QuietUninstallString must contain an executable path."); + + hr = StrAllocString(&sczExecutablePath, argvArp[0], 0); + ExitOnFailure(hr, "Failed to copy executable path."); + + if (pPackage->fPerMachine) + { + hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath); + ExitOnFailure(hr, "Failed to verify the QuietUninstallString executable path is in a secure location: %ls", sczExecutablePath); + if (S_FALSE == hr) + { + LogStringLine(REPORT_STANDARD, "The QuietUninstallString executable path is not in a secure location: %ls", sczExecutablePath); + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + } + } + + hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath); + } else { // get cached executable path @@ -859,6 +917,12 @@ static HRESULT ExecuteBundle( hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath); ExitOnFailure(hr, "Failed to allocate base command."); + for (int i = 1; i < argcArp; ++i) + { + hr = AppAppendCommandLineArgument(&sczBaseCommand, argvArp[i]); + ExitOnFailure(hr, "Failed to append argument from ARP."); + } + if (!fRunEmbedded) { hr = StrAllocConcat(&sczBaseCommand, L" -quiet", 0); @@ -962,6 +1026,12 @@ LExit: StrSecureZeroFreeString(sczUserArgs); ReleaseStr(sczUserArgsObfuscated); ReleaseStr(sczCommandObfuscated); + ReleaseStr(sczArpUninstallString); + + if (argvArp) + { + AppFreeCommandLineArgs(argvArp); + } ReleaseHandle(pi.hThread); ReleaseHandle(pi.hProcess); @@ -974,6 +1044,51 @@ LExit: return hr; } +static HRESULT DetectArpEntry( + __in BURN_PACKAGE* pPackage, + __out BOOL* pfRegistered, + __out LPWSTR* psczQuietUninstallString + ) +{ + HRESULT hr = S_OK; + HKEY hKey = NULL; + HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + REG_KEY_BITNESS keyBitness = pPackage->Bundle.fWin64 ? REG_KEY_64BIT : REG_KEY_32BIT; + + *pfRegistered = FALSE; + if (psczQuietUninstallString) + { + ReleaseNullStr(*psczQuietUninstallString); + } + + if (!pPackage->Bundle.sczArpKeyPath) + { + hr = PathConcatRelativeToBase(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\", pPackage->Bundle.sczBundleId, &pPackage->Bundle.sczArpKeyPath); + ExitOnFailure(hr, "Failed to build full key path."); + } + + hr = RegOpenEx(hkRoot, pPackage->Bundle.sczArpKeyPath, KEY_READ, keyBitness, &hKey); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to open registry key: %ls.", pPackage->Bundle.sczArpKeyPath); + + *pfRegistered = TRUE; + + hr = RegReadString(hKey, L"QuietUninstallString", psczQuietUninstallString); + if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to read QuietUninstallString."); + +LExit: + ReleaseRegKey(hKey); + + return hr; +} + static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType( __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType ) diff --git a/src/burn/engine/bundlepackageengine.h b/src/burn/engine/bundlepackageengine.h index e245f6ce..60854a07 100644 --- a/src/burn/engine/bundlepackageengine.h +++ b/src/burn/engine/bundlepackageengine.h @@ -53,6 +53,7 @@ HRESULT BundlePackageEngineExecutePackage( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, __in BOOL fRollback, + __in BOOL fCacheAvailable, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 9c7cf89f..4a5be8ec 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -907,6 +907,9 @@ extern "C" HRESULT ElevationExecuteBundlePackage( hr = BuffWriteNumber(&pbData, &cbData, fRollback); ExitOnFailure(hr, "Failed to write rollback."); + hr = BuffWriteNumber(&pbData, &cbData, SUCCEEDED(pExecuteAction->bundlePackage.pPackage->hrCacheResult)); + ExitOnFailure(hr, "Failed to write fCacheAvailable."); + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->bundlePackage.sczParent); ExitOnFailure(hr, "Failed to write the parent to the message buffer."); @@ -2855,7 +2858,8 @@ static HRESULT OnExecuteBundlePackage( HRESULT hr = S_OK; SIZE_T iData = 0; LPWSTR sczPackage = NULL; - DWORD dwRollback = 0; + BOOL fRollback = FALSE; + BOOL fCacheAvailable = FALSE; BURN_EXECUTE_ACTION executeAction = { }; LPWSTR sczIgnoreDependencies = NULL; LPWSTR sczAncestors = NULL; @@ -2871,9 +2875,12 @@ static HRESULT OnExecuteBundlePackage( hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.bundlePackage.action); ExitOnFailure(hr, "Failed to read action."); - hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback); + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback); ExitOnFailure(hr, "Failed to read rollback."); + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fCacheAvailable); + ExitOnFailure(hr, "Failed to read fCacheAvailable."); + hr = BuffReadString(pbData, cbData, &iData, &executeAction.bundlePackage.sczParent); ExitOnFailure(hr, "Failed to read the parent."); @@ -2918,7 +2925,7 @@ static HRESULT OnExecuteBundlePackage( } // Execute BUNDLE package. - hr = BundlePackageEngineExecutePackage(&executeAction, pCache, pVariables, static_cast(dwRollback), GenericExecuteMessageHandler, hPipe, &bundleRestart); + hr = BundlePackageEngineExecutePackage(&executeAction, pCache, pVariables, fRollback, fCacheAvailable, GenericExecuteMessageHandler, hPipe, &bundleRestart); ExitOnFailure(hr, "Failed to execute BUNDLE package."); LExit: diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index 3a64ecd8..f7be082d 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -7,11 +7,6 @@ static HRESULT DetectArpEntry( __out BOOTSTRAPPER_PACKAGE_STATE* pPackageState, __out_opt LPWSTR* psczQuietUninstallString ); -static HRESULT ParseArpUninstallString( - __in_z LPCWSTR wzArpUninstallString, - __inout LPWSTR* psczExecutablePath, - __inout LPWSTR* psczArguments - ); // function definitions @@ -435,20 +430,20 @@ extern "C" HRESULT ExeEngineExecutePackage( LPWSTR sczUserArgsObfuscated = NULL; LPWSTR sczCommandObfuscated = NULL; LPWSTR sczArpUninstallString = NULL; - LPWSTR sczArpArguments = NULL; + int argcArp = 0; + LPWSTR* argvArp = NULL; BOOTSTRAPPER_PACKAGE_STATE applyState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; HANDLE hExecutableFile = INVALID_HANDLE_VALUE; DWORD dwExitCode = 0; BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; - LPCWSTR wzUninstallArguments = pPackage->Exe.sczUninstallArguments; if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType && (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action || BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->exePackage.action && fRollback)) { hr = DetectArpEntry(pPackage, &applyState, &sczArpUninstallString); - ExitOnFailure(hr, "Failed to query ArpEntry for uninstall."); + ExitOnFailure(hr, "Failed to query ArpEntry for %hs.", BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action ? "uninstall" : "install"); if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == applyState && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action) { @@ -487,9 +482,14 @@ extern "C" HRESULT ExeEngineExecutePackage( { ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null."); - hr = ParseArpUninstallString(sczArpUninstallString, &sczExecutablePath, &sczArpArguments); + hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp); ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString); + ExitOnNull(argcArp, hr, E_INVALIDARG, "QuietUninstallString must contain an executable path."); + + hr = StrAllocString(&sczExecutablePath, argvArp[0], 0); + ExitOnFailure(hr, "Failed to copy executable path."); + if (pPackage->fPerMachine) { hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath); @@ -503,8 +503,6 @@ extern "C" HRESULT ExeEngineExecutePackage( hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath); - - wzUninstallArguments = sczArpArguments; } else { @@ -528,7 +526,7 @@ extern "C" HRESULT ExeEngineExecutePackage( break; case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: - wzArguments = wzUninstallArguments; + wzArguments = pPackage->Exe.sczUninstallArguments; break; case BOOTSTRAPPER_ACTION_STATE_REPAIR: @@ -583,6 +581,12 @@ extern "C" HRESULT ExeEngineExecutePackage( hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath); ExitOnFailure(hr, "Failed to allocate base command."); + for (int i = 1; i < argcArp; ++i) + { + hr = AppAppendCommandLineArgument(&sczBaseCommand, argvArp[i]); + ExitOnFailure(hr, "Failed to append argument from ARP."); + } + if (pPackage->Exe.fBundle) { hr = StrAllocConcat(&sczBaseCommand, L" -norestart", 0); @@ -655,7 +659,11 @@ LExit: ReleaseStr(sczUserArgsObfuscated); ReleaseStr(sczCommandObfuscated); ReleaseStr(sczArpUninstallString); - ReleaseStr(sczArpArguments); + + if (argvArp) + { + AppFreeCommandLineArgs(argvArp); + } ReleaseFileHandle(hExecutableFile); @@ -1097,35 +1105,3 @@ LExit: return hr; } - -static HRESULT ParseArpUninstallString( - __in_z LPCWSTR wzArpUninstallString, - __inout LPWSTR* psczExecutablePath, - __inout LPWSTR* psczArguments - ) -{ - HRESULT hr = S_OK; - int argc = 0; - LPWSTR* argv = NULL; - - hr = AppParseCommandLine(wzArpUninstallString, &argc, &argv); - ExitOnFailure(hr, "Failed to parse uninstall string as command line: %ls.", wzArpUninstallString); - ExitOnNull(argc, hr, E_INVALIDARG, "Uninstall string must contain an executable path."); - - hr = StrAllocString(psczExecutablePath, argv[0], 0); - ExitOnFailure(hr, "Failed to copy executable path for ArpCommand."); - - for (int i = 1; i < argc; ++i) - { - hr = AppAppendCommandLineArgument(psczArguments, argv[i]); - ExitOnFailure(hr, "Failed to append argument for ArpCommand."); - } - -LExit: - if (argv) - { - AppFreeCommandLineArgs(argv); - } - - return hr; -} diff --git a/src/burn/engine/package.h b/src/burn/engine/package.h index 5fccf50a..449c4e08 100644 --- a/src/burn/engine/package.h +++ b/src/burn/engine/package.h @@ -321,6 +321,7 @@ typedef struct _BURN_PACKAGE struct { LPWSTR sczBundleId; + LPWSTR sczArpKeyPath; VERUTIL_VERSION* pVersion; LPWSTR sczRegistrationKey; LPWSTR sczInstallArguments; diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp index 18f9b274..5c9ebe08 100644 --- a/src/burn/engine/plan.cpp +++ b/src/burn/engine/plan.cpp @@ -2805,13 +2805,16 @@ static BURN_CACHE_PACKAGE_TYPE GetCachePackageType( case BOOTSTRAPPER_ACTION_STATE_NONE: break; case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: - if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type || - BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType) + if (BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType) { - // Bundle and non-ArpEntry Exe packages require the package for all operations (even uninstall). - // TODO: bundles could theoretically use package cache. + // non-ArpEntry Exe packages require the package for all operations (even uninstall). cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED; } + else if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type) + { + // Bundle packages prefer the cache but can fallback to the ARP registration. + cachePackageType = BURN_CACHE_PACKAGE_TYPE_OPTIONAL; + } else { // The other package types can uninstall without the original package. @@ -2823,6 +2826,7 @@ static BURN_CACHE_PACKAGE_TYPE GetCachePackageType( case BOOTSTRAPPER_ACTION_STATE_REPAIR: __fallthrough; case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: __fallthrough; default: + // TODO: bundles could theoretically use package cache. cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED; break; } diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp index 37027ada..7998f837 100644 --- a/src/burn/test/BurnUnitTest/PlanTest.cpp +++ b/src/burn/test/BurnUnitTest/PlanTest.cpp @@ -654,10 +654,10 @@ namespace Bootstrapper fRollback = FALSE; dwIndex = 0; ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); - ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_REQUIRED); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_OPTIONAL); ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageA"); ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 6); - ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_REQUIRED); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_OPTIONAL); ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageB"); Assert::Equal(dwIndex, pPlan->cCacheActions); @@ -824,6 +824,125 @@ namespace Bootstrapper ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageB", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); } + [Fact] + void MultipleBundlePackageUninstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzMultipleBundlePackageManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + NativeAssert::StringEqual(L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", pPlan->wzBundleId); + NativeAssert::StringEqual(L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", pPlan->wzBundleProviderKey); + Assert::Equal(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal(FALSE, pPlan->fDisableRollback); + Assert::Equal(FALSE, pPlan->fDisallowRemoval); + Assert::Equal(FALSE, pPlan->fDowngrade); + Assert::Equal(BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE | BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_PROVIDER_KEY, pPlan->dwRegistrationOperations); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}"); + Assert::Equal(dwIndex, pPlan->cRegistrationActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}"); + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions); + + fRollback = FALSE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB", FALSE, BURN_CACHE_PACKAGE_TYPE_OPTIONAL, BURN_CACHE_PACKAGE_TYPE_REQUIRED); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageB"); + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 6); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA", FALSE, BURN_CACHE_PACKAGE_TYPE_OPTIONAL, BURN_CACHE_PACKAGE_TYPE_REQUIRED); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageA"); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(78462280ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"PackageB"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", unregisterActions1, 1); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageB", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + dwExecuteCheckpointId += 1; // cache checkpoints + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", unregisterActions1, 1); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", registerActions1, 1); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageB", BOOTSTRAPPER_ACTION_STATE_INSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + dwExecuteCheckpointId += 1; // cache checkpoints + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", registerActions1, 1); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(2ul, pPlan->cExecutePackagesTotal); + Assert::Equal(4ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"PackageB"); + ValidateCleanAction(pPlan, dwIndex++, L"PackageA"); + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{7506235A-7C59-4750-82C7-EB460A87ED3A}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{B39CEE4D-CCD7-4797-BE3A-6613BD1DC4BE}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(3ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageB", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + [Fact] void OrphanCompatiblePackageTest() { diff --git a/src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wixproj b/src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wixproj new file mode 100644 index 00000000..276d9dcb --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wixproj @@ -0,0 +1,19 @@ + + + + Bundle + {1CD57FE5-A2FA-4A72-BEBA-74D9730C2C93} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wxs b/src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wxs new file mode 100644 index 00000000..1e27161c --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/BundlePackageTests/PackageFail/PackageFail.wixproj b/src/test/burn/TestData/BundlePackageTests/PackageFail/PackageFail.wixproj new file mode 100644 index 00000000..68b5470b --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/PackageFail/PackageFail.wixproj @@ -0,0 +1,12 @@ + + + + {2674BFB2-634D-42DB-A1EC-C2CD148A2328} + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/WixTestTools/BundleVerifier.cs b/src/test/burn/WixTestTools/BundleVerifier.cs index ff45a291..3a19ca02 100644 --- a/src/test/burn/WixTestTools/BundleVerifier.cs +++ b/src/test/burn/WixTestTools/BundleVerifier.cs @@ -158,14 +158,21 @@ namespace WixTestTools } } - public void VerifyPackageIsCached(string packageId, bool cached = true) + public string GetPackageEntryPointCachePath(string packageId) { using var wixOutput = WixOutput.Read(this.BundlePdb); var intermediate = Intermediate.Load(wixOutput); var section = intermediate.Sections.Single(); var packageSymbol = section.Symbols.OfType().Single(p => p.Id.Id == packageId); + var packagePayloadSymbol = section.Symbols.OfType().Single(p => p.Id.Id == packageSymbol.PayloadRef); var cachePath = this.GetPackageCachePathForCacheId(packageSymbol.CacheId, packageSymbol.PerMachine == true); - Assert.Equal(cached, Directory.Exists(cachePath)); + return Path.Combine(cachePath, packagePayloadSymbol.Name); + } + + public void VerifyPackageIsCached(string packageId, bool cached = true) + { + var entryPointCachePath = this.GetPackageEntryPointCachePath(packageId); + Assert.Equal(cached, File.Exists(entryPointCachePath)); } public void VerifyPackageProviderRemoved(string packageId) diff --git a/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs index 0c6e5873..b3ef9430 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs @@ -5,6 +5,7 @@ namespace WixToolsetTest.BurnE2E using System; using System.IO; using WixTestTools; + using WixToolset.Mba.Core; using Xunit; using Xunit.Abstractions; @@ -137,5 +138,116 @@ namespace WixToolsetTest.BurnE2E upgradeBundlePackageBundlev1.VerifyUnregisteredAndRemovedFromPackageCache(); bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache(); } + + [RuntimeFact] + public void CanRecacheAndReinstallBundlePackageOnUninstallRollback() + { + var packageFail = this.CreatePackageInstaller("PackageFail"); + var packageA = this.CreatePackageInstaller(@"..\BasicFunctionalityTests\PackageA"); + var bundleA = this.CreateBundleInstaller(@"..\BasicFunctionalityTests\BundleA"); + var bundlePackageUninstallFailureBundle = this.CreateBundleInstaller("BundlePackageUninstallFailureBundle"); + var testBAController = this.CreateTestBAController(); + var bundleASelfCachedPath = bundleA.GetExpectedCachedBundlePath(); + var bundleAEmbeddedCachedPath = bundlePackageUninstallFailureBundle.GetPackageEntryPointCachePath("PackageA"); + + packageA.VerifyInstalled(false); + packageFail.VerifyInstalled(false); + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + + testBAController.SetPackageRequestedCacheType("PackageA", BOOTSTRAPPER_CACHE_TYPE.Remove); + + var installLogPath = bundlePackageUninstallFailureBundle.Install(); + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache(); + packageFail.VerifyInstalled(true); + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1); + + Assert.False(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + + testBAController.ResetPackageStates("PackageA"); + testBAController.SetAllowAcquireAfterValidationFailure(); + + var uninstallLogPath = bundlePackageUninstallFailureBundle.Uninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "FAILWHENDEFERRED=1"); + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache(); + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1); + packageFail.VerifyInstalled(true); + + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, "TESTBA: OnCachePackageNonVitalValidationFailure() - id: PackageA, default: None, requested: Acquire")); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + } + + [RuntimeFact] + public void CanReinstallBundlePackageOnUninstallRollback() + { + var packageFail = this.CreatePackageInstaller("PackageFail"); + var packageA = this.CreatePackageInstaller(@"..\BasicFunctionalityTests\PackageA"); + var bundleA = this.CreateBundleInstaller(@"..\BasicFunctionalityTests\BundleA"); + var bundlePackageUninstallFailureBundle = this.CreateBundleInstaller("BundlePackageUninstallFailureBundle"); + var bundleASelfCachedPath = bundleA.GetExpectedCachedBundlePath(); + var bundleAEmbeddedCachedPath = bundlePackageUninstallFailureBundle.GetPackageEntryPointCachePath("PackageA"); + + packageA.VerifyInstalled(false); + packageFail.VerifyInstalled(false); + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + + var installLogPath = bundlePackageUninstallFailureBundle.Install(); + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache(); + packageFail.VerifyInstalled(true); + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1); + + Assert.False(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + + var uninstallLogPath = bundlePackageUninstallFailureBundle.Uninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "FAILWHENDEFERRED=1"); + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache(); + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1); + packageFail.VerifyInstalled(true); + + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, "TESTBA: OnCachePackageNonVitalValidationFailure() - id: PackageA")); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + } + + [RuntimeFact] + public void CanSkipReinstallBundlePackageOnUninstallRollback() + { + var packageFail = this.CreatePackageInstaller("PackageFail"); + var packageA = this.CreatePackageInstaller(@"..\BasicFunctionalityTests\PackageA"); + var bundleA = this.CreateBundleInstaller(@"..\BasicFunctionalityTests\BundleA"); + var bundlePackageUninstallFailureBundle = this.CreateBundleInstaller("BundlePackageUninstallFailureBundle"); + var testBAController = this.CreateTestBAController(); + var bundleASelfCachedPath = bundleA.GetExpectedCachedBundlePath(); + var bundleAEmbeddedCachedPath = bundlePackageUninstallFailureBundle.GetPackageEntryPointCachePath("PackageA"); + + packageA.VerifyInstalled(false); + packageFail.VerifyInstalled(false); + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + + testBAController.SetPackageRequestedCacheType("PackageA", BOOTSTRAPPER_CACHE_TYPE.Remove); + + var installLogPath = bundlePackageUninstallFailureBundle.Install(); + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache(); + packageFail.VerifyInstalled(true); + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1); + + Assert.False(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + + var uninstallLogPath = bundlePackageUninstallFailureBundle.Uninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "FAILWHENDEFERRED=1"); + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache(); + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + packageFail.VerifyInstalled(true); + + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, "TESTBA: OnCachePackageNonVitalValidationFailure() - id: PackageA, default: None, requested: None")); + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath); + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath); + } } } -- cgit v1.2.3-55-g6feb