@joebigelow / wix / commits / 98c369a9

Allow BundlePackage to fallback to QuietUninstallString to uninstall.

Partial implementation of 6756

Sean Hall committed Jun 14, 2022 at 15:10 UTC 98c369a92891244bde76448ae4a2b623b3ab394c
13 files changed +449 -61
src/burn/engine/apply.cpp
+1 -1
@@ -2781,7 +2781,7 @@ static HRESULT ExecuteBundlePackage(
2781 }
2782 else
2783 {
2784 - hrExecute = BundlePackageEngineExecutePackage(pExecuteAction, pContext->pCache, &pEngineState->variables, fRollback, GenericExecuteMessageHandler, pContext, pRestart);
2784 + hrExecute = BundlePackageEngineExecutePackage(pExecuteAction, pContext->pCache, &pEngineState->variables, fRollback, SUCCEEDED(pExecuteAction->bundlePackage.pPackage->hrCacheResult), GenericExecuteMessageHandler, pContext, pRestart);
2785 ExitOnFailure(hrExecute, "Failed to configure per-user BUNDLE package.");
2786 }
2787
src/burn/engine/bundlepackageengine.cpp
+119 -4
@@ -18,6 +18,7 @@ static HRESULT ExecuteBundle(
18 __in BURN_CACHE* pCache,
19 __in BURN_VARIABLES* pVariables,
20 __in BOOL fRollback,
21 + __in BOOL fCacheAvailable,
22 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
23 __in LPVOID pvContext,
24 __in BOOTSTRAPPER_ACTION_STATE action,
@@ -30,6 +31,11 @@ static HRESULT ExecuteBundle(
31 __in_z_opt LPCWSTR wzEngineWorkingDirectory,
32 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
33 );
34 +static HRESULT DetectArpEntry(
35 + __in BURN_PACKAGE* pPackage,
36 + __out BOOL* pfRegistered,
37 + __out LPWSTR* psczQuietUninstallString
38 + );
39 static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType(
40 __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType
41 );
@@ -197,6 +203,7 @@ extern "C" void BundlePackageEnginePackageUninitialize(
203 )
204 {
205 ReleaseStr(pPackage->Bundle.sczBundleId);
206 + ReleaseStr(pPackage->Bundle.sczArpKeyPath);
207 ReleaseVerutilVersion(pPackage->Bundle.pVersion);
208 ReleaseStr(pPackage->Bundle.sczRegistrationKey);
209 ReleaseStr(pPackage->Bundle.sczInstallArguments);
@@ -600,6 +607,7 @@ extern "C" HRESULT BundlePackageEngineExecutePackage(
607 __in BURN_CACHE* pCache,
608 __in BURN_VARIABLES* pVariables,
609 __in BOOL fRollback,
610 + __in BOOL fCacheAvailable,
611 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
612 __in LPVOID pvContext,
613 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
@@ -613,7 +621,7 @@ extern "C" HRESULT BundlePackageEngineExecutePackage(
621 BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_CHAIN_PACKAGE;
622 BURN_PACKAGE* pPackage = pExecuteAction->bundlePackage.pPackage;
623
616 - return ExecuteBundle(pCache, pVariables, fRollback, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, FALSE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart);
624 + return ExecuteBundle(pCache, pVariables, fRollback, fCacheAvailable, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, FALSE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart);
625 }
626
627 extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
@@ -635,7 +643,7 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
643 BOOTSTRAPPER_RELATION_TYPE relationType = ConvertRelationType(pRelatedBundle->planRelationType);
644 BURN_PACKAGE* pPackage = &pRelatedBundle->package;
645
638 - return ExecuteBundle(pCache, pVariables, fRollback, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, TRUE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart);
646 + return ExecuteBundle(pCache, pVariables, fRollback, TRUE, pfnGenericMessageHandler, pvContext, action, relationType, pPackage, TRUE, wzParent, wzIgnoreDependencies, wzAncestors, wzEngineWorkingDirectory, pRestart);
647 }
648
649 extern "C" void BundlePackageEngineUpdateInstallRegistrationState(
@@ -678,10 +686,10 @@ static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback(
686 BOOTSTRAPPER_RELATION_TYPE relationType = RelatedBundleConvertRelationType(pBundle->relationType);
687 BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext;
688
681 - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pPackage->Bundle.sczBundleId, -1))
689 + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pPackage->Bundle.sczBundleId, -1) &&
690 + pPackage->Bundle.fWin64 == (REG_KEY_64BIT == pBundle->regBitness))
691 {
692 Assert(BOOTSTRAPPER_RELATION_UPGRADE == relationType);
684 - Assert(pPackage->Bundle.fWin64 == (REG_KEY_64BIT == pBundle->regBitness));
693
694 pContext->fSelfFound = TRUE;
695 }
@@ -727,6 +735,7 @@ static HRESULT ExecuteBundle(
735 __in BURN_CACHE* pCache,
736 __in BURN_VARIABLES* pVariables,
737 __in BOOL fRollback,
738 + __in BOOL fCacheAvailable,
739 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
740 __in LPVOID pvContext,
741 __in BOOTSTRAPPER_ACTION_STATE action,
@@ -749,6 +758,10 @@ static HRESULT ExecuteBundle(
758 LPWSTR sczUserArgs = NULL;
759 LPWSTR sczUserArgsObfuscated = NULL;
760 LPWSTR sczCommandObfuscated = NULL;
761 + LPWSTR sczArpUninstallString = NULL;
762 + int argcArp = 0;
763 + LPWSTR* argvArp = NULL;
764 + BOOL fRegistered = FALSE;
765 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
766 STARTUPINFOW si = { };
767 PROCESS_INFORMATION pi = { };
@@ -772,6 +785,51 @@ static HRESULT ExecuteBundle(
785 hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory);
786 ExitOnFailure(hr, "Failed to get cached path for related bundle: %ls", pPackage->sczId);
787 }
788 + else if (!fCacheAvailable)
789 + {
790 + ExitOnNull(BOOTSTRAPPER_ACTION_STATE_UNINSTALL == action, hr, E_INVALIDARG, "The only supported action when the cache is not available is UNINSTALL.");
791 +
792 + hr = DetectArpEntry(pPackage, &fRegistered, &sczArpUninstallString);
793 + ExitOnFailure(hr, "Failed to query ARP for uninstall.");
794 +
795 + if (!fRegistered)
796 + {
797 + if (fRollback)
798 + {
799 + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pPackage->sczId, LoggingActionStateToString(action), LoggingPackageStateToString(BOOTSTRAPPER_PACKAGE_STATE_ABSENT));
800 + }
801 + else
802 + {
803 + LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pPackage->sczId);
804 + }
805 +
806 + ExitFunction();
807 + }
808 +
809 + ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null.");
810 +
811 + hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp);
812 + ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString);
813 +
814 + ExitOnNull(argcArp, hr, E_INVALIDARG, "QuietUninstallString must contain an executable path.");
815 +
816 + hr = StrAllocString(&sczExecutablePath, argvArp[0], 0);
817 + ExitOnFailure(hr, "Failed to copy executable path.");
818 +
819 + if (pPackage->fPerMachine)
820 + {
821 + hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath);
822 + ExitOnFailure(hr, "Failed to verify the QuietUninstallString executable path is in a secure location: %ls", sczExecutablePath);
823 + if (S_FALSE == hr)
824 + {
825 + LogStringLine(REPORT_STANDARD, "The QuietUninstallString executable path is not in a secure location: %ls", sczExecutablePath);
826 + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED));
827 + }
828 + }
829 +
830 + hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory);
831 + ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath);
832 + }
833 else
834 {
835 // get cached executable path
@@ -859,6 +917,12 @@ static HRESULT ExecuteBundle(
917 hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath);
918 ExitOnFailure(hr, "Failed to allocate base command.");
919
920 + for (int i = 1; i < argcArp; ++i)
921 + {
922 + hr = AppAppendCommandLineArgument(&sczBaseCommand, argvArp[i]);
923 + ExitOnFailure(hr, "Failed to append argument from ARP.");
924 + }
925 +
926 if (!fRunEmbedded)
927 {
928 hr = StrAllocConcat(&sczBaseCommand, L" -quiet", 0);
@@ -962,6 +1026,12 @@ LExit:
1026 StrSecureZeroFreeString(sczUserArgs);
1027 ReleaseStr(sczUserArgsObfuscated);
1028 ReleaseStr(sczCommandObfuscated);
1029 + ReleaseStr(sczArpUninstallString);
1030 +
1031 + if (argvArp)
1032 + {
1033 + AppFreeCommandLineArgs(argvArp);
1034 + }
1035
1036 ReleaseHandle(pi.hThread);
1037 ReleaseHandle(pi.hProcess);
@@ -974,6 +1044,51 @@ LExit:
1044 return hr;
1045 }
1046
1047 +static HRESULT DetectArpEntry(
1048 + __in BURN_PACKAGE* pPackage,
1049 + __out BOOL* pfRegistered,
1050 + __out LPWSTR* psczQuietUninstallString
1051 + )
1052 +{
1053 + HRESULT hr = S_OK;
1054 + HKEY hKey = NULL;
1055 + HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
1056 + REG_KEY_BITNESS keyBitness = pPackage->Bundle.fWin64 ? REG_KEY_64BIT : REG_KEY_32BIT;
1057 +
1058 + *pfRegistered = FALSE;
1059 + if (psczQuietUninstallString)
1060 + {
1061 + ReleaseNullStr(*psczQuietUninstallString);
1062 + }
1063 +
1064 + if (!pPackage->Bundle.sczArpKeyPath)
1065 + {
1066 + hr = PathConcatRelativeToBase(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\", pPackage->Bundle.sczBundleId, &pPackage->Bundle.sczArpKeyPath);
1067 + ExitOnFailure(hr, "Failed to build full key path.");
1068 + }
1069 +
1070 + hr = RegOpenEx(hkRoot, pPackage->Bundle.sczArpKeyPath, KEY_READ, keyBitness, &hKey);
1071 + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
1072 + {
1073 + ExitFunction1(hr = S_OK);
1074 + }
1075 + ExitOnFailure(hr, "Failed to open registry key: %ls.", pPackage->Bundle.sczArpKeyPath);
1076 +
1077 + *pfRegistered = TRUE;
1078 +
1079 + hr = RegReadString(hKey, L"QuietUninstallString", psczQuietUninstallString);
1080 + if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
1081 + {
1082 + hr = S_OK;
1083 + }
1084 + ExitOnFailure(hr, "Failed to read QuietUninstallString.");
1085 +
1086 +LExit:
1087 + ReleaseRegKey(hKey);
1088 +
1089 + return hr;
1090 +}
1091 +
1092 static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType(
1093 __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType
1094 )
src/burn/engine/bundlepackageengine.h
+1
@@ -53,6 +53,7 @@ HRESULT BundlePackageEngineExecutePackage(
53 __in BURN_CACHE* pCache,
54 __in BURN_VARIABLES* pVariables,
55 __in BOOL fRollback,
56 + __in BOOL fCacheAvailable,
57 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
58 __in LPVOID pvContext,
59 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
src/burn/engine/elevation.cpp
+10 -3
@@ -907,6 +907,9 @@ extern "C" HRESULT ElevationExecuteBundlePackage(
907 hr = BuffWriteNumber(&pbData, &cbData, fRollback);
908 ExitOnFailure(hr, "Failed to write rollback.");
909
910 + hr = BuffWriteNumber(&pbData, &cbData, SUCCEEDED(pExecuteAction->bundlePackage.pPackage->hrCacheResult));
911 + ExitOnFailure(hr, "Failed to write fCacheAvailable.");
912 +
913 hr = BuffWriteString(&pbData, &cbData, pExecuteAction->bundlePackage.sczParent);
914 ExitOnFailure(hr, "Failed to write the parent to the message buffer.");
915
@@ -2855,7 +2858,8 @@ static HRESULT OnExecuteBundlePackage(
2858 HRESULT hr = S_OK;
2859 SIZE_T iData = 0;
2860 LPWSTR sczPackage = NULL;
2858 - DWORD dwRollback = 0;
2861 + BOOL fRollback = FALSE;
2862 + BOOL fCacheAvailable = FALSE;
2863 BURN_EXECUTE_ACTION executeAction = { };
2864 LPWSTR sczIgnoreDependencies = NULL;
2865 LPWSTR sczAncestors = NULL;
@@ -2871,9 +2875,12 @@ static HRESULT OnExecuteBundlePackage(
2875 hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.bundlePackage.action);
2876 ExitOnFailure(hr, "Failed to read action.");
2877
2874 - hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback);
2878 + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback);
2879 ExitOnFailure(hr, "Failed to read rollback.");
2880
2881 + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fCacheAvailable);
2882 + ExitOnFailure(hr, "Failed to read fCacheAvailable.");
2883 +
2884 hr = BuffReadString(pbData, cbData, &iData, &executeAction.bundlePackage.sczParent);
2885 ExitOnFailure(hr, "Failed to read the parent.");
2886
@@ -2918,7 +2925,7 @@ static HRESULT OnExecuteBundlePackage(
2925 }
2926
2927 // Execute BUNDLE package.
2921 - hr = BundlePackageEngineExecutePackage(&executeAction, pCache, pVariables, static_cast<BOOL>(dwRollback), GenericExecuteMessageHandler, hPipe, &bundleRestart);
2928 + hr = BundlePackageEngineExecutePackage(&executeAction, pCache, pVariables, fRollback, fCacheAvailable, GenericExecuteMessageHandler, hPipe, &bundleRestart);
2929 ExitOnFailure(hr, "Failed to execute BUNDLE package.");
2930
2931 LExit:
src/burn/engine/exeengine.cpp
+21 -45
@@ -7,11 +7,6 @@ static HRESULT DetectArpEntry(
7 __out BOOTSTRAPPER_PACKAGE_STATE* pPackageState,
8 __out_opt LPWSTR* psczQuietUninstallString
9 );
10 -static HRESULT ParseArpUninstallString(
11 - __in_z LPCWSTR wzArpUninstallString,
12 - __inout LPWSTR* psczExecutablePath,
13 - __inout LPWSTR* psczArguments
14 - );
10
11 // function definitions
12
@@ -435,20 +430,20 @@ extern "C" HRESULT ExeEngineExecutePackage(
430 LPWSTR sczUserArgsObfuscated = NULL;
431 LPWSTR sczCommandObfuscated = NULL;
432 LPWSTR sczArpUninstallString = NULL;
438 - LPWSTR sczArpArguments = NULL;
433 + int argcArp = 0;
434 + LPWSTR* argvArp = NULL;
435 BOOTSTRAPPER_PACKAGE_STATE applyState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN;
436 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
437 DWORD dwExitCode = 0;
438 BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage;
439 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload;
444 - LPCWSTR wzUninstallArguments = pPackage->Exe.sczUninstallArguments;
440
441 if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType &&
442 (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action ||
443 BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->exePackage.action && fRollback))
444 {
445 hr = DetectArpEntry(pPackage, &applyState, &sczArpUninstallString);
451 - ExitOnFailure(hr, "Failed to query ArpEntry for uninstall.");
446 + ExitOnFailure(hr, "Failed to query ArpEntry for %hs.", BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action ? "uninstall" : "install");
447
448 if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == applyState && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action)
449 {
@@ -487,9 +482,14 @@ extern "C" HRESULT ExeEngineExecutePackage(
482 {
483 ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null.");
484
490 - hr = ParseArpUninstallString(sczArpUninstallString, &sczExecutablePath, &sczArpArguments);
485 + hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp);
486 ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString);
487
488 + ExitOnNull(argcArp, hr, E_INVALIDARG, "QuietUninstallString must contain an executable path.");
489 +
490 + hr = StrAllocString(&sczExecutablePath, argvArp[0], 0);
491 + ExitOnFailure(hr, "Failed to copy executable path.");
492 +
493 if (pPackage->fPerMachine)
494 {
495 hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath);
@@ -503,8 +503,6 @@ extern "C" HRESULT ExeEngineExecutePackage(
503
504 hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory);
505 ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath);
506 -
507 - wzUninstallArguments = sczArpArguments;
506 }
507 else
508 {
@@ -528,7 +526,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
526 break;
527
528 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
531 - wzArguments = wzUninstallArguments;
529 + wzArguments = pPackage->Exe.sczUninstallArguments;
530 break;
531
532 case BOOTSTRAPPER_ACTION_STATE_REPAIR:
@@ -583,6 +581,12 @@ extern "C" HRESULT ExeEngineExecutePackage(
581 hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath);
582 ExitOnFailure(hr, "Failed to allocate base command.");
583
584 + for (int i = 1; i < argcArp; ++i)
585 + {
586 + hr = AppAppendCommandLineArgument(&sczBaseCommand, argvArp[i]);
587 + ExitOnFailure(hr, "Failed to append argument from ARP.");
588 + }
589 +
590 if (pPackage->Exe.fBundle)
591 {
592 hr = StrAllocConcat(&sczBaseCommand, L" -norestart", 0);
@@ -655,7 +659,11 @@ LExit:
659 ReleaseStr(sczUserArgsObfuscated);
660 ReleaseStr(sczCommandObfuscated);
661 ReleaseStr(sczArpUninstallString);
658 - ReleaseStr(sczArpArguments);
662 +
663 + if (argvArp)
664 + {
665 + AppFreeCommandLineArgs(argvArp);
666 + }
667
668 ReleaseFileHandle(hExecutableFile);
669
@@ -1097,35 +1105,3 @@ LExit:
1105
1106 return hr;
1107 }
1100 -
1101 -static HRESULT ParseArpUninstallString(
1102 - __in_z LPCWSTR wzArpUninstallString,
1103 - __inout LPWSTR* psczExecutablePath,
1104 - __inout LPWSTR* psczArguments
1105 - )
1106 -{
1107 - HRESULT hr = S_OK;
1108 - int argc = 0;
1109 - LPWSTR* argv = NULL;
1110 -
1111 - hr = AppParseCommandLine(wzArpUninstallString, &argc, &argv);
1112 - ExitOnFailure(hr, "Failed to parse uninstall string as command line: %ls.", wzArpUninstallString);
1113 - ExitOnNull(argc, hr, E_INVALIDARG, "Uninstall string must contain an executable path.");
1114 -
1115 - hr = StrAllocString(psczExecutablePath, argv[0], 0);
1116 - ExitOnFailure(hr, "Failed to copy executable path for ArpCommand.");
1117 -
1118 - for (int i = 1; i < argc; ++i)
1119 - {
1120 - hr = AppAppendCommandLineArgument(psczArguments, argv[i]);
1121 - ExitOnFailure(hr, "Failed to append argument for ArpCommand.");
1122 - }
1123 -
1124 -LExit:
1125 - if (argv)
1126 - {
1127 - AppFreeCommandLineArgs(argv);
1128 - }
1129 -
1130 - return hr;
1131 -}
src/burn/engine/package.h
+1
@@ -321,6 +321,7 @@ typedef struct _BURN_PACKAGE
321 struct
322 {
323 LPWSTR sczBundleId;
324 + LPWSTR sczArpKeyPath;
325 VERUTIL_VERSION* pVersion;
326 LPWSTR sczRegistrationKey;
327 LPWSTR sczInstallArguments;
src/burn/engine/plan.cpp
+8 -4
@@ -2805,13 +2805,16 @@ static BURN_CACHE_PACKAGE_TYPE GetCachePackageType(
2805 case BOOTSTRAPPER_ACTION_STATE_NONE:
2806 break;
2807 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
2808 - if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type ||
2809 - BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType)
2808 + if (BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType)
2809 {
2811 - // Bundle and non-ArpEntry Exe packages require the package for all operations (even uninstall).
2812 - // TODO: bundles could theoretically use package cache.
2810 + // non-ArpEntry Exe packages require the package for all operations (even uninstall).
2811 cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED;
2812 }
2813 + else if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type)
2814 + {
2815 + // Bundle packages prefer the cache but can fallback to the ARP registration.
2816 + cachePackageType = BURN_CACHE_PACKAGE_TYPE_OPTIONAL;
2817 + }
2818 else
2819 {
2820 // The other package types can uninstall without the original package.
@@ -2823,6 +2826,7 @@ static BURN_CACHE_PACKAGE_TYPE GetCachePackageType(
2826 case BOOTSTRAPPER_ACTION_STATE_REPAIR: __fallthrough;
2827 case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: __fallthrough;
2828 default:
2829 + // TODO: bundles could theoretically use package cache.
2830 cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED;
2831 break;
2832 }
src/burn/test/BurnUnitTest/PlanTest.cpp
+121 -2
@@ -654,10 +654,10 @@ namespace Bootstrapper
654 fRollback = FALSE;
655 dwIndex = 0;
656 ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1);
657 - ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_REQUIRED);
657 + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_OPTIONAL);
658 ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageA");
659 ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 6);
660 - ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_REQUIRED);
660 + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB", TRUE, BURN_CACHE_PACKAGE_TYPE_REQUIRED, BURN_CACHE_PACKAGE_TYPE_OPTIONAL);
661 ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageB");
662 Assert::Equal(dwIndex, pPlan->cCacheActions);
663
@@ -824,6 +824,125 @@ namespace Bootstrapper
824 ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageB", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT);
825 }
826
827 + [Fact]
828 + void MultipleBundlePackageUninstallTest()
829 + {
830 + HRESULT hr = S_OK;
831 + BURN_ENGINE_STATE engineState = { };
832 + BURN_ENGINE_STATE* pEngineState = &engineState;
833 + BURN_PLAN* pPlan = &engineState.plan;
834 +
835 + InitializeEngineStateForCorePlan(wzMultipleBundlePackageManifestFileName, pEngineState);
836 + DetectAttachedContainerAsAttached(pEngineState);
837 + DetectPackagesAsPresentAndCached(pEngineState);
838 +
839 + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL);
840 + NativeAssert::Succeeded(hr, "CorePlan failed");
841 +
842 + Assert::Equal<DWORD>(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action);
843 + NativeAssert::StringEqual(L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", pPlan->wzBundleId);
844 + NativeAssert::StringEqual(L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", pPlan->wzBundleProviderKey);
845 + Assert::Equal<BOOL>(FALSE, pPlan->fEnabledForwardCompatibleBundle);
846 + Assert::Equal<BOOL>(TRUE, pPlan->fPerMachine);
847 + Assert::Equal<BOOL>(TRUE, pPlan->fCanAffectMachineState);
848 + Assert::Equal<BOOL>(FALSE, pPlan->fDisableRollback);
849 + Assert::Equal<BOOL>(FALSE, pPlan->fDisallowRemoval);
850 + Assert::Equal<BOOL>(FALSE, pPlan->fDowngrade);
851 + Assert::Equal<DWORD>(BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE | BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_PROVIDER_KEY, pPlan->dwRegistrationOperations);
852 +
853 + BOOL fRollback = FALSE;
854 + DWORD dwIndex = 0;
855 + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}");
856 + Assert::Equal(dwIndex, pPlan->cRegistrationActions);
857 +
858 + fRollback = TRUE;
859 + dwIndex = 0;
860 + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}");
861 + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions);
862 +
863 + fRollback = FALSE;
864 + dwIndex = 0;
865 + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1);
866 + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB", FALSE, BURN_CACHE_PACKAGE_TYPE_OPTIONAL, BURN_CACHE_PACKAGE_TYPE_REQUIRED);
867 + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageB");
868 + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 6);
869 + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA", FALSE, BURN_CACHE_PACKAGE_TYPE_OPTIONAL, BURN_CACHE_PACKAGE_TYPE_REQUIRED);
870 + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageA");
871 + Assert::Equal(dwIndex, pPlan->cCacheActions);
872 +
873 + fRollback = TRUE;
874 + dwIndex = 0;
875 + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions);
876 +
877 + Assert::Equal(78462280ull, pPlan->qwCacheSizeTotal);
878 +
879 + fRollback = FALSE;
880 + dwIndex = 0;
881 + DWORD dwExecuteCheckpointId = 2;
882 + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
883 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
884 + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"PackageB");
885 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
886 + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", unregisterActions1, 1);
887 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
888 + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageB", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}");
889 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
890 + dwExecuteCheckpointId += 1; // cache checkpoints
891 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
892 + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"PackageA");
893 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
894 + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", unregisterActions1, 1);
895 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
896 + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}");
897 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
898 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
899 + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
900 + Assert::Equal(dwIndex, pPlan->cExecuteActions);
901 +
902 + fRollback = TRUE;
903 + dwIndex = 0;
904 + dwExecuteCheckpointId = 2;
905 + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
906 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
907 + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", registerActions1, 1);
908 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
909 + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageB", BOOTSTRAPPER_ACTION_STATE_INSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}");
910 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
911 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
912 + dwExecuteCheckpointId += 1; // cache checkpoints
913 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
914 + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", registerActions1, 1);
915 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
916 + ValidateExecuteBundlePackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}");
917 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
918 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
919 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
920 + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
921 + Assert::Equal(dwIndex, pPlan->cRollbackActions);
922 +
923 + Assert::Equal(2ul, pPlan->cExecutePackagesTotal);
924 + Assert::Equal(4ul, pPlan->cOverallProgressTicksTotal);
925 +
926 + dwIndex = 0;
927 + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions);
928 +
929 + dwIndex = 0;
930 + ValidateCleanAction(pPlan, dwIndex++, L"PackageB");
931 + ValidateCleanAction(pPlan, dwIndex++, L"PackageA");
932 + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web");
933 + Assert::Equal(dwIndex, pPlan->cCleanActions);
934 +
935 + UINT uIndex = 0;
936 + ValidatePlannedProvider(pPlan, uIndex++, L"{35192ED0-C70A-49B2-9D12-3B1FA39B5E6F}", NULL);
937 + ValidatePlannedProvider(pPlan, uIndex++, L"{7506235A-7C59-4750-82C7-EB460A87ED3A}", NULL);
938 + ValidatePlannedProvider(pPlan, uIndex++, L"{B39CEE4D-CCD7-4797-BE3A-6613BD1DC4BE}", NULL);
939 + Assert::Equal(uIndex, pPlan->cPlannedProviders);
940 +
941 + Assert::Equal(3ul, pEngineState->packages.cPackages);
942 + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT);
943 + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageB", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT);
944 + }
945 +
946 [Fact]
947 void OrphanCompatiblePackageTest()
948 {
src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wixproj new
+19
@@ -0,0 +1,19 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <OutputType>Bundle</OutputType>
5 + <UpgradeCode>{1CD57FE5-A2FA-4A72-BEBA-74D9730C2C93}</UpgradeCode>
6 + </PropertyGroup>
7 + <ItemGroup>
8 + <Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" />
9 + </ItemGroup>
10 + <ItemGroup>
11 + <ProjectReference Include="..\PackageFail\PackageFail.wixproj" />
12 + <ProjectReference Include="..\..\BasicFunctionalityTests\BundleA\BundleA.wixproj" />
13 + <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
14 + </ItemGroup>
15 + <ItemGroup>
16 + <PackageReference Include="WixToolset.Bal.wixext" />
17 + <PackageReference Include="WixToolset.NetFx.wixext" />
18 + </ItemGroup>
19 +</Project>
\ No newline at end of file
src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wxs new
+15
@@ -0,0 +1,15 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +
3 +
4 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal">
5 + <Fragment>
6 + <Variable Name="FAILWHENDEFERRED" bal:Overridable="yes" />
7 +
8 + <PackageGroup Id="BundlePackages">
9 + <MsiPackage Id="PackageFail" SourceFile="$(var.PackageFail.TargetPath)">
10 + <MsiProperty Name="WIXFAILWHENDEFERRED" Value="[FAILWHENDEFERRED]" />
11 + </MsiPackage>
12 + <BundlePackage Id="PackageA" SourceFile="$(var.BundleA.TargetPath)" />
13 + </PackageGroup>
14 + </Fragment>
15 +</Wix>
src/test/burn/TestData/BundlePackageTests/PackageFail/PackageFail.wixproj new
+12
@@ -0,0 +1,12 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <UpgradeCode>{2674BFB2-634D-42DB-A1EC-C2CD148A2328}</UpgradeCode>
5 + </PropertyGroup>
6 + <ItemGroup>
7 + <Compile Include="..\..\Templates\PackageFail.wxs" Link="PackageFail.wxs" />
8 + </ItemGroup>
9 + <ItemGroup>
10 + <PackageReference Include="WixToolset.Util.wixext" />
11 + </ItemGroup>
12 +</Project>
\ No newline at end of file
src/test/burn/WixTestTools/BundleVerifier.cs
+9 -2
@@ -158,14 +158,21 @@ namespace WixTestTools
158 }
159 }
160
161 - public void VerifyPackageIsCached(string packageId, bool cached = true)
161 + public string GetPackageEntryPointCachePath(string packageId)
162 {
163 using var wixOutput = WixOutput.Read(this.BundlePdb);
164 var intermediate = Intermediate.Load(wixOutput);
165 var section = intermediate.Sections.Single();
166 var packageSymbol = section.Symbols.OfType<WixBundlePackageSymbol>().Single(p => p.Id.Id == packageId);
167 + var packagePayloadSymbol = section.Symbols.OfType<WixBundlePayloadSymbol>().Single(p => p.Id.Id == packageSymbol.PayloadRef);
168 var cachePath = this.GetPackageCachePathForCacheId(packageSymbol.CacheId, packageSymbol.PerMachine == true);
168 - Assert.Equal(cached, Directory.Exists(cachePath));
169 + return Path.Combine(cachePath, packagePayloadSymbol.Name);
170 + }
171 +
172 + public void VerifyPackageIsCached(string packageId, bool cached = true)
173 + {
174 + var entryPointCachePath = this.GetPackageEntryPointCachePath(packageId);
175 + Assert.Equal(cached, File.Exists(entryPointCachePath));
176 }
177
178 public void VerifyPackageProviderRemoved(string packageId)
src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs
+112
@@ -5,6 +5,7 @@ namespace WixToolsetTest.BurnE2E
5 using System;
6 using System.IO;
7 using WixTestTools;
8 + using WixToolset.Mba.Core;
9 using Xunit;
10 using Xunit.Abstractions;
11
@@ -137,5 +138,116 @@ namespace WixToolsetTest.BurnE2E
138 upgradeBundlePackageBundlev1.VerifyUnregisteredAndRemovedFromPackageCache();
139 bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache();
140 }
141 +
142 + [RuntimeFact]
143 + public void CanRecacheAndReinstallBundlePackageOnUninstallRollback()
144 + {
145 + var packageFail = this.CreatePackageInstaller("PackageFail");
146 + var packageA = this.CreatePackageInstaller(@"..\BasicFunctionalityTests\PackageA");
147 + var bundleA = this.CreateBundleInstaller(@"..\BasicFunctionalityTests\BundleA");
148 + var bundlePackageUninstallFailureBundle = this.CreateBundleInstaller("BundlePackageUninstallFailureBundle");
149 + var testBAController = this.CreateTestBAController();
150 + var bundleASelfCachedPath = bundleA.GetExpectedCachedBundlePath();
151 + var bundleAEmbeddedCachedPath = bundlePackageUninstallFailureBundle.GetPackageEntryPointCachePath("PackageA");
152 +
153 + packageA.VerifyInstalled(false);
154 + packageFail.VerifyInstalled(false);
155 + bundleA.VerifyUnregisteredAndRemovedFromPackageCache();
156 +
157 + testBAController.SetPackageRequestedCacheType("PackageA", BOOTSTRAPPER_CACHE_TYPE.Remove);
158 +
159 + var installLogPath = bundlePackageUninstallFailureBundle.Install();
160 + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache();
161 + packageFail.VerifyInstalled(true);
162 + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1);
163 +
164 + Assert.False(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
165 + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
166 +
167 + testBAController.ResetPackageStates("PackageA");
168 + testBAController.SetAllowAcquireAfterValidationFailure();
169 +
170 + var uninstallLogPath = bundlePackageUninstallFailureBundle.Uninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "FAILWHENDEFERRED=1");
171 + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache();
172 + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1);
173 + packageFail.VerifyInstalled(true);
174 +
175 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, "TESTBA: OnCachePackageNonVitalValidationFailure() - id: PackageA, default: None, requested: Acquire"));
176 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
177 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
178 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
179 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
180 + }
181 +
182 + [RuntimeFact]
183 + public void CanReinstallBundlePackageOnUninstallRollback()
184 + {
185 + var packageFail = this.CreatePackageInstaller("PackageFail");
186 + var packageA = this.CreatePackageInstaller(@"..\BasicFunctionalityTests\PackageA");
187 + var bundleA = this.CreateBundleInstaller(@"..\BasicFunctionalityTests\BundleA");
188 + var bundlePackageUninstallFailureBundle = this.CreateBundleInstaller("BundlePackageUninstallFailureBundle");
189 + var bundleASelfCachedPath = bundleA.GetExpectedCachedBundlePath();
190 + var bundleAEmbeddedCachedPath = bundlePackageUninstallFailureBundle.GetPackageEntryPointCachePath("PackageA");
191 +
192 + packageA.VerifyInstalled(false);
193 + packageFail.VerifyInstalled(false);
194 + bundleA.VerifyUnregisteredAndRemovedFromPackageCache();
195 +
196 + var installLogPath = bundlePackageUninstallFailureBundle.Install();
197 + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache();
198 + packageFail.VerifyInstalled(true);
199 + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1);
200 +
201 + Assert.False(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
202 + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
203 +
204 + var uninstallLogPath = bundlePackageUninstallFailureBundle.Uninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "FAILWHENDEFERRED=1");
205 + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache();
206 + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1);
207 + packageFail.VerifyInstalled(true);
208 +
209 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, "TESTBA: OnCachePackageNonVitalValidationFailure() - id: PackageA"));
210 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
211 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
212 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
213 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
214 + }
215 +
216 + [RuntimeFact]
217 + public void CanSkipReinstallBundlePackageOnUninstallRollback()
218 + {
219 + var packageFail = this.CreatePackageInstaller("PackageFail");
220 + var packageA = this.CreatePackageInstaller(@"..\BasicFunctionalityTests\PackageA");
221 + var bundleA = this.CreateBundleInstaller(@"..\BasicFunctionalityTests\BundleA");
222 + var bundlePackageUninstallFailureBundle = this.CreateBundleInstaller("BundlePackageUninstallFailureBundle");
223 + var testBAController = this.CreateTestBAController();
224 + var bundleASelfCachedPath = bundleA.GetExpectedCachedBundlePath();
225 + var bundleAEmbeddedCachedPath = bundlePackageUninstallFailureBundle.GetPackageEntryPointCachePath("PackageA");
226 +
227 + packageA.VerifyInstalled(false);
228 + packageFail.VerifyInstalled(false);
229 + bundleA.VerifyUnregisteredAndRemovedFromPackageCache();
230 +
231 + testBAController.SetPackageRequestedCacheType("PackageA", BOOTSTRAPPER_CACHE_TYPE.Remove);
232 +
233 + var installLogPath = bundlePackageUninstallFailureBundle.Install();
234 + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache();
235 + packageFail.VerifyInstalled(true);
236 + bundleA.VerifyRegisteredAndInPackageCache(expectedSystemComponent: 1);
237 +
238 + Assert.False(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
239 + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"Applying execute package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
240 +
241 + var uninstallLogPath = bundlePackageUninstallFailureBundle.Uninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "FAILWHENDEFERRED=1");
242 + bundlePackageUninstallFailureBundle.VerifyRegisteredAndInPackageCache();
243 + bundleA.VerifyUnregisteredAndRemovedFromPackageCache();
244 + packageFail.VerifyInstalled(true);
245 +
246 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, "TESTBA: OnCachePackageNonVitalValidationFailure() - id: PackageA, default: None, requested: None"));
247 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
248 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying execute package: PackageA, action: Uninstall, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
249 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleASelfCachedPath}"), bundleASelfCachedPath);
250 + Assert.False(LogVerifier.MessageInLogFile(uninstallLogPath, $"Applying rollback package: PackageA, action: Install, path: {bundleAEmbeddedCachedPath}"), bundleAEmbeddedCachedPath);
251 + }
252 }
253 }