aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2022-06-14 15:10:07 -0500
committerSean Hall <r.sean.hall@gmail.com>2022-06-14 21:51:14 -0500
commit98c369a92891244bde76448ae4a2b623b3ab394c (patch)
tree398105d02a888ba41b426e84965375e491d79bb7
parentdea657295df261bb0e3e4d620eeae321531e3a11 (diff)
downloadwix-98c369a92891244bde76448ae4a2b623b3ab394c.tar.gz
wix-98c369a92891244bde76448ae4a2b623b3ab394c.tar.bz2
wix-98c369a92891244bde76448ae4a2b623b3ab394c.zip
Allow BundlePackage to fallback to QuietUninstallString to uninstall.
Partial implementation of 6756
-rw-r--r--src/burn/engine/apply.cpp2
-rw-r--r--src/burn/engine/bundlepackageengine.cpp123
-rw-r--r--src/burn/engine/bundlepackageengine.h1
-rw-r--r--src/burn/engine/elevation.cpp13
-rw-r--r--src/burn/engine/exeengine.cpp66
-rw-r--r--src/burn/engine/package.h1
-rw-r--r--src/burn/engine/plan.cpp12
-rw-r--r--src/burn/test/BurnUnitTest/PlanTest.cpp123
-rw-r--r--src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wixproj19
-rw-r--r--src/test/burn/TestData/BundlePackageTests/BundlePackageUninstallFailureBundle/BundlePackageUninstallFailureBundle.wxs15
-rw-r--r--src/test/burn/TestData/BundlePackageTests/PackageFail/PackageFail.wixproj12
-rw-r--r--src/test/burn/WixTestTools/BundleVerifier.cs11
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs112
13 files changed, 449 insertions, 61 deletions
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(
2781 } 2781 }
2782 else 2782 else
2783 { 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."); 2785 ExitOnFailure(hrExecute, "Failed to configure per-user BUNDLE package.");
2786 } 2786 }
2787 2787
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(
18 __in BURN_CACHE* pCache, 18 __in BURN_CACHE* pCache,
19 __in BURN_VARIABLES* pVariables, 19 __in BURN_VARIABLES* pVariables,
20 __in BOOL fRollback, 20 __in BOOL fRollback,
21 __in BOOL fCacheAvailable,
21 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, 22 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
22 __in LPVOID pvContext, 23 __in LPVOID pvContext,
23 __in BOOTSTRAPPER_ACTION_STATE action, 24 __in BOOTSTRAPPER_ACTION_STATE action,
@@ -30,6 +31,11 @@ static HRESULT ExecuteBundle(
30 __in_z_opt LPCWSTR wzEngineWorkingDirectory, 31 __in_z_opt LPCWSTR wzEngineWorkingDirectory,
31 __out BOOTSTRAPPER_APPLY_RESTART* pRestart 32 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
32 ); 33 );
34static HRESULT DetectArpEntry(
35 __in BURN_PACKAGE* pPackage,
36 __out BOOL* pfRegistered,
37 __out LPWSTR* psczQuietUninstallString
38 );
33static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType( 39static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType(
34 __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType 40 __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType
35 ); 41 );
@@ -197,6 +203,7 @@ extern "C" void BundlePackageEnginePackageUninitialize(
197 ) 203 )
198{ 204{
199 ReleaseStr(pPackage->Bundle.sczBundleId); 205 ReleaseStr(pPackage->Bundle.sczBundleId);
206 ReleaseStr(pPackage->Bundle.sczArpKeyPath);
200 ReleaseVerutilVersion(pPackage->Bundle.pVersion); 207 ReleaseVerutilVersion(pPackage->Bundle.pVersion);
201 ReleaseStr(pPackage->Bundle.sczRegistrationKey); 208 ReleaseStr(pPackage->Bundle.sczRegistrationKey);
202 ReleaseStr(pPackage->Bundle.sczInstallArguments); 209 ReleaseStr(pPackage->Bundle.sczInstallArguments);
@@ -600,6 +607,7 @@ extern "C" HRESULT BundlePackageEngineExecutePackage(
600 __in BURN_CACHE* pCache, 607 __in BURN_CACHE* pCache,
601 __in BURN_VARIABLES* pVariables, 608 __in BURN_VARIABLES* pVariables,
602 __in BOOL fRollback, 609 __in BOOL fRollback,
610 __in BOOL fCacheAvailable,
603 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, 611 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
604 __in LPVOID pvContext, 612 __in LPVOID pvContext,
605 __out BOOTSTRAPPER_APPLY_RESTART* pRestart 613 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
@@ -613,7 +621,7 @@ extern "C" HRESULT BundlePackageEngineExecutePackage(
613 BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_CHAIN_PACKAGE; 621 BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_CHAIN_PACKAGE;
614 BURN_PACKAGE* pPackage = pExecuteAction->bundlePackage.pPackage; 622 BURN_PACKAGE* pPackage = pExecuteAction->bundlePackage.pPackage;
615 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);
617} 625}
618 626
619extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle( 627extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
@@ -635,7 +643,7 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
635 BOOTSTRAPPER_RELATION_TYPE relationType = ConvertRelationType(pRelatedBundle->planRelationType); 643 BOOTSTRAPPER_RELATION_TYPE relationType = ConvertRelationType(pRelatedBundle->planRelationType);
636 BURN_PACKAGE* pPackage = &pRelatedBundle->package; 644 BURN_PACKAGE* pPackage = &pRelatedBundle->package;
637 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);
639} 647}
640 648
641extern "C" void BundlePackageEngineUpdateInstallRegistrationState( 649extern "C" void BundlePackageEngineUpdateInstallRegistrationState(
@@ -678,10 +686,10 @@ static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback(
678 BOOTSTRAPPER_RELATION_TYPE relationType = RelatedBundleConvertRelationType(pBundle->relationType); 686 BOOTSTRAPPER_RELATION_TYPE relationType = RelatedBundleConvertRelationType(pBundle->relationType);
679 BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext; 687 BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext;
680 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))
682 { 691 {
683 Assert(BOOTSTRAPPER_RELATION_UPGRADE == relationType); 692 Assert(BOOTSTRAPPER_RELATION_UPGRADE == relationType);
684 Assert(pPackage->Bundle.fWin64 == (REG_KEY_64BIT == pBundle->regBitness));
685 693
686 pContext->fSelfFound = TRUE; 694 pContext->fSelfFound = TRUE;
687 } 695 }
@@ -727,6 +735,7 @@ static HRESULT ExecuteBundle(
727 __in BURN_CACHE* pCache, 735 __in BURN_CACHE* pCache,
728 __in BURN_VARIABLES* pVariables, 736 __in BURN_VARIABLES* pVariables,
729 __in BOOL fRollback, 737 __in BOOL fRollback,
738 __in BOOL fCacheAvailable,
730 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, 739 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
731 __in LPVOID pvContext, 740 __in LPVOID pvContext,
732 __in BOOTSTRAPPER_ACTION_STATE action, 741 __in BOOTSTRAPPER_ACTION_STATE action,
@@ -749,6 +758,10 @@ static HRESULT ExecuteBundle(
749 LPWSTR sczUserArgs = NULL; 758 LPWSTR sczUserArgs = NULL;
750 LPWSTR sczUserArgsObfuscated = NULL; 759 LPWSTR sczUserArgsObfuscated = NULL;
751 LPWSTR sczCommandObfuscated = NULL; 760 LPWSTR sczCommandObfuscated = NULL;
761 LPWSTR sczArpUninstallString = NULL;
762 int argcArp = 0;
763 LPWSTR* argvArp = NULL;
764 BOOL fRegistered = FALSE;
752 HANDLE hExecutableFile = INVALID_HANDLE_VALUE; 765 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
753 STARTUPINFOW si = { }; 766 STARTUPINFOW si = { };
754 PROCESS_INFORMATION pi = { }; 767 PROCESS_INFORMATION pi = { };
@@ -772,6 +785,51 @@ static HRESULT ExecuteBundle(
772 hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); 785 hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory);
773 ExitOnFailure(hr, "Failed to get cached path for related bundle: %ls", pPackage->sczId); 786 ExitOnFailure(hr, "Failed to get cached path for related bundle: %ls", pPackage->sczId);
774 } 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 }
775 else 833 else
776 { 834 {
777 // get cached executable path 835 // get cached executable path
@@ -859,6 +917,12 @@ static HRESULT ExecuteBundle(
859 hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath); 917 hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath);
860 ExitOnFailure(hr, "Failed to allocate base command."); 918 ExitOnFailure(hr, "Failed to allocate base command.");
861 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
862 if (!fRunEmbedded) 926 if (!fRunEmbedded)
863 { 927 {
864 hr = StrAllocConcat(&sczBaseCommand, L" -quiet", 0); 928 hr = StrAllocConcat(&sczBaseCommand, L" -quiet", 0);
@@ -962,6 +1026,12 @@ LExit:
962 StrSecureZeroFreeString(sczUserArgs); 1026 StrSecureZeroFreeString(sczUserArgs);
963 ReleaseStr(sczUserArgsObfuscated); 1027 ReleaseStr(sczUserArgsObfuscated);
964 ReleaseStr(sczCommandObfuscated); 1028 ReleaseStr(sczCommandObfuscated);
1029 ReleaseStr(sczArpUninstallString);
1030
1031 if (argvArp)
1032 {
1033 AppFreeCommandLineArgs(argvArp);
1034 }
965 1035
966 ReleaseHandle(pi.hThread); 1036 ReleaseHandle(pi.hThread);
967 ReleaseHandle(pi.hProcess); 1037 ReleaseHandle(pi.hProcess);
@@ -974,6 +1044,51 @@ LExit:
974 return hr; 1044 return hr;
975} 1045}
976 1046
1047static 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
1086LExit:
1087 ReleaseRegKey(hKey);
1088
1089 return hr;
1090}
1091
977static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType( 1092static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType(
978 __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType 1093 __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE relationType
979 ) 1094 )
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(
53 __in BURN_CACHE* pCache, 53 __in BURN_CACHE* pCache,
54 __in BURN_VARIABLES* pVariables, 54 __in BURN_VARIABLES* pVariables,
55 __in BOOL fRollback, 55 __in BOOL fRollback,
56 __in BOOL fCacheAvailable,
56 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, 57 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
57 __in LPVOID pvContext, 58 __in LPVOID pvContext,
58 __out BOOTSTRAPPER_APPLY_RESTART* pRestart 59 __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(
907 hr = BuffWriteNumber(&pbData, &cbData, fRollback); 907 hr = BuffWriteNumber(&pbData, &cbData, fRollback);
908 ExitOnFailure(hr, "Failed to write rollback."); 908 ExitOnFailure(hr, "Failed to write rollback.");
909 909
910 hr = BuffWriteNumber(&pbData, &cbData, SUCCEEDED(pExecuteAction->bundlePackage.pPackage->hrCacheResult));
911 ExitOnFailure(hr, "Failed to write fCacheAvailable.");
912
910 hr = BuffWriteString(&pbData, &cbData, pExecuteAction->bundlePackage.sczParent); 913 hr = BuffWriteString(&pbData, &cbData, pExecuteAction->bundlePackage.sczParent);
911 ExitOnFailure(hr, "Failed to write the parent to the message buffer."); 914 ExitOnFailure(hr, "Failed to write the parent to the message buffer.");
912 915
@@ -2855,7 +2858,8 @@ static HRESULT OnExecuteBundlePackage(
2855 HRESULT hr = S_OK; 2858 HRESULT hr = S_OK;
2856 SIZE_T iData = 0; 2859 SIZE_T iData = 0;
2857 LPWSTR sczPackage = NULL; 2860 LPWSTR sczPackage = NULL;
2858 DWORD dwRollback = 0; 2861 BOOL fRollback = FALSE;
2862 BOOL fCacheAvailable = FALSE;
2859 BURN_EXECUTE_ACTION executeAction = { }; 2863 BURN_EXECUTE_ACTION executeAction = { };
2860 LPWSTR sczIgnoreDependencies = NULL; 2864 LPWSTR sczIgnoreDependencies = NULL;
2861 LPWSTR sczAncestors = NULL; 2865 LPWSTR sczAncestors = NULL;
@@ -2871,9 +2875,12 @@ static HRESULT OnExecuteBundlePackage(
2871 hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.bundlePackage.action); 2875 hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.bundlePackage.action);
2872 ExitOnFailure(hr, "Failed to read action."); 2876 ExitOnFailure(hr, "Failed to read action.");
2873 2877
2874 hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback); 2878 hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback);
2875 ExitOnFailure(hr, "Failed to read rollback."); 2879 ExitOnFailure(hr, "Failed to read rollback.");
2876 2880
2881 hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fCacheAvailable);
2882 ExitOnFailure(hr, "Failed to read fCacheAvailable.");
2883
2877 hr = BuffReadString(pbData, cbData, &iData, &executeAction.bundlePackage.sczParent); 2884 hr = BuffReadString(pbData, cbData, &iData, &executeAction.bundlePackage.sczParent);
2878 ExitOnFailure(hr, "Failed to read the parent."); 2885 ExitOnFailure(hr, "Failed to read the parent.");
2879 2886
@@ -2918,7 +2925,7 @@ static HRESULT OnExecuteBundlePackage(
2918 } 2925 }
2919 2926
2920 // Execute BUNDLE package. 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);
2922 ExitOnFailure(hr, "Failed to execute BUNDLE package."); 2929 ExitOnFailure(hr, "Failed to execute BUNDLE package.");
2923 2930
2924LExit: 2931LExit:
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(
7 __out BOOTSTRAPPER_PACKAGE_STATE* pPackageState, 7 __out BOOTSTRAPPER_PACKAGE_STATE* pPackageState,
8 __out_opt LPWSTR* psczQuietUninstallString 8 __out_opt LPWSTR* psczQuietUninstallString
9 ); 9 );
10static HRESULT ParseArpUninstallString(
11 __in_z LPCWSTR wzArpUninstallString,
12 __inout LPWSTR* psczExecutablePath,
13 __inout LPWSTR* psczArguments
14 );
15 10
16// function definitions 11// function definitions
17 12
@@ -435,20 +430,20 @@ extern "C" HRESULT ExeEngineExecutePackage(
435 LPWSTR sczUserArgsObfuscated = NULL; 430 LPWSTR sczUserArgsObfuscated = NULL;
436 LPWSTR sczCommandObfuscated = NULL; 431 LPWSTR sczCommandObfuscated = NULL;
437 LPWSTR sczArpUninstallString = NULL; 432 LPWSTR sczArpUninstallString = NULL;
438 LPWSTR sczArpArguments = NULL; 433 int argcArp = 0;
434 LPWSTR* argvArp = NULL;
439 BOOTSTRAPPER_PACKAGE_STATE applyState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; 435 BOOTSTRAPPER_PACKAGE_STATE applyState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN;
440 HANDLE hExecutableFile = INVALID_HANDLE_VALUE; 436 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
441 DWORD dwExitCode = 0; 437 DWORD dwExitCode = 0;
442 BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; 438 BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage;
443 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; 439 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload;
444 LPCWSTR wzUninstallArguments = pPackage->Exe.sczUninstallArguments;
445 440
446 if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType && 441 if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType &&
447 (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action || 442 (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action ||
448 BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->exePackage.action && fRollback)) 443 BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->exePackage.action && fRollback))
449 { 444 {
450 hr = DetectArpEntry(pPackage, &applyState, &sczArpUninstallString); 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");
452 447
453 if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == applyState && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action) 448 if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == applyState && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action)
454 { 449 {
@@ -487,9 +482,14 @@ extern "C" HRESULT ExeEngineExecutePackage(
487 { 482 {
488 ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null."); 483 ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null.");
489 484
490 hr = ParseArpUninstallString(sczArpUninstallString, &sczExecutablePath, &sczArpArguments); 485 hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp);
491 ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString); 486 ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString);
492 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) 493 if (pPackage->fPerMachine)
494 { 494 {
495 hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath); 495 hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath);
@@ -503,8 +503,6 @@ extern "C" HRESULT ExeEngineExecutePackage(
503 503
504 hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); 504 hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory);
505 ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath); 505 ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath);
506
507 wzUninstallArguments = sczArpArguments;
508 } 506 }
509 else 507 else
510 { 508 {
@@ -528,7 +526,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
528 break; 526 break;
529 527
530 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: 528 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
531 wzArguments = wzUninstallArguments; 529 wzArguments = pPackage->Exe.sczUninstallArguments;
532 break; 530 break;
533 531
534 case BOOTSTRAPPER_ACTION_STATE_REPAIR: 532 case BOOTSTRAPPER_ACTION_STATE_REPAIR:
@@ -583,6 +581,12 @@ extern "C" HRESULT ExeEngineExecutePackage(
583 hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath); 581 hr = StrAllocFormatted(&sczBaseCommand, L"\"%ls\"", sczExecutablePath);
584 ExitOnFailure(hr, "Failed to allocate base command."); 582 ExitOnFailure(hr, "Failed to allocate base command.");
585 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
586 if (pPackage->Exe.fBundle) 590 if (pPackage->Exe.fBundle)
587 { 591 {
588 hr = StrAllocConcat(&sczBaseCommand, L" -norestart", 0); 592 hr = StrAllocConcat(&sczBaseCommand, L" -norestart", 0);
@@ -655,7 +659,11 @@ LExit:
655 ReleaseStr(sczUserArgsObfuscated); 659 ReleaseStr(sczUserArgsObfuscated);
656 ReleaseStr(sczCommandObfuscated); 660 ReleaseStr(sczCommandObfuscated);
657 ReleaseStr(sczArpUninstallString); 661 ReleaseStr(sczArpUninstallString);
658 ReleaseStr(sczArpArguments); 662
663 if (argvArp)
664 {
665 AppFreeCommandLineArgs(argvArp);
666 }
659 667
660 ReleaseFileHandle(hExecutableFile); 668 ReleaseFileHandle(hExecutableFile);
661 669
@@ -1097,35 +1105,3 @@ LExit:
1097 1105
1098 return hr; 1106 return hr;
1099} 1107}
1100
1101static 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
1124LExit:
1125 if (argv)
1126 {
1127 AppFreeCommandLineArgs(argv);
1128 }
1129
1130 return hr;
1131}
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
321 struct 321 struct
322 { 322 {
323 LPWSTR sczBundleId; 323 LPWSTR sczBundleId;
324 LPWSTR sczArpKeyPath;
324 VERUTIL_VERSION* pVersion; 325 VERUTIL_VERSION* pVersion;
325 LPWSTR sczRegistrationKey; 326 LPWSTR sczRegistrationKey;
326 LPWSTR sczInstallArguments; 327 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(
2805 case BOOTSTRAPPER_ACTION_STATE_NONE: 2805 case BOOTSTRAPPER_ACTION_STATE_NONE:
2806 break; 2806 break;
2807 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: 2807 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
2808 if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type || 2808 if (BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType)
2809 BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType)
2810 { 2809 {
2811 // Bundle and non-ArpEntry Exe packages require the package for all operations (even uninstall). 2810 // non-ArpEntry Exe packages require the package for all operations (even uninstall).
2812 // TODO: bundles could theoretically use package cache.
2813 cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED; 2811 cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED;
2814 } 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 }
2815 else 2818 else
2816 { 2819 {
2817 // The other package types can uninstall without the original package. 2820 // The other package types can uninstall without the original package.
@@ -2823,6 +2826,7 @@ static BURN_CACHE_PACKAGE_TYPE GetCachePackageType(
2823 case BOOTSTRAPPER_ACTION_STATE_REPAIR: __fallthrough; 2826 case BOOTSTRAPPER_ACTION_STATE_REPAIR: __fallthrough;
2824 case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: __fallthrough; 2827 case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: __fallthrough;
2825 default: 2828 default:
2829 // TODO: bundles could theoretically use package cache.
2826 cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED; 2830 cachePackageType = BURN_CACHE_PACKAGE_TYPE_REQUIRED;
2827 break; 2831 break;
2828 } 2832 }
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
654 fRollback = FALSE; 654 fRollback = FALSE;
655 dwIndex = 0; 655 dwIndex = 0;
656 ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); 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"); 658 ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageA");
659 ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 6); 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"); 661 ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++, L"PackageB");
662 Assert::Equal(dwIndex, pPlan->cCacheActions); 662 Assert::Equal(dwIndex, pPlan->cCacheActions);
663 663
@@ -825,6 +825,125 @@ namespace Bootstrapper
825 } 825 }
826 826
827 [Fact] 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]
828 void OrphanCompatiblePackageTest() 947 void OrphanCompatiblePackageTest()
829 { 948 {
830 HRESULT hr = S_OK; 949 HRESULT hr = S_OK;
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 @@
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
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 @@
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>
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 @@
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
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
158 } 158 }
159 } 159 }
160 160
161 public void VerifyPackageIsCached(string packageId, bool cached = true) 161 public string GetPackageEntryPointCachePath(string packageId)
162 { 162 {
163 using var wixOutput = WixOutput.Read(this.BundlePdb); 163 using var wixOutput = WixOutput.Read(this.BundlePdb);
164 var intermediate = Intermediate.Load(wixOutput); 164 var intermediate = Intermediate.Load(wixOutput);
165 var section = intermediate.Sections.Single(); 165 var section = intermediate.Sections.Single();
166 var packageSymbol = section.Symbols.OfType<WixBundlePackageSymbol>().Single(p => p.Id.Id == packageId); 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);
167 var cachePath = this.GetPackageCachePathForCacheId(packageSymbol.CacheId, packageSymbol.PerMachine == true); 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));
169 } 176 }
170 177
171 public void VerifyPackageProviderRemoved(string packageId) 178 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
5 using System; 5 using System;
6 using System.IO; 6 using System.IO;
7 using WixTestTools; 7 using WixTestTools;
8 using WixToolset.Mba.Core;
8 using Xunit; 9 using Xunit;
9 using Xunit.Abstractions; 10 using Xunit.Abstractions;
10 11
@@ -137,5 +138,116 @@ namespace WixToolsetTest.BurnE2E
137 upgradeBundlePackageBundlev1.VerifyUnregisteredAndRemovedFromPackageCache(); 138 upgradeBundlePackageBundlev1.VerifyUnregisteredAndRemovedFromPackageCache();
138 bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache(); 139 bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache();
139 } 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 }
140 } 252 }
141} 253}