@joebigelow / wix-1 / commits / 8e8f724d

Add UnsafeUninstall action.

Fixes #6721

Sean Hall committed Feb 22, 2022 at 20:23 UTC 8e8f724d90c6835febb8b5865009746aea73a334
15 files changed +284 -53
src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperEngine.h
+1 -2
@@ -15,13 +15,12 @@ extern "C" {
15
16 static const HRESULT E_SUSPECTED_AV_INTERFERENCE = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIX, 2000);
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.
18 enum BOOTSTRAPPER_ACTION
19 {
20 BOOTSTRAPPER_ACTION_UNKNOWN,
21 BOOTSTRAPPER_ACTION_HELP,
22 BOOTSTRAPPER_ACTION_LAYOUT,
23 + BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL,
24 BOOTSTRAPPER_ACTION_UNINSTALL,
25 BOOTSTRAPPER_ACTION_CACHE,
26 BOOTSTRAPPER_ACTION_INSTALL,
src/api/burn/WixToolset.Mba.Core/IBootstrapperEngine.cs
+5
@@ -338,6 +338,11 @@ namespace WixToolset.Mba.Core
338 /// </summary>
339 Layout,
340
341 + /// <summary>
342 + ///
343 + /// </summary>
344 + UnsafeUninstall,
345 +
346 /// <summary>
347 ///
348 /// </summary>
src/burn/engine/apply.cpp
+10 -3
@@ -505,8 +505,7 @@ extern "C" HRESULT ApplyUnregister(
505
506 registrationType = defaultRegistrationType;
507
508 - hr = UserExperienceOnUnregisterBegin(&pEngineState->userExperience, &registrationType);
509 - ExitOnRootFailure(hr, "BA aborted unregister begin.");
508 + UserExperienceOnUnregisterBegin(&pEngineState->userExperience, &registrationType);
509
510 // Barring the special cases, if it was determined that we should keep the registration then
511 // do that, otherwise the resume mode is NONE and registration will be removed.
@@ -517,7 +516,7 @@ extern "C" HRESULT ApplyUnregister(
516
517 // If apply failed in any way and we're going to be keeping the bundle registered then
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)
520 {
521 // Execute any rollback registration actions.
522 HRESULT hrRegistrationRollback = ExecuteDependentRegistrationActions(pEngineState->companionConnection.hPipe, &pEngineState->registration, pEngineState->plan.rgRollbackRegistrationActions, pEngineState->plan.cRollbackRegistrationActions);
@@ -526,6 +525,14 @@ extern "C" HRESULT ApplyUnregister(
525
526 LogId(REPORT_STANDARD, MSG_SESSION_END, pEngineState->registration.sczRegistrationKey, LoggingResumeModeToString(resumeMode), LoggingRestartToString(restart), LoggingBoolToString(pEngineState->registration.fDisableResume), LoggingRegistrationTypeToString(defaultRegistrationType), LoggingRegistrationTypeToString(registrationType));
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 +
536 if (pEngineState->registration.fPerMachine)
537 {
538 hr = ElevationSessionEnd(pEngineState->companionConnection.hPipe, resumeMode, restart, pEngineState->registration.fDetectedForeignProviderKeyBundleId, registrationType);
src/burn/engine/core.cpp
+23 -5
@@ -488,7 +488,7 @@ extern "C" HRESULT CorePlan(
488 pEngineState->plan.pPayloads = &pEngineState->payloads;
489 pEngineState->plan.wzBundleId = 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;
493
494 hr = PlanSetVariables(action, &pEngineState->variables);
@@ -756,6 +756,14 @@ extern "C" HRESULT CoreApply(
756 }
757 }
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 +
767 if (fSuspend || BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart)
768 {
769 // Leave cache alone.
@@ -773,7 +781,7 @@ extern "C" HRESULT CoreApply(
781 }
782
783 LExit:
776 - if (fRollbackCache)
784 + if (fRollbackCache && !pEngineState->plan.fDisableRollback)
785 {
786 ApplyCacheRollback(&pEngineState->userExperience, &pEngineState->plan, pEngineState->companionConnection.hCachePipe, &applyContext);
787 }
@@ -978,6 +986,9 @@ static HRESULT CoreRecreateCommandLine(
986 case BOOTSTRAPPER_ACTION_UNINSTALL:
987 hr = StrAllocConcat(psczCommandLine, L" /uninstall", 0);
988 break;
989 + case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL:
990 + hr = StrAllocConcat(psczCommandLine, L" /unsafeuninstall", 0);
991 + break;
992 }
993 ExitOnFailure(hr, "Failed to append action state to command-line");
994
@@ -1414,6 +1425,13 @@ extern "C" HRESULT CoreParseCommandLine(
1425 ExitOnFailure(hr, "Failed to copy path for layout directory.");
1426 }
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 + }
1435 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"uninstall", -1))
1436 {
1437 if (BOOTSTRAPPER_ACTION_HELP != pCommand->action)
@@ -2207,7 +2225,7 @@ static void LogPackages(
2225 __in const BOOTSTRAPPER_ACTION action
2226 )
2227 {
2210 - BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == action;
2228 + BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == action;
2229
2230 if (pUpgradeBundlePackage)
2231 {
@@ -2286,8 +2304,8 @@ static void LogPackages(
2304 }
2305 }
2306
2289 - // Display related bundles last if caching, installing, modifying, or repairing.
2290 - if (BOOTSTRAPPER_ACTION_UNINSTALL < action)
2307 + // Display related bundles last if not uninstalling.
2308 + if (!fUninstalling)
2309 {
2310 LogRelatedBundles(pRelatedBundles, FALSE);
2311 }
src/burn/engine/elevation.cpp
+15 -2
@@ -2289,8 +2289,21 @@ static HRESULT OnApplyInitialize(
2289
2290 LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_STARTING);
2291
2292 - BOOTSTRAPPER_ACTION action = static_cast<BOOTSTRAPPER_ACTION>(dwAction);
2293 - SRP_ACTION restoreAction = (BOOTSTRAPPER_ACTION_INSTALL == action) ? SRP_ACTION_INSTALL : (BOOTSTRAPPER_ACTION_UNINSTALL == action) ? SRP_ACTION_UNINSTALL : SRP_ACTION_MODIFY;
2292 + SRP_ACTION restoreAction = SRP_ACTION_UNKNOWN;
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 +
2307 hrStatus = hr = SrpCreateRestorePoint(sczBundleName, restoreAction);
2308 if (SUCCEEDED(hr))
2309 {
src/burn/engine/engine.mc
+21
@@ -492,6 +492,13 @@ Language=English
492 Planned rollback boundary: '%1!ls!', vital: %2!hs!, transaction: %3!hs! (default: %4!hs!)
493 .
494
495 +MessageId=223
496 +Severity=Success
497 +SymbolicName=MSG_PLAN_NOT_SKIPPED_DUE_TO_DEPENDENTS
498 +Language=English
499 +Ignoring bundle dependents due to action UnsafeUninstall...
500 +.
501 +
502 MessageId=299
503 Severity=Success
504 SymbolicName=MSG_PLAN_COMPLETE
@@ -947,6 +954,20 @@ Language=English
954 package: %1!ls!, install registration state: %2!hs!, cache registration state: %3!hs!
955 .
956
957 +MessageId=375
958 +Severity=Success
959 +SymbolicName=MSG_UNSAFE_APPLY_COMPLETED
960 +Language=English
961 +Ignoring suspend and restart values due to action UnsafeUninstall...
962 +.
963 +
964 +MessageId=376
965 +Severity=Success
966 +SymbolicName=MSG_UNSAFE_SESSION_END
967 +Language=English
968 +Ignoring resume and registration values due to action UnsafeUninstall...
969 +.
970 +
971 MessageId=380
972 Severity=Warning
973 SymbolicName=MSG_APPLY_SKIPPED
src/burn/engine/logging.cpp
+2
@@ -354,6 +354,8 @@ extern "C" LPCSTR LoggingBurnActionToString(
354 return "Layout";
355 case BOOTSTRAPPER_ACTION_CACHE:
356 return "Cache";
357 + case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL:
358 + return "UnsafeUninstall";
359 case BOOTSTRAPPER_ACTION_UNINSTALL:
360 return "Uninstall";
361 case BOOTSTRAPPER_ACTION_INSTALL:
src/burn/engine/msiengine.cpp
+1 -1
@@ -834,7 +834,7 @@ extern "C" HRESULT MsiEnginePlanInitializePackage(
834 {
835 Assert(BURN_PACKAGE_TYPE_MSI == pPackage->compatiblePackage.type);
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;
839
840 hr = UserExperienceOnPlanCompatibleMsiPackageBegin(pUserExperience, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->compatiblePackage.Msi.pVersion, &pPackage->compatiblePackage.fRequested);
src/burn/engine/plan.cpp
+53 -31
@@ -361,13 +361,15 @@ extern "C" HRESULT PlanDefaultPackageRequestState(
361 else if (BOOTSTRAPPER_RELATION_PATCH == relationType && BURN_PACKAGE_TYPE_MSP == packageType)
362 {
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 {
366 - *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
366 + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE;
367 }
368 - else
368 + else if (BOOTSTRAPPER_ACTION_INSTALL == action ||
369 + BOOTSTRAPPER_ACTION_MODIFY == action ||
370 + BOOTSTRAPPER_ACTION_REPAIR == action)
371 {
370 - *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE;
372 + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
373 }
374 }
375 else // pick the best option for the action state and install condition.
@@ -375,7 +377,7 @@ extern "C" HRESULT PlanDefaultPackageRequestState(
377 hr = GetActionDefaultRequestState(action, currentState, &defaultRequestState);
378 ExitOnFailure(hr, "Failed to get default request state for action.");
379
378 - if (BOOTSTRAPPER_ACTION_UNINSTALL != action)
380 + if (BOOTSTRAPPER_ACTION_UNINSTALL != action && BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL != action)
381 {
382 // If we're not doing an uninstall, use the install condition
383 // to determine whether to use the default request state or make the package absent.
@@ -485,7 +487,8 @@ extern "C" HRESULT PlanForwardCompatibleBundles(
487 {
488 fRecommendIgnore = FALSE;
489 }
488 - else if (BOOTSTRAPPER_ACTION_UNINSTALL == action ||
490 + else if (BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == action ||
491 + BOOTSTRAPPER_ACTION_UNINSTALL == action ||
492 BOOTSTRAPPER_ACTION_MODIFY == action ||
493 BOOTSTRAPPER_ACTION_REPAIR == action)
494 {
@@ -552,10 +555,10 @@ extern "C" HRESULT PlanRegistration(
555 HRESULT hr = S_OK;
556 STRINGDICT_HANDLE sdBundleDependents = NULL;
557 STRINGDICT_HANDLE sdIgnoreDependents = NULL;
558 + BOOL fDependentBlocksUninstall = FALSE;
559
560 pPlan->fCanAffectMachineState = TRUE; // register the bundle since we're modifying machine state.
561 pPlan->fDisallowRemoval = FALSE; // by default the bundle can be planned to be removed
558 - pPlan->fIgnoreAllDependents = pDependencies->fIgnoreAllDependents;
562
563 // Ensure the bundle is cached if not running from the cache.
564 if (!CacheBundleRunningFromCache(pPlan->pCache))
@@ -563,7 +566,7 @@ extern "C" HRESULT PlanRegistration(
566 pPlan->dwRegistrationOperations |= BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE;
567 }
568
566 - if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action)
569 + if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action)
570 {
571 // If our provider key was not owned by a different bundle,
572 // then plan to write our provider key registration to "fix it" if broken
@@ -588,7 +591,7 @@ extern "C" HRESULT PlanRegistration(
591 ExitOnFailure(hr, "Failed to add self-dependent to ignore dependents.");
592 }
593
591 - if (!pPlan->fIgnoreAllDependents)
594 + if (!pDependencies->fIgnoreAllDependents)
595 {
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.
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(
644 hr = S_OK;
645
646 // TODO: callback to the BA and let it have the option to ignore this dependent?
644 - if (!pPlan->fDisallowRemoval)
647 + if (!fDependentBlocksUninstall)
648 {
646 - pPlan->fDisallowRemoval = TRUE; // ensure the registration stays
647 - *pfContinuePlanning = FALSE; // skip the rest of planning.
649 + fDependentBlocksUninstall = TRUE;
650
651 LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DUE_TO_DEPENDENTS);
652 }
@@ -653,6 +655,20 @@ extern "C" HRESULT PlanRegistration(
655 }
656 ExitOnFailure(hr, "Failed to check for remaining dependents during planning.");
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 + }
672 }
673 }
674 }
@@ -776,11 +792,12 @@ static HRESULT PlanPackagesHelper(
792 HRESULT hr = S_OK;
793 BOOL fBundlePerMachine = pPlan->fPerMachine; // bundle is per-machine if plan starts per-machine.
794 BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL;
795 + BOOL fReverseOrder = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
796
797 // Initialize the packages.
798 for (DWORD i = 0; i < cPackages; ++i)
799 {
783 - DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i;
800 + DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
801 BURN_PACKAGE* pPackage = rgPackages + iPackage;
802
803 hr = InitializePackage(pPlan, pUX, pVariables, pPackage);
@@ -790,7 +807,7 @@ static HRESULT PlanPackagesHelper(
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.
808 for (DWORD i = 0; i < cPackages; ++i)
809 {
793 - DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i;
810 + DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
811 BURN_PACKAGE* pPackage = rgPackages + iPackage;
812
813 if (BURN_PACKAGE_TYPE_MSP == pPackage->type)
@@ -803,7 +820,7 @@ static HRESULT PlanPackagesHelper(
820 // Plan the packages.
821 for (DWORD i = 0; i < cPackages; ++i)
822 {
806 - DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i;
823 + DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
824 BURN_PACKAGE* pPackage = rgPackages + iPackage;
825
826 hr = ProcessPackage(fBundlePerMachine, pUX, pPlan, pPackage, pLog, pVariables, &pRollbackBoundary);
@@ -825,7 +842,7 @@ static HRESULT PlanPackagesHelper(
842 // Plan clean up of packages.
843 for (DWORD i = 0; i < cPackages; ++i)
844 {
828 - DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i;
845 + DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
846 BURN_PACKAGE* pPackage = rgPackages + iPackage;
847
848 hr = PlanCleanPackage(pPlan, pPackage);
@@ -842,7 +859,7 @@ static HRESULT PlanPackagesHelper(
859 // Let the BA know the actions that were planned.
860 for (DWORD i = 0; i < cPackages; ++i)
861 {
845 - DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i;
862 + DWORD iPackage = fReverseOrder ? cPackages - 1 - i : i;
863 BURN_PACKAGE* pPackage = rgPackages + iPackage;
864
865 UserExperienceOnPlannedPackage(pUX, pPackage->sczId, pPackage->execute, pPackage->rollback, pPackage->fPlannedCache, pPackage->fPlannedUncache);
@@ -931,8 +948,9 @@ static HRESULT ProcessPackage(
948 {
949 HRESULT hr = S_OK;
950 BURN_ROLLBACK_BOUNDARY* pEffectiveRollbackBoundary = NULL;
951 + BOOL fBackward = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
952
935 - pEffectiveRollbackBoundary = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? pPackage->pRollbackBoundaryBackward : pPackage->pRollbackBoundaryForward;
953 + pEffectiveRollbackBoundary = fBackward ? pPackage->pRollbackBoundaryBackward : pPackage->pRollbackBoundaryForward;
954 hr = ProcessPackageRollbackBoundary(pPlan, pUX, pLog, pVariables, pEffectiveRollbackBoundary, ppRollbackBoundary);
955 ExitOnFailure(hr, "Failed to process package rollback boundary.");
956
@@ -1205,6 +1223,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1223 {
1224 HRESULT hr = S_OK;
1225 int nCompareResult = 0;
1226 + BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == action;
1227
1228 // Never touch related bundles during Cache.
1229 if (BOOTSTRAPPER_ACTION_CACHE == action)
@@ -1215,7 +1234,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1234 switch (relatedBundleRelationType)
1235 {
1236 case BOOTSTRAPPER_RELATION_UPGRADE:
1218 - if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && BOOTSTRAPPER_ACTION_UNINSTALL < action)
1237 + if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && !fUninstalling)
1238 {
1239 hr = VerCompareParsedVersions(pRegistrationVersion, pRelatedBundleVersion, &nCompareResult);
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(
1244 break;
1245 case BOOTSTRAPPER_RELATION_PATCH: __fallthrough;
1246 case BOOTSTRAPPER_RELATION_ADDON:
1228 - if (BOOTSTRAPPER_ACTION_UNINSTALL == action)
1247 + if (fUninstalling)
1248 {
1249 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT;
1250 }
@@ -1242,7 +1261,7 @@ extern "C" HRESULT PlanDefaultRelatedBundleRequestState(
1261 // Automatically repair dependent bundles to restore missing
1262 // packages after uninstall unless we're being upgraded with the
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)
1265 {
1266 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR;
1267 }
@@ -1270,6 +1289,7 @@ extern "C" HRESULT PlanRelatedBundlesBegin(
1289 LPWSTR* rgsczAncestors = NULL;
1290 UINT cAncestors = 0;
1291 STRINGDICT_HANDLE sdAncestors = NULL;
1292 + BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
1293
1294 if (pPlan->pInternalCommand->sczAncestors)
1295 {
@@ -1329,7 +1349,7 @@ extern "C" HRESULT PlanRelatedBundlesBegin(
1349 ExitOnRootFailure(hr, "BA aborted plan related bundle.");
1350
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)
1353 {
1354 if (0 < pRelatedBundle->package.cDependencyProviders)
1355 {
@@ -1361,6 +1381,9 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1381 HRESULT hr = S_OK;
1382 LPWSTR sczIgnoreDependencies = 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;
1387
1388 // Get the list of dependencies to ignore to pass to related bundles.
1389 hr = DependencyAllocIgnoreDependencies(pPlan, &sczIgnoreDependencies);
@@ -1369,9 +1392,6 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1392 hr = DictCreateStringList(&sdProviderKeys, pPlan->cExecuteActions, DICT_FLAG_CASEINSENSITIVE);
1393 ExitOnFailure(hr, "Failed to create dictionary for planned packages.");
1394
1372 - BOOL fExecutingAnyPackage = FALSE;
1373 - BOOL fInstallingAnyPackage = FALSE;
1374 -
1395 for (DWORD i = 0; i < pPlan->cExecuteActions; ++i)
1396 {
1397 BOOTSTRAPPER_ACTION_STATE packageAction = BOOTSTRAPPER_ACTION_STATE_NONE;
@@ -1444,7 +1464,7 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1464 }
1465
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)
1468 {
1469 pRelatedBundle->package.requested = BOOTSTRAPPER_REQUEST_STATE_NONE;
1470 LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DEPENDENT_BUNDLE_REPAIR, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType));
@@ -1457,7 +1477,7 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1477 ExitOnFailure(hr, "Failed to copy the list of dependencies to ignore.");
1478
1479 // Uninstall addons and patches early in the chain, before other packages are uninstalled.
1460 - if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action)
1480 + if (fUninstalling)
1481 {
1482 pdwInsertIndex = &dwExecuteActionEarlyIndex;
1483 }
@@ -1475,7 +1495,7 @@ extern "C" HRESULT PlanRelatedBundlesComplete(
1495 ExitOnFailure(hr, "Failed to begin plan dependency actions to package: %ls", pRelatedBundle->package.sczId);
1496
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)
1499 {
1500 ++(*pdwInsertIndex);
1501 }
@@ -1599,9 +1619,10 @@ extern "C" HRESULT PlanCleanPackage(
1619 HRESULT hr = S_OK;
1620 BOOL fPlanCleanPackage = FALSE;
1621 BURN_CLEAN_ACTION* pCleanAction = NULL;
1622 + BOOL fUninstalling = BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action || BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL == pPlan->action;
1623
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)
1626 {
1627 // The following are all different reasons why the package should be cleaned from the cache.
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(
1645 {
1646 fPlanCleanPackage = TRUE;
1647 }
1627 - else if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action && // uninstalling and
1648 + else if (fUninstalling && // uninstalling and
1649 BOOTSTRAPPER_REQUEST_STATE_NONE == pPackage->requested && // requested do nothing (aka: default) and
1650 BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is still do nothing and
1651 !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and
@@ -2091,6 +2112,7 @@ static HRESULT GetActionDefaultRequestState(
2112 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR;
2113 break;
2114
2115 + case BOOTSTRAPPER_ACTION_UNSAFE_UNINSTALL: __fallthrough;
2116 case BOOTSTRAPPER_ACTION_UNINSTALL:
2117 *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT;
2118 break;
@@ -2671,7 +2693,7 @@ static BOOL ForceCache(
2693 BOOTSTRAPPER_REQUEST_STATE_CACHE < pPackage->requested;
2694 case BOOTSTRAPPER_CACHE_TYPE_FORCE:
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;
2697 default:
2698 return FALSE;
2699 }
src/burn/engine/plan.h
-1
@@ -248,7 +248,6 @@ typedef struct _BURN_PLAN
248 BOOL fDisallowRemoval;
249 BOOL fDisableRollback;
250 BOOL fAffectedMachineState;
251 - BOOL fIgnoreAllDependents;
251 LPWSTR sczLayoutDirectory;
252 BOOL fBundleAlreadyRegistered;
253
src/burn/engine/userexperience.cpp
+1 -1
@@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad(
104 args.pCommand = pCommand;
105 args.pfnBootstrapperEngineProc = EngineForApplicationProc;
106 args.pvBootstrapperEngineProcContext = pEngineContext;
107 - args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 1, 10, 0);
107 + args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 2, 22, 0);
108
109 results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS);
110
src/burn/test/BurnUnitTest/PlanTest.cpp
+78
@@ -992,6 +992,84 @@ namespace Bootstrapper
992 ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_IGNORED, BURN_PACKAGE_REGISTRATION_STATE_IGNORED);
993 }
994
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]
1074 void SlipstreamInstallTest()
1075 {
src/test/burn/WixTestTools/BundleInstaller.cs
+30
@@ -3,6 +3,7 @@
3 namespace WixTestTools
4 {
5 using System;
6 + using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9
@@ -130,6 +131,35 @@ namespace WixTestTools
131 return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Uninstall, arguments, bundlePath: bundlePath);
132 }
133
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>
164 /// Executes the bundle with optional arguments.
165 /// </summary>
src/test/burn/WixTestTools/MSIExec.cs
+5
@@ -697,6 +697,11 @@ namespace WixTestTools
697 /// Uninstalls the product as part of cleanup
698 /// </summary>
699 Cleanup,
700 +
701 + /// <summary>
702 + /// No action automatically added to arguments
703 + /// </summary>
704 + Custom,
705 }
706
707 /// <summary>
src/test/burn/WixToolsetTest.BurnE2E/ForwardCompatibleBundleTests.cs
+39 -7
@@ -17,6 +17,38 @@ namespace WixToolsetTest.BurnE2E
17 private const string V100 = "1.0.0.0";
18 private const string V200 = "2.0.0.0";
19
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]
53 public void CanTrack1ForwardCompatibleDependentThroughMajorUpgrade()
54 {
@@ -70,7 +102,7 @@ namespace WixToolsetTest.BurnE2E
102
103 packageAv1.VerifyInstalled(false);
104 packageAv2.VerifyInstalled(false);
73 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
105 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
106 }
107
108 [Fact]
@@ -116,7 +148,7 @@ namespace WixToolsetTest.BurnE2E
148
149 packageAv1.VerifyInstalled(false);
150 packageAv2.VerifyInstalled(false);
119 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
151 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
152 }
153
154 [Fact]
@@ -198,7 +230,7 @@ namespace WixToolsetTest.BurnE2E
230
231 packageAv1.VerifyInstalled(false);
232 packageAv2.VerifyInstalled(false);
201 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
233 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
234 }
235
236 [Fact]
@@ -280,7 +312,7 @@ namespace WixToolsetTest.BurnE2E
312
313 packageCv1.VerifyInstalled(false);
314 packageCv2.VerifyInstalled(false);
283 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
315 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
316 }
317
318 [Fact]
@@ -366,7 +398,7 @@ namespace WixToolsetTest.BurnE2E
398
399 packageAv1.VerifyInstalled(false);
400 packageAv2.VerifyInstalled(false);
369 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
401 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
402 }
403
404 [Fact]
@@ -414,7 +446,7 @@ namespace WixToolsetTest.BurnE2E
446
447 packageAv1.VerifyInstalled(false);
448 packageAv2.VerifyInstalled(false);
417 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
449 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
450 }
451
452 [Fact]
@@ -463,7 +495,7 @@ namespace WixToolsetTest.BurnE2E
495
496 packageAv1.VerifyInstalled(false);
497 packageAv2.VerifyInstalled(false);
466 - Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out actualProviderVersion));
498 + Assert.False(BundleRegistration.TryGetDependencyProviderValue(providerId, "Version", out _));
499 }
500 }
501 }