aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperEngine.h3
-rw-r--r--src/api/burn/WixToolset.Mba.Core/IBootstrapperEngine.cs5
-rw-r--r--src/burn/engine/apply.cpp13
-rw-r--r--src/burn/engine/core.cpp28
-rw-r--r--src/burn/engine/elevation.cpp17
-rw-r--r--src/burn/engine/engine.mc21
-rw-r--r--src/burn/engine/logging.cpp2
-rw-r--r--src/burn/engine/msiengine.cpp2
-rw-r--r--src/burn/engine/plan.cpp84
-rw-r--r--src/burn/engine/plan.h1
-rw-r--r--src/burn/engine/userexperience.cpp2
-rw-r--r--src/burn/test/BurnUnitTest/PlanTest.cpp78
-rw-r--r--src/test/burn/WixTestTools/BundleInstaller.cs30
-rw-r--r--src/test/burn/WixTestTools/MSIExec.cs5
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/ForwardCompatibleBundleTests.cs46
15 files changed, 284 insertions, 53 deletions
diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperEngine.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperEngine.h
index d45c7b2a..cdb01330 100644
--- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperEngine.h
+++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperEngine.h
@@ -15,13 +15,12 @@ extern "C" {
15 15
16static const HRESULT E_SUSPECTED_AV_INTERFERENCE = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIX, 2000); 16static const HRESULT E_SUSPECTED_AV_INTERFERENCE = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIX, 2000);
17 17
18// Note that ordering of the enumeration values is important.
19// Some code paths use < or > comparisions and simply reording values will break those comparisons.
20enum BOOTSTRAPPER_ACTION 18enum BOOTSTRAPPER_ACTION
21{ 19{
22 BOOTSTRAPPER_ACTION_UNKNOWN, 20 BOOTSTRAPPER_ACTION_UNKNOWN,
23 BOOTSTRAPPER_ACTION_HELP, 21 BOOTSTRAPPER_ACTION_HELP,
24 BOOTSTRAPPER_ACTION_LAYOUT, 22 BOOTSTRAPPER_ACTION_LAYOUT,
23 BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL,
25 BOOTSTRAPPER_ACTION_UNINSTALL, 24 BOOTSTRAPPER_ACTION_UNINSTALL,
26 BOOTSTRAPPER_ACTION_CACHE, 25 BOOTSTRAPPER_ACTION_CACHE,
27 BOOTSTRAPPER_ACTION_INSTALL, 26 BOOTSTRAPPER_ACTION_INSTALL,
diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperEngine.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperEngine.cs
index 8ead0919..ebea6c4f 100644
--- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperEngine.cs
+++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperEngine.cs
@@ -341,6 +341,11 @@ namespace WixToolset.Mba.Core
341 /// <summary> 341 /// <summary>
342 /// 342 ///
343 /// </summary> 343 /// </summary>
344 UnsafeUninstall,
345
346 /// <summary>
347 ///
348 /// </summary>
344 Uninstall, 349 Uninstall,
345 350
346 /// <summary> 351 /// <summary>
diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp
index 73f8fc72..4e652768 100644
--- a/src/burn/engine/apply.cpp
+++ b/src/burn/engine/apply.cpp
@@ -505,8 +505,7 @@ extern "C" HRESULT ApplyUnregister(
505 505
506 registrationType = defaultRegistrationType; 506 registrationType = defaultRegistrationType;
507 507
508 hr = UserExperienceOnUnregisterBegin(&pEngineState->userExperience, &registrationType); 508 UserExperienceOnUnregisterBegin(&pEngineState->userExperience, &registrationType);
509 ExitOnRootFailure(hr, "BA aborted unregister begin.");
510 509
511 // Barring the special cases, if it was determined that we should keep the registration then 510 // Barring the special cases, if it was determined that we should keep the registration then
512 // do that, otherwise the resume mode is NONE and registration will be removed. 511 // do that, otherwise the resume mode is NONE and registration will be removed.
@@ -517,7 +516,7 @@ extern "C" HRESULT ApplyUnregister(
517 516
518 // If apply failed in any way and we're going to be keeping the bundle registered then 517 // If apply failed in any way and we're going to be keeping the bundle registered then
519 // execute any rollback dependency registration actions. 518 // execute any rollback dependency registration actions.
520 if (fFailed && BURN_RESUME_MODE_NONE < resumeMode) 519 if (fFailed && BURN_RESUME_MODE_NONE < resumeMode && !pEngineState->plan.fDisableRollback)
521 { 520 {
522 // Execute any rollback registration actions. 521 // Execute any rollback registration actions.
523 HRESULT hrRegistrationRollback = ExecuteDependentRegistrationActions(pEngineState->companionConnection.hPipe, &pEngineState->registration, pEngineState->plan.rgRollbackRegistrationActions, pEngineState->plan.cRollbackRegistrationActions); 522 HRESULT hrRegistrationRollback = ExecuteDependentRegistrationActions(pEngineState->companionConnection.hPipe, &pEngineState->registration, pEngineState->plan.rgRollbackRegistrationActions, pEngineState->plan.cRollbackRegistrationActions);
@@ -526,6 +525,14 @@ extern "C" HRESULT ApplyUnregister(
526 525
527 LogId(REPORT_STANDARD, MSG_SESSION_END, pEngineState->registration.sczRegistrationKey, LoggingResumeModeToString(resumeMode), LoggingRestartToString(restart), LoggingBoolToString(pEngineState->registration.fDisableResume), LoggingRegistrationTypeToString(defaultRegistrationType), LoggingRegistrationTypeToString(registrationType)); 526 LogId(REPORT_STANDARD, MSG_SESSION_END, pEngineState->registration.sczRegistrationKey, LoggingResumeModeToString(resumeMode), LoggingRestartToString(restart), LoggingBoolToString(pEngineState->registration.fDisableResume), LoggingRegistrationTypeToString(defaultRegistrationType), LoggingRegistrationTypeToString(registrationType));
528 527
528 if (BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pEngineState->plan.action)
529 {
530 registrationType = BOOTSTRAPPER_REGISTRATION_TYPE_NONE;
531 resumeMode = BURN_RESUME_MODE_NONE;
532
533 LogId(REPORT_STANDARD, MSG_UNSAFE_SESSION_END);
534 }
535
529 if (pEngineState->registration.fPerMachine) 536 if (pEngineState->registration.fPerMachine)
530 { 537 {
531 hr = ElevationSessionEnd(pEngineState->companionConnection.hPipe, resumeMode, restart, pEngineState->registration.fDetectedForeignProviderKeyBundleId, registrationType); 538 hr = ElevationSessionEnd(pEngineState->companionConnection.hPipe, resumeMode, restart, pEngineState->registration.fDetectedForeignProviderKeyBundleId, registrationType);
diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp
index 8fac7bd0..9d5364a4 100644
--- a/src/burn/engine/core.cpp
+++ b/src/burn/engine/core.cpp
@@ -488,7 +488,7 @@ extern "C" HRESULT CorePlan(
488 pEngineState->plan.pPayloads = &pEngineState->payloads; 488 pEngineState->plan.pPayloads = &pEngineState->payloads;
489 pEngineState->plan.wzBundleId = pEngineState->registration.sczId; 489 pEngineState->plan.wzBundleId = pEngineState->registration.sczId;
490 pEngineState->plan.wzBundleProviderKey = pEngineState->registration.sczId; 490 pEngineState->plan.wzBundleProviderKey = pEngineState->registration.sczId;
491 pEngineState->plan.fDisableRollback = pEngineState->fDisableRollback; 491 pEngineState->plan.fDisableRollback = pEngineState->fDisableRollback || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pEngineState->plan.action;
492 pEngineState->plan.fBundleAlreadyRegistered = pEngineState->registration.fInstalled; 492 pEngineState->plan.fBundleAlreadyRegistered = pEngineState->registration.fInstalled;
493 493
494 hr = PlanSetVariables(action, &pEngineState->variables); 494 hr = PlanSetVariables(action, &pEngineState->variables);
@@ -756,6 +756,14 @@ extern "C" HRESULT CoreApply(
756 } 756 }
757 } 757 }
758 758
759 if (BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pEngineState->plan.action)
760 {
761 fSuspend = FALSE;
762 restart = BOOTSTRAPPER_APPLY_RESTART_NONE;
763
764 LogId(REPORT_STANDARD, MSG_UNSAFE_APPLY_COMPLETED);
765 }
766
759 if (fSuspend || BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart) 767 if (fSuspend || BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart)
760 { 768 {
761 // Leave cache alone. 769 // Leave cache alone.
@@ -773,7 +781,7 @@ extern "C" HRESULT CoreApply(
773 } 781 }
774 782
775LExit: 783LExit:
776 if (fRollbackCache) 784 if (fRollbackCache && !pEngineState->plan.fDisableRollback)
777 { 785 {
778 ApplyCacheRollback(&pEngineState->userExperience, &pEngineState->plan, pEngineState->companionConnection.hCachePipe, &applyContext); 786 ApplyCacheRollback(&pEngineState->userExperience, &pEngineState->plan, pEngineState->companionConnection.hCachePipe, &applyContext);
779 } 787 }
@@ -978,6 +986,9 @@ static HRESULT CoreRecreateCommandLine(
978 case BOOTSTRAPPER_ACTION_UNINSTALL: 986 case BOOTSTRAPPER_ACTION_UNINSTALL:
979 hr = StrAllocConcat(psczCommandLine, L" /uninstall", 0); 987 hr = StrAllocConcat(psczCommandLine, L" /uninstall", 0);
980 break; 988 break;
989 case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL:
990 hr = StrAllocConcat(psczCommandLine, L" /unsafeuninstall", 0);
991 break;
981 } 992 }
982 ExitOnFailure(hr, "Failed to append action state to command-line"); 993 ExitOnFailure(hr, "Failed to append action state to command-line");
983 994
@@ -1414,6 +1425,13 @@ extern "C" HRESULT CoreParseCommandLine(
1414 ExitOnFailure(hr, "Failed to copy path for layout directory."); 1425 ExitOnFailure(hr, "Failed to copy path for layout directory.");
1415 } 1426 }
1416 } 1427 }
1428 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"unsafeuninstall", -1))
1429 {
1430 if (BOOTSTRAPPER_ACTION_HELP != pCommand->action)
1431 {
1432 pCommand->action = BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL;
1433 }
1434 }
1417 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"uninstall", -1)) 1435 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"uninstall", -1))
1418 { 1436 {
1419 if (BOOTSTRAPPER_ACTION_HELP != pCommand->action) 1437 if (BOOTSTRAPPER_ACTION_HELP != pCommand->action)
@@ -2207,7 +2225,7 @@ static void LogPackages(
2207 __in const BOOTSTRAPPER_ACTION action 2225 __in const BOOTSTRAPPER_ACTION action
2208 ) 2226 )
2209{ 2227{
2210 BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == action; 2228 BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == action;
2211 2229
2212 if (pUpgradeBundlePackage) 2230 if (pUpgradeBundlePackage)
2213 { 2231 {
@@ -2286,8 +2304,8 @@ static void LogPackages(
2286 } 2304 }
2287 } 2305 }
2288 2306
2289 // Display related bundles last if caching, installing, modifying, or repairing. 2307 // Display related bundles last if not uninstalling.
2290 if (BOOTSTRAPPER_ACTION_UNINSTALL < action) 2308 if (!fUninstalling)
2291 { 2309 {
2292 LogRelatedBundles(pRelatedBundles, FALSE); 2310 LogRelatedBundles(pRelatedBundles, FALSE);
2293 } 2311 }
diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp
index 86b65cf9..d12a151a 100644
--- a/src/burn/engine/elevation.cpp
+++ b/src/burn/engine/elevation.cpp
@@ -2289,8 +2289,21 @@ static HRESULT OnApplyInitialize(
2289 2289
2290 LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_STARTING); 2290 LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_STARTING);
2291 2291
2292 BOOTSTRAPPER_ACTION action = static_cast<BOOTSTRAPPER_ACTION>(dwAction); 2292 SRP_ACTION restoreAction = SRP_ACTION_UNKNOWN;
2293 SRP_ACTION restoreAction = (BOOTSTRAPPER_ACTION_INSTALL == action) ? SRP_ACTION_INSTALL : (BOOTSTRAPPER_ACTION_UNINSTALL == action) ? SRP_ACTION_UNINSTALL : SRP_ACTION_MODIFY; 2293 switch (static_cast<BOOTSTRAPPER_ACTION>(dwAction))
2294 {
2295 case BOOTSTRAPPER_ACTION_INSTALL:
2296 restoreAction = SRP_ACTION_INSTALL;
2297 break;
2298 case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL: __fallthrough;
2299 case BOOTSTRAPPER_ACTION_UNINSTALL:
2300 restoreAction = SRP_ACTION_UNINSTALL;
2301 break;
2302 default:
2303 restoreAction = SRP_ACTION_MODIFY;
2304 break;
2305 }
2306
2294 hrStatus = hr = SrpCreateRestorePoint(sczBundleName, restoreAction); 2307 hrStatus = hr = SrpCreateRestorePoint(sczBundleName, restoreAction);
2295 if (SUCCEEDED(hr)) 2308 if (SUCCEEDED(hr))
2296 { 2309 {
diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc
index 675a5644..4425af12 100644
--- a/src/burn/engine/engine.mc
+++ b/src/burn/engine/engine.mc
@@ -492,6 +492,13 @@ Language=English
492Planned rollback boundary: '%1!ls!', vital: %2!hs!, transaction: %3!hs! (default: %4!hs!) 492Planned rollback boundary: '%1!ls!', vital: %2!hs!, transaction: %3!hs! (default: %4!hs!)
493. 493.
494 494
495MessageId=223
496Severity=Success
497SymbolicName=MSG_PLAN_NOT_SKIPPED_DUE_TO_DEPENDENTS
498Language=English
499Ignoring bundle dependents due to action UnsafeUninstall...
500.
501
495MessageId=299 502MessageId=299
496Severity=Success 503Severity=Success
497SymbolicName=MSG_PLAN_COMPLETE 504SymbolicName=MSG_PLAN_COMPLETE
@@ -947,6 +954,20 @@ Language=English
947 package: %1!ls!, install registration state: %2!hs!, cache registration state: %3!hs! 954 package: %1!ls!, install registration state: %2!hs!, cache registration state: %3!hs!
948. 955.
949 956
957MessageId=375
958Severity=Success
959SymbolicName=MSG_UNSAFE_APPLY_COMPLETED
960Language=English
961Ignoring suspend and restart values due to action UnsafeUninstall...
962.
963
964MessageId=376
965Severity=Success
966SymbolicName=MSG_UNSAFE_SESSION_END
967Language=English
968Ignoring resume and registration values due to action UnsafeUninstall...
969.
970
950MessageId=380 971MessageId=380
951Severity=Warning 972Severity=Warning
952SymbolicName=MSG_APPLY_SKIPPED 973SymbolicName=MSG_APPLY_SKIPPED
diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp
index c5dd0ed8..07fc9ef3 100644
--- a/src/burn/engine/logging.cpp
+++ b/src/burn/engine/logging.cpp
@@ -354,6 +354,8 @@ extern "C" LPCSTR LoggingBurnActionToString(
354 return "Layout"; 354 return "Layout";
355 case BOOTSTRAPPER_ACTION_CACHE: 355 case BOOTSTRAPPER_ACTION_CACHE:
356 return "Cache"; 356 return "Cache";
357 case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL:
358 return "UnsafeUninstall";
357 case BOOTSTRAPPER_ACTION_UNINSTALL: 359 case BOOTSTRAPPER_ACTION_UNINSTALL:
358 return "Uninstall"; 360 return "Uninstall";
359 case BOOTSTRAPPER_ACTION_INSTALL: 361 case BOOTSTRAPPER_ACTION_INSTALL:
diff --git a/src/burn/engine/msiengine.cpp b/src/burn/engine/msiengine.cpp
index c27dd8c2..d306f3e0 100644
--- a/src/burn/engine/msiengine.cpp
+++ b/src/burn/engine/msiengine.cpp
@@ -834,7 +834,7 @@ extern "C" HRESULT MsiEnginePlanInitializePackage(
834 { 834 {
835 Assert(BURN_PACKAGE_TYPE_MSI == pPackage->compatiblePackage.type); 835 Assert(BURN_PACKAGE_TYPE_MSI == pPackage->compatiblePackage.type);
836 836
837 pPackage->compatiblePackage.fDefaultRequested = BOOTSTRAPPER_ACTION_UNINSTALL == overallAction; 837 pPackage->compatiblePackage.fDefaultRequested = BOOTSTRAPPER_ACTION_UNINSTALL == overallAction || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == overallAction;
838 pPackage->compatiblePackage.fRequested = pPackage->compatiblePackage.fDefaultRequested; 838 pPackage->compatiblePackage.fRequested = pPackage->compatiblePackage.fDefaultRequested;
839 839
840 hr = UserExperienceOnPlanCompatibleMsiPackageBegin(pUserExperience, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->compatiblePackage.Msi.pVersion, &pPackage->compatiblePackage.fRequested); 840 hr = UserExperienceOnPlanCompatibleMsiPackageBegin(pUserExperience, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->compatiblePackage.Msi.pVersion, &pPackage->compatiblePackage.fRequested);
diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp
index f1fb87b8..49dd83f4 100644
--- a/src/burn/engine/plan.cpp
+++ b/src/burn/engine/plan.cpp
@@ -361,13 +361,15 @@ extern "C" HRESULT PlanDefaultPackageRequestState(
361 else if (BOOTSTRAPPER_RELATION_PATCH == relationType && BURN_PACKAGE_TYPE_MSP == packageType) 361 else if (BOOTSTRAPPER_RELATION_PATCH == relationType && BURN_PACKAGE_TYPE_MSP == packageType)
362 { 362 {
363 // For patch related bundles, only install a patch if currently absent during install, modify, or repair. 363 // For patch related bundles, only install a patch if currently absent during install, modify, or repair.
364 if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == currentState && BOOTSTRAPPER_ACTION_INSTALL <= action) 364 if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT != currentState)
365 { 365 {
366 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; 366 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE;
367 } 367 }
368 else 368 else if (BOOTSTRAPPER_ACTION_INSTALL == action ||
369 BOOTSTRAPPER_ACTION_MODIFY == action ||
370 BOOTSTRAPPER_ACTION_REPAIR == action)
369 { 371 {
370 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; 372 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
371 } 373 }
372 } 374 }
373 else // pick the best option for the action state and install condition. 375 else // pick the best option for the action state and install condition.
@@ -375,7 +377,7 @@ extern "C" HRESULT PlanDefaultPackageRequestState(
375 hr = GetActionDefaultRequestState(action, currentState, &defaultRequestState); 377 hr = GetActionDefaultRequestState(action, currentState, &defaultRequestState);
376 ExitOnFailure(hr, "Failed to get default request state for action."); 378 ExitOnFailure(hr, "Failed to get default request state for action.");
377 379
378 if (BOOTSTRAPPER_ACTION_UNINSTALL != action) 380 if (BOOTSTRAPPER_ACTION_UNINSTALL != action && BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL != action)
379 { 381 {
380 // If we're not doing an uninstall, use the install condition 382 // If we're not doing an uninstall, use the install condition
381 // to determine whether to use the default request state or make the package absent. 383 // to determine whether to use the default request state or make the package absent.
@@ -485,7 +487,8 @@ extern "C" HRESULT PlanForwardCompatibleBundles(
485 { 487 {
486 fRecommendIgnore = FALSE; 488 fRecommendIgnore = FALSE;
487 } 489 }
488 else if (BOOTSTRAPPER_ACTION_UNINSTALL == action || 490 else if (BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == action ||
491 BOOTSTRAPPER_ACTION_UNINSTALL == action ||
489 BOOTSTRAPPER_ACTION_MODIFY == action || 492 BOOTSTRAPPER_ACTION_MODIFY == action ||
490 BOOTSTRAPPER_ACTION_REPAIR == action) 493 BOOTSTRAPPER_ACTION_REPAIR == action)
491 { 494 {
@@ -552,10 +555,10 @@ extern "C" HRESULT PlanRegistration(
552 HRESULT hr = S_OK; 555 HRESULT hr = S_OK;
553 STRINGDICT_HANDLE sdBundleDependents = NULL; 556 STRINGDICT_HANDLE sdBundleDependents = NULL;
554 STRINGDICT_HANDLE sdIgnoreDependents = NULL; 557 STRINGDICT_HANDLE sdIgnoreDependents = NULL;
558 BOOL fDependentBlocksUninstall = FALSE;
555 559
556 pPlan->fCanAffectMachineState = TRUE; // register the bundle since we're modifying machine state. 560 pPlan->fCanAffectMachineState = TRUE; // register the bundle since we're modifying machine state.
557 pPlan->fDisallowRemoval = FALSE; // by default the bundle can be planned to be removed 561 pPlan->fDisallowRemoval = FALSE; // by default the bundle can be planned to be removed
558 pPlan->fIgnoreAllDependents = pDependencies->fIgnoreAllDependents;
559 562
560 // Ensure the bundle is cached if not running from the cache. 563 // Ensure the bundle is cached if not running from the cache.
561 if (!CacheBundleRunningFromCache(pPlan->pCache)) 564 if (!CacheBundleRunningFromCache(pPlan->pCache))
@@ -563,7 +566,7 @@ extern "C" HRESULT PlanRegistration(
563 pPlan->dwRegistrationOperations |= BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE; 566 pPlan->dwRegistrationOperations |= BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE;
564 } 567 }
565 568
566 if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) 569 if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action)
567 { 570 {
568 // If our provider key was not owned by a different bundle, 571 // If our provider key was not owned by a different bundle,
569 // then plan to write our provider key registration to "fix it" if broken 572 // then plan to write our provider key registration to "fix it" if broken
@@ -588,7 +591,7 @@ extern "C" HRESULT PlanRegistration(
588 ExitOnFailure(hr, "Failed to add self-dependent to ignore dependents."); 591 ExitOnFailure(hr, "Failed to add self-dependent to ignore dependents.");
589 } 592 }
590 593
591 if (!pPlan->fIgnoreAllDependents) 594 if (!pDependencies->fIgnoreAllDependents)
592 { 595 {
593 // If we are not doing an upgrade, we check to see if there are still dependents on us and if so we skip planning. 596 // If we are not doing an upgrade, we check to see if there are still dependents on us and if so we skip planning.
594 // However, when being upgraded, we always execute our uninstall because a newer version of us is probably 597 // However, when being upgraded, we always execute our uninstall because a newer version of us is probably
@@ -641,10 +644,9 @@ extern "C" HRESULT PlanRegistration(
641 hr = S_OK; 644 hr = S_OK;
642 645
643 // TODO: callback to the BA and let it have the option to ignore this dependent? 646 // TODO: callback to the BA and let it have the option to ignore this dependent?
644 if (!pPlan->fDisallowRemoval) 647 if (!fDependentBlocksUninstall)
645 { 648 {
646 pPlan->fDisallowRemoval = TRUE; // ensure the registration stays 649 fDependentBlocksUninstall = TRUE;
647 *pfContinuePlanning = FALSE; // skip the rest of planning.
648 650
649 LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DUE_TO_DEPENDENTS); 651 LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DUE_TO_DEPENDENTS);
650 } 652 }
@@ -653,6 +655,20 @@ extern "C" HRESULT PlanRegistration(
653 } 655 }
654 ExitOnFailure(hr, "Failed to check for remaining dependents during planning."); 656 ExitOnFailure(hr, "Failed to check for remaining dependents during planning.");
655 } 657 }
658
659 if (fDependentBlocksUninstall)
660 {
661 if (BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action)
662 {
663 fDependentBlocksUninstall = FALSE;
664 LogId(REPORT_STANDARD, MSG_PLAN_NOT_SKIPPED_DUE_TO_DEPENDENTS);
665 }
666 else
667 {
668 pPlan->fDisallowRemoval = TRUE; // ensure the registration stays
669 *pfContinuePlanning = FALSE; // skip the rest of planning.
670 }
671 }
656 } 672 }
657 } 673 }
658 } 674 }
@@ -776,11 +792,12 @@ static HRESULT PlanPackagesHelper(
776 HRESULT hr = S_OK; 792 HRESULT hr = S_OK;
777 BOOL fBundlePerMachine = pPlan->fPerMachine; // bundle is per-machine if plan starts per-machine. 793 BOOL fBundlePerMachine = pPlan->fPerMachine; // bundle is per-machine if plan starts per-machine.
778 BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; 794 BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL;
795 BOOL fReverseOrder = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
779 796
780 // Initialize the packages. 797 // Initialize the packages.
781 for (DWORD i = 0; i < cPackages; ++i) 798 for (DWORD i = 0; i < cPackages; ++i)
782 { 799 {
783 DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; 800 DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
784 BURN_PACKAGE* pPackage = rgPackages + iPackage; 801 BURN_PACKAGE* pPackage = rgPackages + iPackage;
785 802
786 hr = InitializePackage(pPlan, pUX, pVariables, pPackage); 803 hr = InitializePackage(pPlan, pUX, pVariables, pPackage);
@@ -790,7 +807,7 @@ static HRESULT PlanPackagesHelper(
790 // Initialize the patch targets after all packages, since they could rely on the requested state of packages that are after the patch's package in the chain. 807 // Initialize the patch targets after all packages, since they could rely on the requested state of packages that are after the patch's package in the chain.
791 for (DWORD i = 0; i < cPackages; ++i) 808 for (DWORD i = 0; i < cPackages; ++i)
792 { 809 {
793 DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; 810 DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
794 BURN_PACKAGE* pPackage = rgPackages + iPackage; 811 BURN_PACKAGE* pPackage = rgPackages + iPackage;
795 812
796 if (BURN_PACKAGE_TYPE_MSP == pPackage->type) 813 if (BURN_PACKAGE_TYPE_MSP == pPackage->type)
@@ -803,7 +820,7 @@ static HRESULT PlanPackagesHelper(
803 // Plan the packages. 820 // Plan the packages.
804 for (DWORD i = 0; i < cPackages; ++i) 821 for (DWORD i = 0; i < cPackages; ++i)
805 { 822 {
806 DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; 823 DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
807 BURN_PACKAGE* pPackage = rgPackages + iPackage; 824 BURN_PACKAGE* pPackage = rgPackages + iPackage;
808 825
809 hr = ProcessPackage(fBundlePerMachine, pUX, pPlan, pPackage, pLog, pVariables, &pRollbackBoundary); 826 hr = ProcessPackage(fBundlePerMachine, pUX, pPlan, pPackage, pLog, pVariables, &pRollbackBoundary);
@@ -825,7 +842,7 @@ static HRESULT PlanPackagesHelper(
825 // Plan clean up of packages. 842 // Plan clean up of packages.
826 for (DWORD i = 0; i < cPackages; ++i) 843 for (DWORD i = 0; i < cPackages; ++i)
827 { 844 {
828 DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; 845 DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
829 BURN_PACKAGE* pPackage = rgPackages + iPackage; 846 BURN_PACKAGE* pPackage = rgPackages + iPackage;
830 847
831 hr = PlanCleanPackage(pPlan, pPackage); 848 hr = PlanCleanPackage(pPlan, pPackage);
@@ -842,7 +859,7 @@ static HRESULT PlanPackagesHelper(
842 // Let the BA know the actions that were planned. 859 // Let the BA know the actions that were planned.
843 for (DWORD i = 0; i < cPackages; ++i) 860 for (DWORD i = 0; i < cPackages; ++i)
844 { 861 {
845 DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; 862 DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
846 BURN_PACKAGE* pPackage = rgPackages + iPackage; 863 BURN_PACKAGE* pPackage = rgPackages + iPackage;
847 864
848 UserExperienceOnPlannedPackage(pUX, pPackage->sczId, pPackage->execute, pPackage->rollback, pPackage->fPlannedCache, pPackage->fPlannedUncache); 865 UserExperienceOnPlannedPackage(pUX, pPackage->sczId, pPackage->execute, pPackage->rollback, pPackage->fPlannedCache, pPackage->fPlannedUncache);
@@ -931,8 +948,9 @@ static HRESULT ProcessPackage(
931{ 948{
932 HRESULT hr = S_OK; 949 HRESULT hr = S_OK;
933 BURN_ROLLBACK_BOUNDARY* pEffectiveRollbackBoundary = NULL; 950 BURN_ROLLBACK_BOUNDARY* pEffectiveRollbackBoundary = NULL;
951 BOOL fBackward = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
934 952
935 pEffectiveRollbackBoundary = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? pPackage->pRollbackBoundaryBackward : pPackage->pRollbackBoundaryForward; 953 pEffectiveRollbackBoundary = fBackward ? pPackage->pRollbackBoundaryBackward : pPackage->pRollbackBoundaryForward;
936 hr = ProcessPackageRollbackBoundary(pPlan, pUX, pLog, pVariables, pEffectiveRollbackBoundary, ppRollbackBoundary); 954 hr = ProcessPackageRollbackBoundary(pPlan, pUX, pLog, pVariables, pEffectiveRollbackBoundary, ppRollbackBoundary);
937 ExitOnFailure(hr, "Failed to process package rollback boundary."); 955 ExitOnFailure(hr, "Failed to process package rollback boundary.");
938 956
@@ -1205,6 +1223,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1205{ 1223{
1206 HRESULT hr = S_OK; 1224 HRESULT hr = S_OK;
1207 int nCompareResult = 0; 1225 int nCompareResult = 0;
1226 BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == action;
1208 1227
1209 // Never touch related bundles during Cache. 1228 // Never touch related bundles during Cache.
1210 if (BOOTSTRAPPER_ACTION_CACHE == action) 1229 if (BOOTSTRAPPER_ACTION_CACHE == action)
@@ -1215,7 +1234,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1215 switch (relatedBundleRelationType) 1234 switch (relatedBundleRelationType)
1216 { 1235 {
1217 case BOOTSTRAPPER_RELATION_UPGRADE: 1236 case BOOTSTRAPPER_RELATION_UPGRADE:
1218 if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && BOOTSTRAPPER_ACTION_UNINSTALL < action) 1237 if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && !fUninstalling)
1219 { 1238 {
1220 hr = VerCompareParsedVersions(pRegistrationVersion, pRelatedBundleVersion, &nCompareResult); 1239 hr = VerCompareParsedVersions(pRegistrationVersion, pRelatedBundleVersion, &nCompareResult);
1221 ExitOnFailure(hr, "Failed to compare bundle version '%ls' to related bundle version '%ls'", pRegistrationVersion ? pRegistrationVersion->sczVersion : NULL, pRelatedBundleVersion ? pRelatedBundleVersion->sczVersion : NULL); 1240 ExitOnFailure(hr, "Failed to compare bundle version '%ls' to related bundle version '%ls'", pRegistrationVersion ? pRegistrationVersion->sczVersion : NULL, pRelatedBundleVersion ? pRelatedBundleVersion->sczVersion : NULL);
@@ -1225,7 +1244,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1225 break; 1244 break;
1226 case BOOTSTRAPPER_RELATION_PATCH: __fallthrough; 1245 case BOOTSTRAPPER_RELATION_PATCH: __fallthrough;
1227 case BOOTSTRAPPER_RELATION_ADDON: 1246 case BOOTSTRAPPER_RELATION_ADDON:
1228 if (BOOTSTRAPPER_ACTION_UNINSTALL == action) 1247 if (fUninstalling)
1229 { 1248 {
1230 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT; 1249 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT;
1231 } 1250 }
@@ -1242,7 +1261,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1242 // Automatically repair dependent bundles to restore missing 1261 // Automatically repair dependent bundles to restore missing
1243 // packages after uninstall unless we're being upgraded with the 1262 // packages after uninstall unless we're being upgraded with the
1244 // assumption that upgrades are cumulative (as intended). 1263 // assumption that upgrades are cumulative (as intended).
1245 if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && BOOTSTRAPPER_ACTION_UNINSTALL == action) 1264 if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && fUninstalling)
1246 { 1265 {
1247 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR; 1266 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR;
1248 } 1267 }
@@ -1270,6 +1289,7 @@ extern "C" HRESULT PlanRelatedBundlesBegin(
1270 LPWSTR* rgsczAncestors = NULL; 1289 LPWSTR* rgsczAncestors = NULL;
1271 UINT cAncestors = 0; 1290 UINT cAncestors = 0;
1272 STRINGDICT_HANDLE sdAncestors = NULL; 1291 STRINGDICT_HANDLE sdAncestors = NULL;
1292 BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
1273 1293
1274 if (pPlan->pInternalCommand->sczAncestors) 1294 if (pPlan->pInternalCommand->sczAncestors)
1275 { 1295 {
@@ -1329,7 +1349,7 @@ extern "C" HRESULT PlanRelatedBundlesBegin(
1329 ExitOnRootFailure(hr, "BA aborted plan related bundle."); 1349 ExitOnRootFailure(hr, "BA aborted plan related bundle.");
1330 1350
1331 // If uninstalling and the dependent related bundle may be executed, ignore its provider key to allow for downgrades with ref-counting. 1351 // If uninstalling and the dependent related bundle may be executed, ignore its provider key to allow for downgrades with ref-counting.
1332 if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action && BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_REQUEST_STATE_NONE != pRelatedBundle->package.requested) 1352 if (fUninstalling && BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_REQUEST_STATE_NONE != pRelatedBundle->package.requested)
1333 { 1353 {
1334 if (0 < pRelatedBundle->package.cDependencyProviders) 1354 if (0 < pRelatedBundle->package.cDependencyProviders)
1335 { 1355 {
@@ -1361,6 +1381,9 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1361 HRESULT hr = S_OK; 1381 HRESULT hr = S_OK;
1362 LPWSTR sczIgnoreDependencies = NULL; 1382 LPWSTR sczIgnoreDependencies = NULL;
1363 STRINGDICT_HANDLE sdProviderKeys = NULL; 1383 STRINGDICT_HANDLE sdProviderKeys = NULL;
1384 BOOL fExecutingAnyPackage = FALSE;
1385 BOOL fInstallingAnyPackage = FALSE;
1386 BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
1364 1387
1365 // Get the list of dependencies to ignore to pass to related bundles. 1388 // Get the list of dependencies to ignore to pass to related bundles.
1366 hr = DependencyAllocIgnoreDependencies(pPlan, &sczIgnoreDependencies); 1389 hr = DependencyAllocIgnoreDependencies(pPlan, &sczIgnoreDependencies);
@@ -1369,9 +1392,6 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1369 hr = DictCreateStringList(&sdProviderKeys, pPlan->cExecuteActions, DICT_FLAG_CASEINSENSITIVE); 1392 hr = DictCreateStringList(&sdProviderKeys, pPlan->cExecuteActions, DICT_FLAG_CASEINSENSITIVE);
1370 ExitOnFailure(hr, "Failed to create dictionary for planned packages."); 1393 ExitOnFailure(hr, "Failed to create dictionary for planned packages.");
1371 1394
1372 BOOL fExecutingAnyPackage = FALSE;
1373 BOOL fInstallingAnyPackage = FALSE;
1374
1375 for (DWORD i = 0; i < pPlan->cExecuteActions; ++i) 1395 for (DWORD i = 0; i < pPlan->cExecuteActions; ++i)
1376 { 1396 {
1377 BOOTSTRAPPER_ACTION_STATE packageAction = BOOTSTRAPPER_ACTION_STATE_NONE; 1397 BOOTSTRAPPER_ACTION_STATE packageAction = BOOTSTRAPPER_ACTION_STATE_NONE;
@@ -1444,7 +1464,7 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1444 } 1464 }
1445 1465
1446 // For an uninstall, there is no need to repair dependent bundles if no packages are executing. 1466 // For an uninstall, there is no need to repair dependent bundles if no packages are executing.
1447 if (!fExecutingAnyPackage && BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_REQUEST_STATE_REPAIR == pRelatedBundle->package.requested && BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) 1467 if (!fExecutingAnyPackage && BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_REQUEST_STATE_REPAIR == pRelatedBundle->package.requested && fUninstalling)
1448 { 1468 {
1449 pRelatedBundle->package.requested = BOOTSTRAPPER_REQUEST_STATE_NONE; 1469 pRelatedBundle->package.requested = BOOTSTRAPPER_REQUEST_STATE_NONE;
1450 LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DEPENDENT_BUNDLE_REPAIR, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType)); 1470 LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DEPENDENT_BUNDLE_REPAIR, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType));
@@ -1457,7 +1477,7 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1457 ExitOnFailure(hr, "Failed to copy the list of dependencies to ignore."); 1477 ExitOnFailure(hr, "Failed to copy the list of dependencies to ignore.");
1458 1478
1459 // Uninstall addons and patches early in the chain, before other packages are uninstalled. 1479 // Uninstall addons and patches early in the chain, before other packages are uninstalled.
1460 if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) 1480 if (fUninstalling)
1461 { 1481 {
1462 pdwInsertIndex = &dwExecuteActionEarlyIndex; 1482 pdwInsertIndex = &dwExecuteActionEarlyIndex;
1463 } 1483 }
@@ -1475,7 +1495,7 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1475 ExitOnFailure(hr, "Failed to begin plan dependency actions to package: %ls", pRelatedBundle->package.sczId); 1495 ExitOnFailure(hr, "Failed to begin plan dependency actions to package: %ls", pRelatedBundle->package.sczId);
1476 1496
1477 // If uninstalling a related bundle, make sure the bundle is uninstalled after removing registration. 1497 // If uninstalling a related bundle, make sure the bundle is uninstalled after removing registration.
1478 if (pdwInsertIndex && BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) 1498 if (pdwInsertIndex && fUninstalling)
1479 { 1499 {
1480 ++(*pdwInsertIndex); 1500 ++(*pdwInsertIndex);
1481 } 1501 }
@@ -1599,9 +1619,10 @@ extern "C" HRESULT PlanCleanPackage(
1599 HRESULT hr = S_OK; 1619 HRESULT hr = S_OK;
1600 BOOL fPlanCleanPackage = FALSE; 1620 BOOL fPlanCleanPackage = FALSE;
1601 BURN_CLEAN_ACTION* pCleanAction = NULL; 1621 BURN_CLEAN_ACTION* pCleanAction = NULL;
1622 BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
1602 1623
1603 // The following is a complex set of logic that determines when a package should be cleaned from the cache. 1624 // The following is a complex set of logic that determines when a package should be cleaned from the cache.
1604 if (BOOTSTRAPPER_CACHE_TYPE_FORCE > pPackage->cacheType || BOOTSTRAPPER_ACTION_CACHE > pPlan->action) 1625 if (BOOTSTRAPPER_CACHE_TYPE_FORCE > pPackage->cacheType || fUninstalling)
1605 { 1626 {
1606 // The following are all different reasons why the package should be cleaned from the cache. 1627 // The following are all different reasons why the package should be cleaned from the cache.
1607 // The else-ifs are used to make the conditions easier to see (rather than have them combined 1628 // The else-ifs are used to make the conditions easier to see (rather than have them combined
@@ -1624,7 +1645,7 @@ extern "C" HRESULT PlanCleanPackage(
1624 { 1645 {
1625 fPlanCleanPackage = TRUE; 1646 fPlanCleanPackage = TRUE;
1626 } 1647 }
1627 else if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action && // uninstalling and 1648 else if (fUninstalling && // uninstalling and
1628 BOOTSTRAPPER_REQUEST_STATE_NONE == pPackage->requested && // requested do nothing (aka: default) and 1649 BOOTSTRAPPER_REQUEST_STATE_NONE == pPackage->requested && // requested do nothing (aka: default) and
1629 BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is still do nothing and 1650 BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is still do nothing and
1630 !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and 1651 !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and
@@ -2091,6 +2112,7 @@ static HRESULT GetActionDefaultRequestState(
2091 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR; 2112 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR;
2092 break; 2113 break;
2093 2114
2115 case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL: __fallthrough;
2094 case BOOTSTRAPPER_ACTION_UNINSTALL: 2116 case BOOTSTRAPPER_ACTION_UNINSTALL:
2095 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT; 2117 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT;
2096 break; 2118 break;
@@ -2671,7 +2693,7 @@ static BOOL ForceCache(
2671 BOOTSTRAPPER_REQUEST_STATE_CACHE < pPackage->requested; 2693 BOOTSTRAPPER_REQUEST_STATE_CACHE < pPackage->requested;
2672 case BOOTSTRAPPER_CACHE_TYPE_FORCE: 2694 case BOOTSTRAPPER_CACHE_TYPE_FORCE:
2673 // All packages that have cacheType set to force should be cached if the bundle is going to be present. 2695 // All packages that have cacheType set to force should be cached if the bundle is going to be present.
2674 return BOOTSTRAPPER_ACTION_UNINSTALL < pPlan->action; 2696 return BOOTSTRAPPER_ACTION_UNINSTALL != pPlan->action && BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL != pPlan->action;
2675 default: 2697 default:
2676 return FALSE; 2698 return FALSE;
2677 } 2699 }
diff --git a/src/burn/engine/plan.h b/src/burn/engine/plan.h
index 3dce8e5d..c0936970 100644
--- a/src/burn/engine/plan.h
+++ b/src/burn/engine/plan.h
@@ -248,7 +248,6 @@ typedef struct _BURN_PLAN
248 BOOL fDisallowRemoval; 248 BOOL fDisallowRemoval;
249 BOOL fDisableRollback; 249 BOOL fDisableRollback;
250 BOOL fAffectedMachineState; 250 BOOL fAffectedMachineState;
251 BOOL fIgnoreAllDependents;
252 LPWSTR sczLayoutDirectory; 251 LPWSTR sczLayoutDirectory;
253 BOOL fBundleAlreadyRegistered; 252 BOOL fBundleAlreadyRegistered;
254 253
diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp
index 59988bef..a2f33f80 100644
--- a/src/burn/engine/userexperience.cpp
+++ b/src/burn/engine/userexperience.cpp
@@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad(
104 args.pCommand = pCommand; 104 args.pCommand = pCommand;
105 args.pfnBootstrapperEngineProc = EngineForApplicationProc; 105 args.pfnBootstrapperEngineProc = EngineForApplicationProc;
106 args.pvBootstrapperEngineProcContext = pEngineContext; 106 args.pvBootstrapperEngineProcContext = pEngineContext;
107 args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 1, 10, 0); 107 args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 2, 22, 0);
108 108
109 results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); 109 results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS);
110 110
diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp
index 81484234..ba28713f 100644
--- a/src/burn/test/BurnUnitTest/PlanTest.cpp
+++ b/src/burn/test/BurnUnitTest/PlanTest.cpp
@@ -993,6 +993,84 @@ namespace Bootstrapper
993 } 993 }
994 994
995 [Fact] 995 [Fact]
996 void SingleMsiUnsafeUninstallTest()
997 {
998 HRESULT hr = S_OK;
999 BURN_ENGINE_STATE engineState = { };
1000 BURN_ENGINE_STATE* pEngineState = &engineState;
1001 BURN_PLAN* pPlan = &engineState.plan;
1002
1003 InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState);
1004 DetectPackagesAsPresentAndCached(pEngineState);
1005
1006 hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL);
1007 NativeAssert::Succeeded(hr, "CorePlan failed");
1008
1009 Assert::Equal<DWORD>(BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL, pPlan->action);
1010 Assert::Equal<BOOL>(TRUE, pPlan->fPerMachine);
1011 Assert::Equal<BOOL>(TRUE, pPlan->fDisableRollback);
1012
1013 BOOL fRollback = FALSE;
1014 DWORD dwIndex = 0;
1015 Assert::Equal(dwIndex, pPlan->cCacheActions);
1016
1017 fRollback = TRUE;
1018 dwIndex = 0;
1019 Assert::Equal(dwIndex, pPlan->cRollbackCacheActions);
1020
1021 Assert::Equal(0ull, pPlan->qwEstimatedSize);
1022 Assert::Equal(0ull, pPlan->qwCacheSizeTotal);
1023
1024 fRollback = FALSE;
1025 dwIndex = 0;
1026 DWORD dwExecuteCheckpointId = 1;
1027 ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
1028 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1029 ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", unregisterActions1, 1);
1030 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1031 ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", unregisterActions1, 1);
1032 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1033 ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, BOOTSTRAPPER_MSI_FILE_VERSIONING_MISSING_OR_OLDER, 0);
1034 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1035 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1036 ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
1037 Assert::Equal(dwIndex, pPlan->cExecuteActions);
1038
1039 fRollback = TRUE;
1040 dwIndex = 0;
1041 dwExecuteCheckpointId = 1;
1042 ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
1043 ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", registerActions1, 1);
1044 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1045 ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", registerActions1, 1);
1046 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1047 ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, BOOTSTRAPPER_MSI_FILE_VERSIONING_MISSING_OR_OLDER, 0);
1048 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1049 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1050 ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1051 ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
1052 Assert::Equal(dwIndex, pPlan->cRollbackActions);
1053
1054 Assert::Equal(1ul, pPlan->cExecutePackagesTotal);
1055 Assert::Equal(1ul, pPlan->cOverallProgressTicksTotal);
1056
1057 dwIndex = 0;
1058 Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions);
1059
1060 dwIndex = 0;
1061 ValidateCleanAction(pPlan, dwIndex++, L"PackageA");
1062 Assert::Equal(dwIndex, pPlan->cCleanActions);
1063
1064 UINT uIndex = 0;
1065 ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL);
1066 ValidatePlannedProvider(pPlan, uIndex++, L"{64633047-D172-4BBB-B202-64337D15C952}", NULL);
1067 Assert::Equal(uIndex, pPlan->cPlannedProviders);
1068
1069 Assert::Equal(1ul, pEngineState->packages.cPackages);
1070 ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT);
1071 }
1072
1073 [Fact]
996 void SlipstreamInstallTest() 1074 void SlipstreamInstallTest()
997 { 1075 {
998 HRESULT hr = S_OK; 1076 HRESULT hr = S_OK;
diff --git a/src/test/burn/WixTestTools/BundleInstaller.cs b/src/test/burn/WixTestTools/BundleInstaller.cs
index a49c4024..2b449ebf 100644
--- a/src/test/burn/WixTestTools/BundleInstaller.cs
+++ b/src/test/burn/WixTestTools/BundleInstaller.cs
@@ -3,6 +3,7 @@
3namespace WixTestTools 3namespace WixTestTools
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic;
6 using System.IO; 7 using System.IO;
7 using System.Text; 8 using System.Text;
8 9
@@ -131,6 +132,35 @@ namespace WixTestTools
131 } 132 }
132 133
133 /// <summary> 134 /// <summary>
135 /// Uninstalls the bundle unsafely with optional arguments.
136 /// </summary>
137 /// <param name="expectedExitCode">Expected exit code, defaults to success.</param>
138 /// <param name="arguments">Optional arguments to pass to the tool.</param>
139 /// <returns>Path to the generated log file.</returns>
140 public string UnsafeUninstall(int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments)
141 {
142 var newArgumentList = new List<string>();
143 newArgumentList.Add("-unsafeuninstall");
144 newArgumentList.AddRange(arguments);
145 return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Custom, newArgumentList.ToArray());
146 }
147
148 /// <summary>
149 /// Uninstalls the bundle unsafely at the given path with optional arguments.
150 /// </summary>
151 /// <param name="bundlePath">This should be the bundle in the package cache.</param>
152 /// <param name="expectedExitCode">Expected exit code, defaults to success.</param>
153 /// <param name="arguments">Optional arguments to pass to the tool.</param>
154 /// <returns>Path to the generated log file.</returns>
155 public string UnsafeUninstall(string bundlePath, int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments)
156 {
157 var newArgumentList = new List<string>();
158 newArgumentList.Add("-unsafeuninstall");
159 newArgumentList.AddRange(arguments);
160 return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Custom, newArgumentList.ToArray(), bundlePath: bundlePath);
161 }
162
163 /// <summary>
134 /// Executes the bundle with optional arguments. 164 /// Executes the bundle with optional arguments.
135 /// </summary> 165 /// </summary>
136 /// <param name="expectedExitCode">Expected exit code.</param> 166 /// <param name="expectedExitCode">Expected exit code.</param>
diff --git a/src/test/burn/WixTestTools/MSIExec.cs b/src/test/burn/WixTestTools/MSIExec.cs
index 8dce96cf..a10a48d6 100644
--- a/src/test/burn/WixTestTools/MSIExec.cs
+++ b/src/test/burn/WixTestTools/MSIExec.cs
@@ -697,6 +697,11 @@ namespace WixTestTools
697 /// Uninstalls the product as part of cleanup 697 /// Uninstalls the product as part of cleanup
698 /// </summary> 698 /// </summary>
699 Cleanup, 699 Cleanup,
700
701 /// <summary>
702 /// No action automatically added to arguments
703 /// </summary>
704 Custom,
700 } 705 }
701 706
702 /// <summary> 707 /// <summary>
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/ForwardCompatibleBundleTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/ForwardCompatibleBundleTests.cs
index eb649c86..357cf515 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/ForwardCompatibleBundleTests.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/ForwardCompatibleBundleTests.cs
@@ -18,6 +18,38 @@ namespace WixToolsetTest.BurnE2E
18 private const string V200 = "2.0.0.0"; 18 private const string V200 = "2.0.0.0";
19 19
20 [Fact] 20 [Fact]
21 public void CanIgnoreBundleDependentForUnsafeUninstall()
22 {
23 string providerId = BundleAProviderId;
24 string parent = "~BundleAv1";
25 string parentSwitch = String.Concat("-parent ", parent);
26
27 var packageAv1 = this.CreatePackageInstaller("PackageAv1");
28 var bundleAv1 = this.CreateBundleInstaller("BundleAv1");
29 var testBAController = this.CreateTestBAController();
30
31 packageAv1.VerifyInstalled(false);
32
33 // Install the v1 bundle with a parent.
34 bundleAv1.Install(arguments: parentSwitch);
35 bundleAv1.VerifyRegisteredAndInPackageCache();
36
37 packageAv1.VerifyInstalled(true);
38 Assert.True(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out var actualProviderVersion));
39 Assert.Equal(V100, actualProviderVersion);
40 Assert.True(BundleRegistration.DependencyDependentExists(providerId, parent));
41
42 // Cancel package B right away.
43 testBAController.SetPackageCancelExecuteAtProgress("PackageA", 1);
44
45 bundleAv1.UnsafeUninstall((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_USEREXIT);
46 bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache();
47
48 packageAv1.VerifyInstalled(true);
49 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
50 }
51
52 [Fact]
21 public void CanTrack1ForwardCompatibleDependentThroughMajorUpgrade() 53 public void CanTrack1ForwardCompatibleDependentThroughMajorUpgrade()
22 { 54 {
23 string providerId = BundleAProviderId; 55 string providerId = BundleAProviderId;
@@ -70,7 +102,7 @@ namespace WixToolsetTest.BurnE2E
70 102
71 packageAv1.VerifyInstalled(false); 103 packageAv1.VerifyInstalled(false);
72 packageAv2.VerifyInstalled(false); 104 packageAv2.VerifyInstalled(false);
73 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 105 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
74 } 106 }
75 107
76 [Fact] 108 [Fact]
@@ -116,7 +148,7 @@ namespace WixToolsetTest.BurnE2E
116 148
117 packageAv1.VerifyInstalled(false); 149 packageAv1.VerifyInstalled(false);
118 packageAv2.VerifyInstalled(false); 150 packageAv2.VerifyInstalled(false);
119 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 151 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
120 } 152 }
121 153
122 [Fact] 154 [Fact]
@@ -198,7 +230,7 @@ namespace WixToolsetTest.BurnE2E
198 230
199 packageAv1.VerifyInstalled(false); 231 packageAv1.VerifyInstalled(false);
200 packageAv2.VerifyInstalled(false); 232 packageAv2.VerifyInstalled(false);
201 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 233 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
202 } 234 }
203 235
204 [Fact] 236 [Fact]
@@ -280,7 +312,7 @@ namespace WixToolsetTest.BurnE2E
280 312
281 packageCv1.VerifyInstalled(false); 313 packageCv1.VerifyInstalled(false);
282 packageCv2.VerifyInstalled(false); 314 packageCv2.VerifyInstalled(false);
283 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 315 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
284 } 316 }
285 317
286 [Fact] 318 [Fact]
@@ -366,7 +398,7 @@ namespace WixToolsetTest.BurnE2E
366 398
367 packageAv1.VerifyInstalled(false); 399 packageAv1.VerifyInstalled(false);
368 packageAv2.VerifyInstalled(false); 400 packageAv2.VerifyInstalled(false);
369 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 401 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
370 } 402 }
371 403
372 [Fact] 404 [Fact]
@@ -414,7 +446,7 @@ namespace WixToolsetTest.BurnE2E
414 446
415 packageAv1.VerifyInstalled(false); 447 packageAv1.VerifyInstalled(false);
416 packageAv2.VerifyInstalled(false); 448 packageAv2.VerifyInstalled(false);
417 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 449 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
418 } 450 }
419 451
420 [Fact] 452 [Fact]
@@ -463,7 +495,7 @@ namespace WixToolsetTest.BurnE2E
463 495
464 packageAv1.VerifyInstalled(false); 496 packageAv1.VerifyInstalled(false);
465 packageAv2.VerifyInstalled(false); 497 packageAv2.VerifyInstalled(false);
466 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion)); 498 Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
467 } 499 }
468 } 500 }
469} 501}