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
}