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, ®istrationType);
509
- ExitOnRootFailure(hr, "BA aborted unregister begin.");
508
+ UserExperienceOnUnregisterBegin(&pEngineState->userExperience, ®istrationType);
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
}