From da1d1376953ef1c9afb32d5eee02b785e52e372e Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 14 Jan 2022 21:37:24 -0600 Subject: Remove orphan compatible MSI packages. Reimplements #3190 --- src/burn/engine/apply.cpp | 162 ++++++++++++++--- src/burn/engine/core.cpp | 8 +- src/burn/engine/dependency.cpp | 123 +++++++++++-- src/burn/engine/dependency.h | 5 + src/burn/engine/detect.cpp | 2 + src/burn/engine/elevation.cpp | 303 +++++++++++++++++++++++++++++++- src/burn/engine/elevation.h | 17 +- src/burn/engine/engine.mc | 23 ++- src/burn/engine/logging.cpp | 43 +++++ src/burn/engine/logging.h | 7 + src/burn/engine/msiengine.cpp | 181 +++++++++++++++++++ src/burn/engine/msiengine.h | 14 ++ src/burn/engine/package.cpp | 25 +++ src/burn/engine/package.h | 34 ++++ src/burn/engine/plan.cpp | 83 ++++++++- src/burn/engine/plan.h | 10 +- src/burn/engine/registration.cpp | 4 + src/burn/engine/userexperience.cpp | 118 ++++++++++++- src/burn/engine/userexperience.h | 26 +++ src/burn/test/BurnUnitTest/PlanTest.cpp | 133 +++++++++++++- 20 files changed, 1252 insertions(+), 69 deletions(-) (limited to 'src/burn') diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp index 99884234..6bf3020c 100644 --- a/src/burn/engine/apply.cpp +++ b/src/burn/engine/apply.cpp @@ -60,7 +60,7 @@ typedef struct _BURN_EXECUTE_CONTEXT BURN_USER_EXPERIENCE* pUX; BURN_APPLY_CONTEXT* pApplyContext; BOOL fRollback; - BURN_PACKAGE* pExecutingPackage; + LPCWSTR wzExecutingPackageId; DWORD cExecutedPackages; DWORD cExecutePackagesTotal; } BURN_EXECUTE_CONTEXT; @@ -278,6 +278,19 @@ static void ResetTransactionRegistrationState( __in BURN_ENGINE_STATE* pEngineState, __in BOOL fCommit ); +static HRESULT ExecuteUninstallMsiCompatiblePackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT CleanCompatiblePackage( + __in BURN_CACHE* pCache, + __in HANDLE hElevatedPipe, + __in BURN_PACKAGE* pPackage + ); static HRESULT CleanPackage( __in BURN_CACHE* pCache, __in HANDLE hElevatedPipe, @@ -300,7 +313,8 @@ static HRESULT ReportOverallProgressTicks( static HRESULT ExecutePackageComplete( __in BURN_USER_EXPERIENCE* pUX, __in BURN_VARIABLES* pVariables, - __in BURN_PACKAGE* pPackage, + __in LPCWSTR wzPackageId, + __in BOOL fPackageVital, __in HRESULT hrOverall, __in HRESULT hrExecute, __in BOOL fRollback, @@ -808,7 +822,20 @@ extern "C" void ApplyClean( BURN_CLEAN_ACTION* pCleanAction = pPlan->rgCleanActions + i; BURN_PACKAGE* pPackage = pCleanAction->pPackage; - hr = CleanPackage(pPlan->pCache, hPipe, pPackage); + switch (pCleanAction->type) + { + case BURN_CLEAN_ACTION_TYPE_COMPATIBLE_PACKAGE: + hr = CleanCompatiblePackage(pPlan->pCache, hPipe, pPackage); + break; + + case BURN_CLEAN_ACTION_TYPE_PACKAGE: + hr = CleanPackage(pPlan->pCache, hPipe, pPackage); + break; + + default: + AssertSz(FALSE, "Unknown clean action."); + break; + } } } @@ -2350,6 +2377,11 @@ static HRESULT DoExecuteAction( ExitOnFailure(hr, "Failed to execute commit MSI transaction action."); break; + case BURN_EXECUTE_ACTION_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE: + hr = ExecuteUninstallMsiCompatiblePackage(pEngineState, pExecuteAction, pContext, &fRetry, pfSuspend, &restart); + ExitOnFailure(hr, "Failed to execute uninstall MSI compatible package."); + break; + default: hr = E_UNEXPECTED; ExitOnFailure(hr, "Invalid execute action."); @@ -2509,7 +2541,7 @@ static HRESULT ExecuteRelatedBundle( } Assert(pContext->fRollback == fRollback); - pContext->pExecutingPackage = pPackage; + pContext->wzExecutingPackageId = pPackage->sczId; fBeginCalled = TRUE; // Send package execute begin to BA. @@ -2550,7 +2582,7 @@ static HRESULT ExecuteRelatedBundle( LExit: if (fBeginCalled) { - hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage->sczId, pPackage->fVital, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); } return hr; @@ -2581,7 +2613,7 @@ static HRESULT ExecuteExePackage( } Assert(pContext->fRollback == fRollback); - pContext->pExecutingPackage = pPackage; + pContext->wzExecutingPackageId = pPackage->sczId; fBeginCalled = TRUE; // Send package execute begin to BA. @@ -2629,7 +2661,7 @@ LExit: if (fBeginCalled) { - hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage->sczId, pPackage->fVital, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); } return hr; @@ -2659,7 +2691,7 @@ static HRESULT ExecuteMsiPackage( } Assert(pContext->fRollback == fRollback); - pContext->pExecutingPackage = pPackage; + pContext->wzExecutingPackageId = pPackage->sczId; fBeginCalled = TRUE; // Send package execute begin to BA. @@ -2693,7 +2725,7 @@ LExit: if (fBeginCalled) { - hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage->sczId, pPackage->fVital, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); } return hr; @@ -2723,7 +2755,7 @@ static HRESULT ExecuteMspPackage( } Assert(pContext->fRollback == fRollback); - pContext->pExecutingPackage = pPackage; + pContext->wzExecutingPackageId = pPackage->sczId; fBeginCalled = TRUE; // Send package execute begin to BA. @@ -2766,7 +2798,7 @@ LExit: if (fBeginCalled) { - hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage->sczId, pPackage->fVital, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); } return hr; @@ -2798,7 +2830,7 @@ static HRESULT ExecuteMsuPackage( } Assert(pContext->fRollback == fRollback); - pContext->pExecutingPackage = pPackage; + pContext->wzExecutingPackageId = pPackage->sczId; fBeginCalled = TRUE; // Send package execute begin to BA. @@ -2846,7 +2878,7 @@ LExit: if (fBeginCalled) { - hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage->sczId, pPackage->fVital, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); } return hr; @@ -3110,6 +3142,79 @@ static void ResetTransactionRegistrationState( } } +static HRESULT ExecuteUninstallMsiCompatiblePackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + HRESULT hrExecute = S_OK; + BOOL fRollback = FALSE; + BOOTSTRAPPER_ACTION_STATE action = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + INSTALLUILEVEL uiLevel = INSTALLUILEVEL_NONE; + BOOL fDisableExternalUiHandler = FALSE; + BOOL fBeginCalled = FALSE; + BURN_PACKAGE* pParentPackage = pExecuteAction->uninstallMsiCompatiblePackage.pParentPackage; + + Assert(pContext->fRollback == fRollback); + pContext->wzExecutingPackageId = pParentPackage->compatiblePackage.compatibleEntry.sczId; + fBeginCalled = TRUE; + + // Send package execute begin to BA. + hr = UserExperienceOnExecutePackageBegin(&pEngineState->userExperience, pContext->wzExecutingPackageId, !fRollback, action, uiLevel, fDisableExternalUiHandler); + ExitOnRootFailure(hr, "BA aborted execute MSI compatible package begin."); + + // execute package + if (pParentPackage->fPerMachine) + { + hrExecute = ElevationUninstallMsiCompatiblePackage(pEngineState->companionConnection.hPipe, pEngineState->userExperience.hwndApply, pExecuteAction, &pEngineState->variables, fRollback, MsiExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to uninstall per-machine MSI compatible package."); + } + else + { + hrExecute = MsiEngineUninstallCompatiblePackage(pEngineState->userExperience.hwndApply, pExecuteAction, pContext->pCache, &pEngineState->variables, fRollback, MsiExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to uninstall per-user MSI compatible package."); + } + + pContext->cExecutedPackages += fRollback ? -1 : 1; + + hr = ReportOverallProgressTicks(&pEngineState->userExperience, fRollback, pEngineState->plan.cOverallProgressTicksTotal, pContext->pApplyContext); + ExitOnRootFailure(hr, "BA aborted MSI compatible package execute progress."); + +LExit: + + if (fBeginCalled) + { + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pContext->wzExecutingPackageId, FALSE, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + } + + return hr; +} + +static HRESULT CleanCompatiblePackage( + __in BURN_CACHE* pCache, + __in HANDLE hElevatedPipe, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + + if (pPackage->fPerMachine) + { + hr = ElevationCleanCompatiblePackage(hElevatedPipe, pPackage); + } + else + { + hr = CacheRemovePackage(pCache, FALSE, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->compatiblePackage.sczCacheId); + } + + return hr; +} + static HRESULT CleanPackage( __in BURN_CACHE* pCache, __in HANDLE hElevatedPipe, @@ -3150,16 +3255,16 @@ static int GenericExecuteMessageHandler( case GENERIC_EXECUTE_MESSAGE_PROGRESS: { DWORD dwOverallProgress = pContext->cExecutePackagesTotal ? (pContext->cExecutedPackages * 100 + pMessage->progress.dwPercentage) / (pContext->cExecutePackagesTotal) : 0; - UserExperienceOnExecuteProgress(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->progress.dwPercentage, dwOverallProgress, &nResult); // ignore return value. + UserExperienceOnExecuteProgress(pContext->pUX, pContext->wzExecutingPackageId, pMessage->progress.dwPercentage, dwOverallProgress, &nResult); // ignore return value. } break; case GENERIC_EXECUTE_MESSAGE_ERROR: - UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, 0, NULL, &nResult); // ignore return value. + UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->wzExecutingPackageId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, 0, NULL, &nResult); // ignore return value. break; case GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE: - UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->filesInUse.cFiles, pMessage->filesInUse.rgwzFiles, BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX, &nResult); // ignore return value. + UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->wzExecutingPackageId, pMessage->filesInUse.cFiles, pMessage->filesInUse.rgwzFiles, BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX, &nResult); // ignore return value. fPassthrough = TRUE; break; } @@ -3188,25 +3293,25 @@ static int MsiExecuteMessageHandler( case WIU_MSI_EXECUTE_MESSAGE_PROGRESS: { DWORD dwOverallProgress = pContext->cExecutePackagesTotal ? (pContext->cExecutedPackages * 100 + pMessage->progress.dwPercentage) / (pContext->cExecutePackagesTotal) : 0; - UserExperienceOnExecuteProgress(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->progress.dwPercentage, dwOverallProgress, &nResult); // ignore return value. + UserExperienceOnExecuteProgress(pContext->pUX, pContext->wzExecutingPackageId, pMessage->progress.dwPercentage, dwOverallProgress, &nResult); // ignore return value. } break; case WIU_MSI_EXECUTE_MESSAGE_ERROR: nResult = pMessage->nResultRecommendation; - UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. + UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER, pContext->wzExecutingPackageId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. break; case WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE: nResult = pMessage->nResultRecommendation; - UserExperienceOnExecuteMsiMessage(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiMessage.mt, pMessage->dwUIHint, pMessage->msiMessage.wzMessage, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. + UserExperienceOnExecuteMsiMessage(pContext->pUX, pContext->wzExecutingPackageId, pMessage->msiMessage.mt, pMessage->dwUIHint, pMessage->msiMessage.wzMessage, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. break; case WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE: fRestartManager = TRUE; __fallthrough; case WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE: - UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiFilesInUse.cFiles, pMessage->msiFilesInUse.rgwzFiles, fRestartManager ? BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM : BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI, &nResult); // ignore return value. + UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->wzExecutingPackageId, pMessage->msiFilesInUse.cFiles, pMessage->msiFilesInUse.rgwzFiles, fRestartManager ? BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM : BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI, &nResult); // ignore return value. fPassthrough = TRUE; break; } @@ -3246,7 +3351,8 @@ static HRESULT ReportOverallProgressTicks( static HRESULT ExecutePackageComplete( __in BURN_USER_EXPERIENCE* pUX, __in BURN_VARIABLES* pVariables, - __in BURN_PACKAGE* pPackage, + __in LPCWSTR wzPackageId, + __in BOOL fPackageVital, __in HRESULT hrOverall, __in HRESULT hrExecute, __in BOOL fRollback, @@ -3256,10 +3362,10 @@ static HRESULT ExecutePackageComplete( ) { HRESULT hr = FAILED(hrOverall) ? hrOverall : hrExecute; // if the overall function failed use that otherwise use the execution result. - BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION executePackageCompleteAction = FAILED(hrOverall) || SUCCEEDED(hrExecute) || pPackage->fVital ? BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_NONE : BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_IGNORE; + BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION executePackageCompleteAction = FAILED(hrOverall) || SUCCEEDED(hrExecute) || fPackageVital ? BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_NONE : BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_IGNORE; // Send package execute complete to BA. - UserExperienceOnExecutePackageComplete(pUX, pPackage->sczId, hr, *pRestart, &executePackageCompleteAction); + UserExperienceOnExecutePackageComplete(pUX, wzPackageId, hr, *pRestart, &executePackageCompleteAction); if (BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_RESTART == executePackageCompleteAction) { *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; @@ -3271,23 +3377,23 @@ static HRESULT ExecutePackageComplete( if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == *pRestart) { // Best effort to set the forced restart package variable. - VariableSetString(pVariables, BURN_BUNDLE_FORCED_RESTART_PACKAGE, pPackage->sczId, TRUE, FALSE); + VariableSetString(pVariables, BURN_BUNDLE_FORCED_RESTART_PACKAGE, wzPackageId, TRUE, FALSE); } // If we're retrying, leave a message in the log file and say everything is okay. if (*pfRetry) { - LogId(REPORT_STANDARD, MSG_APPLY_RETRYING_PACKAGE, pPackage->sczId, hrExecute); + LogId(REPORT_STANDARD, MSG_APPLY_RETRYING_PACKAGE, wzPackageId, hrExecute); hr = S_OK; } - else if (SUCCEEDED(hrOverall) && FAILED(hrExecute) && BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_IGNORE == executePackageCompleteAction && !pPackage->fVital) // If we *only* failed to execute and the BA ignored this *not-vital* package, say everything is okay. + else if (SUCCEEDED(hrOverall) && FAILED(hrExecute) && BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_IGNORE == executePackageCompleteAction && !fPackageVital) // If we *only* failed to execute and the BA ignored this *not-vital* package, say everything is okay. { - LogId(REPORT_STANDARD, MSG_APPLY_CONTINUING_NONVITAL_PACKAGE, pPackage->sczId, hrExecute); + LogId(REPORT_STANDARD, MSG_APPLY_CONTINUING_NONVITAL_PACKAGE, wzPackageId, hrExecute); hr = S_OK; } else { - LogId(REPORT_STANDARD, MSG_APPLY_COMPLETED_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, hr, LoggingRestartToString(*pRestart)); + LogId(REPORT_STANDARD, MSG_APPLY_COMPLETED_PACKAGE, LoggingRollbackOrExecute(fRollback), wzPackageId, hr, LoggingRestartToString(*pRestart)); } return hr; diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp index d70810f2..7f7a915e 100644 --- a/src/burn/engine/core.cpp +++ b/src/burn/engine/core.cpp @@ -389,6 +389,7 @@ extern "C" HRESULT CoreDetect( pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + pPackage->compatiblePackage.fDetected = FALSE; } } @@ -2240,7 +2241,7 @@ static void LogPackages( } LogId(REPORT_STANDARD, MSG_PLANNED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingRequestStateToString(pPackage->defaultRequested), LoggingRequestStateToString(pPackage->requested), LoggingActionStateToString(pPackage->execute), LoggingActionStateToString(pPackage->rollback), LoggingCacheTypeToString(pPackage->authoredCacheType), LoggingCacheTypeToString(pPackage->cacheType), LoggingBoolToString(pPackage->fPlannedCache), LoggingBoolToString(pPackage->fPlannedUncache), LoggingDependencyActionToString(pPackage->dependencyExecute), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->expectedInstallRegistrationState), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->expectedCacheRegistrationState)); - + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) { if (pPackage->Msi.cFeatures) @@ -2266,6 +2267,11 @@ static void LogPackages( LogId(REPORT_STANDARD, MSG_PLANNED_SLIPSTREAMED_MSP_TARGET, pSlipstreamMsp->pMspPackage->sczId, LoggingActionStateToString(pSlipstreamMsp->execute), LoggingActionStateToString(pSlipstreamMsp->rollback)); } } + + if (pPackage->compatiblePackage.fRemove) + { + LogId(REPORT_STANDARD, MSG_PLANNED_ORPHAN_PACKAGE_FROM_PROVIDER, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->Msi.sczProductCode); + } } else if (BURN_PACKAGE_TYPE_MSP == pPackage->type && pPackage->Msp.cTargetProductCodes) { diff --git a/src/burn/engine/dependency.cpp b/src/burn/engine/dependency.cpp index 5d7e1a94..6ee95935 100644 --- a/src/burn/engine/dependency.cpp +++ b/src/burn/engine/dependency.cpp @@ -54,6 +54,10 @@ static HRESULT AddPackageDependencyActions( __in const BURN_DEPENDENCY_ACTION dependencyRollbackAction ); +static LPCWSTR GetPackageProviderId( + __in const BURN_PACKAGE* pPackage + ); + static HRESULT RegisterPackageProvider( __in const BURN_PACKAGE* pPackage ); @@ -299,6 +303,9 @@ extern "C" HRESULT DependencyDetectChainPackage( hr = DetectPackageDependents(pPackage, pRegistration); ExitOnFailure(hr, "Failed to detect dependents for package '%ls'", pPackage->sczId); + hr = DependencyDetectCompatibleEntry(pPackage, pRegistration); + ExitOnFailure(hr, "Failed to detect compatible package for package '%ls'", pPackage->sczId); + LExit: return hr; } @@ -396,6 +403,8 @@ extern "C" HRESULT DependencyPlanPackageBegin( STRINGDICT_HANDLE sdIgnoredDependents = NULL; BURN_DEPENDENCY_ACTION dependencyExecuteAction = BURN_DEPENDENCY_ACTION_NONE; BURN_DEPENDENCY_ACTION dependencyRollbackAction = BURN_DEPENDENCY_ACTION_NONE; + BOOL fDependentBlocksUninstall = FALSE; + BOOL fAttemptingUninstall = BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute || pPackage->compatiblePackage.fRemove; pPackage->dependencyExecute = BURN_DEPENDENCY_ACTION_NONE; pPackage->dependencyRollback = BURN_DEPENDENCY_ACTION_NONE; @@ -415,7 +424,7 @@ extern "C" HRESULT DependencyPlanPackageBegin( } // If we're uninstalling the package, check if any dependents are registered. - if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + if (fAttemptingUninstall) { // Build up a list of dependents to ignore, including the current bundle. hr = GetIgnoredDependents(pPackage, pPlan, &sdIgnoredDependents); @@ -444,9 +453,9 @@ extern "C" HRESULT DependencyPlanPackageBegin( { hr = S_OK; - if (!pPackage->fDependencyManagerWasHere) + if (!fDependentBlocksUninstall) { - pPackage->fDependencyManagerWasHere = TRUE; + fDependentBlocksUninstall = TRUE; LogId(REPORT_STANDARD, MSG_DEPENDENCY_PACKAGE_HASDEPENDENTS, pPackage->sczId); } @@ -459,14 +468,20 @@ extern "C" HRESULT DependencyPlanPackageBegin( } } + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + { + pPackage->fDependencyManagerWasHere = fDependentBlocksUninstall; + } + // Calculate the dependency actions before the package itself is planned. CalculateDependencyActionStates(pPackage, pPlan->action, &dependencyExecuteAction, &dependencyRollbackAction); // If dependents were found, change the action to not uninstall the package. - if (pPackage->fDependencyManagerWasHere) + if (fDependentBlocksUninstall) { pPackage->execute = BOOTSTRAPPER_ACTION_STATE_NONE; pPackage->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + pPackage->compatiblePackage.fRemove = FALSE; } else { @@ -494,7 +509,7 @@ extern "C" HRESULT DependencyPlanPackageBegin( } // If the package will be removed, add its providers to the growing list in the plan. - if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + if (fAttemptingUninstall) { for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) { @@ -739,6 +754,73 @@ extern "C" void DependencyUnregisterBundle( } } +extern "C" HRESULT DependencyDetectCompatibleEntry( + __in BURN_PACKAGE* pPackage, + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + LPWSTR sczId = NULL; + LPWSTR sczName = NULL; + LPWSTR sczVersion = NULL; + LPCWSTR wzPackageProviderId = GetPackageProviderId(pPackage); + HKEY hkHive = pRegistration->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_MSI: + // Only MSI packages can handle compatible entries. + break; + default: + ExitFunction(); + } + + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + hr = DepGetProviderInformation(hkHive, pProvider->sczKey, &sczId, &sczName, &sczVersion); + if (E_NOTFOUND == hr) + { + hr = S_OK; + continue; + } + ExitOnFailure(hr, "Failed to get provider information for compatible package: %ls", pProvider->sczKey); + + // Make sure the compatible package is not the package itself. + if (!wzPackageProviderId) + { + if (!sczId) + { + continue; + } + } + else if (sczId && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzPackageProviderId, -1, sczId, -1)) + { + continue; + } + + pPackage->compatiblePackage.fDetected = TRUE; + + hr = StrAllocString(&pPackage->compatiblePackage.compatibleEntry.sczProviderKey, pProvider->sczKey, 0); + ExitOnFailure(hr, "Failed to copy provider key for compatible entry."); + + pPackage->compatiblePackage.compatibleEntry.sczId = sczId; + sczId = NULL; + + pPackage->compatiblePackage.compatibleEntry.sczName = sczName; + sczName = NULL; + + pPackage->compatiblePackage.compatibleEntry.sczVersion = sczVersion; + sczVersion = NULL; + + break; + } + +LExit: + return hr; +} + // internal functions @@ -1170,24 +1252,35 @@ LExit: return hr; } +static LPCWSTR GetPackageProviderId( + __in const BURN_PACKAGE* pPackage + ) +{ + LPCWSTR wzId = NULL; + + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_MSI: + wzId = pPackage->Msi.sczProductCode; + break; + case BURN_PACKAGE_TYPE_MSP: + wzId = pPackage->Msp.sczPatchCode; + break; + } + + return wzId; +} + static HRESULT RegisterPackageProvider( __in const BURN_PACKAGE* pPackage ) { HRESULT hr = S_OK; - LPWSTR wzId = NULL; - HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; if (pPackage->rgDependencyProviders) { - if (BURN_PACKAGE_TYPE_MSI == pPackage->type) - { - wzId = pPackage->Msi.sczProductCode; - } - else if (BURN_PACKAGE_TYPE_MSP == pPackage->type) - { - wzId = pPackage->Msp.sczPatchCode; - } + HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + LPCWSTR wzId = GetPackageProviderId(pPackage); for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) { diff --git a/src/burn/engine/dependency.h b/src/burn/engine/dependency.h index 8d7344eb..41718066 100644 --- a/src/burn/engine/dependency.h +++ b/src/burn/engine/dependency.h @@ -191,6 +191,11 @@ void DependencyUnregisterBundle( __in const BURN_PACKAGES* pPackages ); +HRESULT DependencyDetectCompatibleEntry( + __in BURN_PACKAGE* pPackage, + __in BURN_REGISTRATION* pRegistration + ); + #if defined(__cplusplus) } #endif diff --git a/src/burn/engine/detect.cpp b/src/burn/engine/detect.cpp index be828366..37b034ae 100644 --- a/src/burn/engine/detect.cpp +++ b/src/burn/engine/detect.cpp @@ -98,6 +98,8 @@ extern "C" void DetectReset( pProvider->rgDependents = NULL; pProvider->cDependents = 0; } + + PackageUninitializeCompatible(&pPackage->compatiblePackage); } for (DWORD iPatchInfo = 0; iPatchInfo < pPackages->cPatchInfo; ++iPatchInfo) diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 221d8b6d..9ebba7c5 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -32,6 +32,8 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE BURN_ELEVATION_MESSAGE_TYPE_BEGIN_MSI_TRANSACTION, BURN_ELEVATION_MESSAGE_TYPE_COMMIT_MSI_TRANSACTION, BURN_ELEVATION_MESSAGE_TYPE_ROLLBACK_MSI_TRANSACTION, + BURN_ELEVATION_MESSAGE_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE, + BURN_ELEVATION_MESSAGE_TYPE_CLEAN_COMPATIBLE_PACKAGE, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_BEGIN, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_COMPLETE, @@ -168,11 +170,16 @@ static HRESULT OnApplyInitialize( __in HANDLE hPipe, __in BURN_VARIABLES* pVariables, __in BURN_REGISTRATION* pRegistration, + __in BURN_PACKAGES* pPackages, __in HANDLE* phLock, __in BOOL* pfDisabledWindowsUpdate, __in BYTE* pbData, __in SIZE_T cbData ); +static HRESULT ElevatedProcessDetect( + __in BURN_REGISTRATION* pRegistration, + __in BURN_PACKAGES* pPackages + ); static HRESULT OnApplyUninitialize( __in HANDLE* phLock ); @@ -271,6 +278,14 @@ static HRESULT OnExecuteMsuPackage( __in BYTE* pbData, __in SIZE_T cbData ); +static HRESULT OnUninstallMsiCompatiblePackage( + __in HANDLE hPipe, + __in BURN_CACHE* pCache, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); static HRESULT OnExecutePackageProviderAction( __in BURN_PACKAGES* pPackages, __in BURN_RELATED_BUNDLES* pRelatedBundles, @@ -306,6 +321,12 @@ static int MsiExecuteMessageHandler( __in WIU_MSI_EXECUTE_MESSAGE* pMessage, __in_opt LPVOID pvContext ); +static HRESULT OnCleanCompatiblePackage( + __in BURN_CACHE* pCache, + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ); static HRESULT OnCleanPackage( __in BURN_CACHE* pCache, __in BURN_PACKAGES* pPackages, @@ -1230,6 +1251,58 @@ LExit: return hr; } +extern "C" HRESULT ElevationUninstallMsiCompatiblePackage( + __in HANDLE hPipe, + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + BURN_ELEVATION_MSI_MESSAGE_CONTEXT context = { }; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)fRollback); + ExitOnFailure(hr, "Failed to write rollback flag to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->uninstallMsiCompatiblePackage.pParentPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->uninstallMsiCompatiblePackage.pParentPackage->compatiblePackage.compatibleEntry.sczId); + ExitOnFailure(hr, "Failed to write compatible package id to message buffer."); + + hr = BuffWritePointer(&pbData, &cbData, (DWORD_PTR)hwndParent); + ExitOnFailure(hr, "Failed to write parent hwnd to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->uninstallMsiCompatiblePackage.sczLogPath); + ExitOnFailure(hr, "Failed to write package log to message buffer."); + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + + // send message + context.pfnMessageHandler = pfnMessageHandler; + context.pvContext = pvContext; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE, pbData, cbData, ProcessMsiPackageMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE message to per-machine process."); + + hr = ProcessResult(dwResult, pRestart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + extern "C" HRESULT ElevationExecutePackageProviderAction( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction @@ -1295,6 +1368,35 @@ LExit: return hr; } +extern "C" HRESULT ElevationCleanCompatiblePackage( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pPackage->sczId); + ExitOnFailure(hr, "Failed to write clean package id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pPackage->compatiblePackage.compatibleEntry.sczId); + ExitOnFailure(hr, "Failed to write compatible package id to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_CLEAN_COMPATIBLE_PACKAGE, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_CLEAN_COMPATIBLE_PACKAGE message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + /******************************************************************* ElevationCleanPackage - @@ -1940,7 +2042,7 @@ static HRESULT ProcessElevatedChildMessage( break; case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE: - hrResult = OnApplyInitialize(pContext->hPipe, pContext->pVariables, pContext->pRegistration, pContext->phLock, pContext->pfDisabledAutomaticUpdates, (BYTE*)pMsg->pvData, pMsg->cbData); + hrResult = OnApplyInitialize(pContext->hPipe, pContext->pVariables, pContext->pRegistration, pContext->pPackages, pContext->phLock, pContext->pfDisabledAutomaticUpdates, (BYTE*)pMsg->pvData, pMsg->cbData); break; case BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE: @@ -2003,6 +2105,14 @@ static HRESULT ProcessElevatedChildMessage( hrResult = OnLaunchApprovedExe(pContext->hPipe, pContext->pApprovedExes, pContext->pCache, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); break; + case BURN_ELEVATION_MESSAGE_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE: + hrResult = OnUninstallMsiCompatiblePackage(pContext->hPipe, pContext->pCache, pContext->pPackages, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_CLEAN_COMPATIBLE_PACKAGE: + hrResult = OnCleanCompatiblePackage(pContext->pCache, pContext->pPackages, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + default: hr = E_INVALIDARG; ExitOnRootFailure(hr, "Unexpected elevated message sent to child process, msg: %u", pMsg->dwMessage); @@ -2082,6 +2192,7 @@ static HRESULT OnApplyInitialize( __in HANDLE hPipe, __in BURN_VARIABLES* pVariables, __in BURN_REGISTRATION* pRegistration, + __in BURN_PACKAGES* pPackages, __in HANDLE* phLock, __in BOOL* pfDisabledWindowsUpdate, __in BYTE* pbData, @@ -2113,11 +2224,9 @@ static HRESULT OnApplyInitialize( hr = ApplyLock(TRUE, phLock); ExitOnFailure(hr, "Failed to acquire lock due to setup in other session."); - // Reset and reload the related bundles. - RelatedBundlesUninitialize(&pRegistration->relatedBundles); - - hr = RelatedBundlesInitializeForScope(TRUE, pRegistration, &pRegistration->relatedBundles); - ExitOnFailure(hr, "Failed to initialize per-machine related bundles."); + // Detect. + hr = ElevatedProcessDetect(pRegistration, pPackages); + ExitOnFailure(hr, "Failed to run detection in elevated process."); // Attempt to pause AU with best effort. if (BURN_AU_PAUSE_ACTION_IFELEVATED == dwAUAction || BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME == dwAUAction) @@ -2187,6 +2296,39 @@ LExit: return hr; } +static HRESULT ElevatedProcessDetect( + __in BURN_REGISTRATION* pRegistration, + __in BURN_PACKAGES* pPackages + ) +{ + HRESULT hr = S_OK; + + DetectReset(pRegistration, pPackages); + + hr = RelatedBundlesInitializeForScope(TRUE, pRegistration, &pRegistration->relatedBundles); + ExitOnFailure(hr, "Failed to initialize per-machine related bundles."); + + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + BURN_PACKAGE* pPackage = pPackages->rgPackages + i; + + hr = DependencyDetectCompatibleEntry(pPackage, pRegistration); + ExitOnFailure(hr, "Failed to detect per-machine compatible entry for package: %ls", pPackage->sczId); + + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_MSI: + hr = MsiEngineDetectCompatiblePackage(pPackage); + ExitOnFailure(hr, "Failed to detect per-machine compatible package for package: %ls", pPackage->sczId); + + break; + } + } + +LExit: + return hr; +} + static HRESULT OnApplyUninitialize( __in HANDLE* phLock ) @@ -2659,6 +2801,11 @@ static HRESULT OnExecuteExePackage( hr = PackageFindById(pPackages, sczPackage, &executeAction.exePackage.pPackage); ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + if (BURN_PACKAGE_TYPE_EXE != executeAction.exePackage.pPackage->type) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package is not an EXE package: %ls", sczPackage); + } + // Execute EXE package. hr = ExeEngineExecutePackage(&executeAction, pCache, pVariables, static_cast(dwRollback), GenericExecuteMessageHandler, hPipe, &exeRestart); ExitOnFailure(hr, "Failed to execute EXE package."); @@ -2760,6 +2907,11 @@ static HRESULT OnExecuteMsiPackage( hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); ExitOnFailure(hr, "Failed to read variables."); + if (BURN_PACKAGE_TYPE_MSI != executeAction.msiPackage.pPackage->type) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package is not an MSI package: %ls", sczPackage); + } + // Execute MSI package. hr = MsiEngineExecutePackage(hwndParent, &executeAction, pCache, pVariables, fRollback, MsiExecuteMessageHandler, hPipe, &msiRestart); ExitOnFailure(hr, "Failed to execute MSI package."); @@ -2859,6 +3011,11 @@ static HRESULT OnExecuteMspPackage( hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback); ExitOnFailure(hr, "Failed to read rollback flag."); + if (BURN_PACKAGE_TYPE_MSP != executeAction.mspTarget.pPackage->type) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package is not an MSP package: %ls", sczPackage); + } + // Execute MSP package. hr = MspEngineExecutePackage(hwndParent, &executeAction, pCache, pVariables, fRollback, MsiExecuteMessageHandler, hPipe, &restart); ExitOnFailure(hr, "Failed to execute MSP package."); @@ -2920,6 +3077,11 @@ static HRESULT OnExecuteMsuPackage( hr = PackageFindById(pPackages, sczPackage, &executeAction.msuPackage.pPackage); ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + if (BURN_PACKAGE_TYPE_MSU != executeAction.msuPackage.pPackage->type) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package is not an MSU package: %ls", sczPackage); + } + // execute MSU package hr = MsuEngineExecutePackage(&executeAction, pCache, pVariables, static_cast(dwRollback), static_cast(dwStopWusaService), GenericExecuteMessageHandler, hPipe, &restart); ExitOnFailure(hr, "Failed to execute MSU package."); @@ -2943,6 +3105,88 @@ LExit: return hr; } +static HRESULT OnUninstallMsiCompatiblePackage( + __in HANDLE hPipe, + __in BURN_CACHE* pCache, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackageId = NULL; + LPWSTR sczCompatiblePackageId = NULL; + HWND hwndParent = NULL; + BOOL fRollback = 0; + BURN_EXECUTE_ACTION executeAction = { }; + BURN_PACKAGE* pPackage = NULL; + BURN_COMPATIBLE_PACKAGE* pCompatiblePackage = NULL; + BOOTSTRAPPER_APPLY_RESTART msiRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE; + + // Deserialize message data. + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback); + ExitOnFailure(hr, "Failed to read rollback flag."); + + hr = BuffReadString(pbData, cbData, &iData, &sczPackageId); + ExitOnFailure(hr, "Failed to read MSI package id."); + + hr = BuffReadString(pbData, cbData, &iData, &sczCompatiblePackageId); + ExitOnFailure(hr, "Failed to read MSI compatible package id."); + + hr = BuffReadPointer(pbData, cbData, &iData, (DWORD_PTR*)&hwndParent); + ExitOnFailure(hr, "Failed to read parent hwnd."); + + hr = BuffReadString(pbData, cbData, &iData, &executeAction.uninstallMsiCompatiblePackage.sczLogPath); + ExitOnFailure(hr, "Failed to read package log."); + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + hr = PackageFindById(pPackages, sczPackageId, &pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", sczPackageId); + + executeAction.uninstallMsiCompatiblePackage.pParentPackage = pPackage; + pCompatiblePackage = &pPackage->compatiblePackage; + + if (!pCompatiblePackage->fDetected || BURN_PACKAGE_TYPE_MSI != pCompatiblePackage->type || !pCompatiblePackage->compatibleEntry.sczId) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package '%ls' has no compatible MSI package", sczPackageId); + } + + if (!sczCompatiblePackageId || !*sczCompatiblePackageId || + CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pCompatiblePackage->compatibleEntry.sczId, -1, sczCompatiblePackageId, -1)) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package '%ls' has no compatible package with id: %ls", sczPackageId, sczCompatiblePackageId); + } + + // Uninstall MSI compatible package. + hr = MsiEngineUninstallCompatiblePackage(hwndParent, &executeAction, pCache, pVariables, fRollback, MsiExecuteMessageHandler, hPipe, &msiRestart); + ExitOnFailure(hr, "Failed to execute MSI package."); + +LExit: + ReleaseStr(sczPackageId); + ReleaseStr(sczCompatiblePackageId); + PlanUninitializeExecuteAction(&executeAction); + + if (SUCCEEDED(hr)) + { + if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == msiRestart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == msiRestart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED); + } + } + + return hr; +} + static HRESULT OnExecutePackageProviderAction( __in BURN_PACKAGES* pPackages, __in BURN_RELATED_BUNDLES* pRelatedBundles, @@ -3258,6 +3502,53 @@ LExit: return nResult; } +static HRESULT OnCleanCompatiblePackage( + __in BURN_CACHE* pCache, + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackageId = NULL; + LPWSTR sczCompatiblePackageId = NULL; + BURN_PACKAGE* pPackage = NULL; + BURN_COMPATIBLE_PACKAGE* pCompatiblePackage = NULL; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackageId); + ExitOnFailure(hr, "Failed to read package id."); + + hr = BuffReadString(pbData, cbData, &iData, &sczCompatiblePackageId); + ExitOnFailure(hr, "Failed to read compatible package id."); + + hr = PackageFindById(pPackages, sczPackageId, &pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", sczPackageId); + + pCompatiblePackage = &pPackage->compatiblePackage; + + if (!pCompatiblePackage->fDetected || !pCompatiblePackage->compatibleEntry.sczId || !pCompatiblePackage->sczCacheId || !*pCompatiblePackage->sczCacheId) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package '%ls' has no compatible package to clean.", sczPackageId); + } + + if (!sczCompatiblePackageId || !*sczCompatiblePackageId || + CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pCompatiblePackage->compatibleEntry.sczId, -1, sczCompatiblePackageId, -1)) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Package '%ls' has no compatible package with id: %ls", sczPackageId, sczCompatiblePackageId); + } + + // Remove the package from the cache. + hr = CacheRemovePackage(pCache, TRUE, pCompatiblePackage->compatibleEntry.sczId, pCompatiblePackage->sczCacheId); + ExitOnFailure(hr, "Failed to remove from cache compatible package: %ls", pCompatiblePackage->compatibleEntry.sczId); + +LExit: + ReleaseStr(sczPackageId); + ReleaseStr(sczCompatiblePackageId); + return hr; +} + static HRESULT OnCleanPackage( __in BURN_CACHE* pCache, __in BURN_PACKAGES* pPackages, diff --git a/src/burn/engine/elevation.h b/src/burn/engine/elevation.h index 0e63c687..b4d0ca83 100644 --- a/src/burn/engine/elevation.h +++ b/src/burn/engine/elevation.h @@ -127,6 +127,16 @@ HRESULT ElevationExecuteMsuPackage( __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart ); +HRESULT ElevationUninstallMsiCompatiblePackage( + __in HANDLE hPipe, + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); HRESULT ElevationExecutePackageProviderAction( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction @@ -135,12 +145,9 @@ HRESULT ElevationExecutePackageDependencyAction( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction ); -HRESULT ElevationLaunchElevatedChild( +HRESULT ElevationCleanCompatiblePackage( __in HANDLE hPipe, - __in BURN_PACKAGE* pPackage, - __in LPCWSTR wzPipeName, - __in LPCWSTR wzPipeToken, - __out DWORD* pdwChildPid + __in BURN_PACKAGE* pPackage ); HRESULT ElevationCleanPackage( __in HANDLE hPipe, diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc index 5c1b0b69..6ce11564 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -262,6 +262,13 @@ Detected forward compatible bundle: %1!ls!, type: %2!hs!, scope: %3!hs!, version . MessageId=108 +Severity=Success +SymbolicName=MSG_DETECTED_COMPATIBLE_PACKAGE_FROM_PROVIDER +Language=English +Detected compatible package: %1!ls!, provider: %2!ls!, installed: %3!ls!, version: %4!ls!, chained: %5!ls! +. + +MessageId=109 Severity=Warning SymbolicName=MSG_DETECT_RELATED_BUNDLE_NOT_CACHED Language=English @@ -429,6 +436,13 @@ Language=English Plan skipped related bundle: %1!ls!, type: %2!hs!, because it was previously scheduled. . +MessageId=215 +Severity=Success +SymbolicName=MSG_PLANNED_ORPHAN_PACKAGE_FROM_PROVIDER +Language=English +Will remove orphan package: %1!ls!, installed: %2!ls!, chained: %3!ls! +. + MessageId=216 Severity=Success SymbolicName=MSG_PLAN_SKIPPED_RELATED_BUNDLE_EMBEDDED_BUNDLE_NEWER @@ -604,7 +618,6 @@ Language=English Failed to layout container: %2!ls! to layout directory: %3!ls!, error: %1!ls!. . - MessageId=317 Severity=Error SymbolicName=MSG_FAILED_LAYOUT_PAYLOAD @@ -927,7 +940,6 @@ Language=English Calculating whether to keep registration . - MessageId=374 Severity=Success SymbolicName=MSG_POST_APPLY_PACKAGE @@ -1005,6 +1017,13 @@ Language=English Skipping the rest of non-vital rollback boundary, id: %1!ls! . +MessageId=390 +Severity=Success +SymbolicName=MSG_APPLYING_ORPHAN_COMPATIBLE_PACKAGE +Language=English +Applying %1!hs! compatible package: %2!ls!, parent package: %3!ls!, action: %4!hs!, arguments: '%5!ls!' +. + MessageId=399 Severity=Success SymbolicName=MSG_APPLY_COMPLETE diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp index 1e436f68..7c048523 100644 --- a/src/burn/engine/logging.cpp +++ b/src/burn/engine/logging.cpp @@ -227,6 +227,49 @@ extern "C" void LoggingIncrementPackageSequence() ++vdwPackageSequence; } +extern "C" HRESULT LoggingSetCompatiblePackageVariable( + __in BURN_PACKAGE* pPackage, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __out_opt LPWSTR* psczLogPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczLogPathVariable = NULL; + LPWSTR sczLogPath = NULL; + + // Make sure that no package log files are created when logging has been disabled via Log element. + if (BURN_LOGGING_STATE_DISABLED == pLog->state) + { + ExitFunction(); + } + + if (pPackage->sczLogPathVariable && *pPackage->sczLogPathVariable) + { + // Format a suitable log path variable from the original package. + hr = StrAllocFormatted(&sczLogPathVariable, L"%ls_Compatible", pPackage->sczLogPathVariable); + ExitOnFailure(hr, "Failed to format log path variable for compatible package."); + + hr = StrAllocFormatted(&sczLogPath, L"%ls_%03u_%ls_%ls.%ls", pLog->sczPrefix, vdwPackageSequence, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pLog->sczExtension); + ExitOnFailure(hr, "Failed to allocate path for package log."); + + hr = VariableSetString(pVariables, sczLogPathVariable, sczLogPath, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set log path into variable."); + + if (psczLogPath) + { + hr = StrAllocString(psczLogPath, sczLogPath, 0); + ExitOnFailure(hr, "Failed to copy package log path."); + } + } + +LExit: + ReleaseStr(sczLogPathVariable); + ReleaseStr(sczLogPath); + + return hr; +} + extern "C" HRESULT LoggingSetPackageVariable( __in BURN_PACKAGE* pPackage, __in_z_opt LPCWSTR wzSuffix, diff --git a/src/burn/engine/logging.h b/src/burn/engine/logging.h index 11f676b3..ef603931 100644 --- a/src/burn/engine/logging.h +++ b/src/burn/engine/logging.h @@ -53,6 +53,13 @@ void LoggingOpenFailed(); void LoggingIncrementPackageSequence(); +HRESULT LoggingSetCompatiblePackageVariable( + __in BURN_PACKAGE* pPackage, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __out_opt LPWSTR* psczLogPath + ); + HRESULT LoggingSetPackageVariable( __in BURN_PACKAGE* pPackage, __in_z_opt LPCWSTR wzSuffix, diff --git a/src/burn/engine/msiengine.cpp b/src/burn/engine/msiengine.cpp index 87ae77e9..68582d29 100644 --- a/src/burn/engine/msiengine.cpp +++ b/src/burn/engine/msiengine.cpp @@ -506,6 +506,9 @@ extern "C" HRESULT MsiEngineDetectPackage( { BURN_RELATED_MSI* pRelatedMsi = &pPackage->Msi.rgRelatedMsis[i]; + ReleaseVerutilVersion(pVersion); + pVersion = NULL; + for (DWORD iProduct = 0; ; ++iProduct) { // get product @@ -708,6 +711,46 @@ extern "C" HRESULT MsiEngineDetectPackage( hr = DependencyDetectChainPackage(pPackage, pRegistration); ExitOnFailure(hr, "Failed to detect dependencies for MSI package."); + if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) + { + hr = MsiEngineDetectCompatiblePackage(pPackage); + ExitOnFailure(hr, "Failed to detect compatible package for MSI package."); + + if (BURN_PACKAGE_TYPE_MSI == pPackage->compatiblePackage.type) + { + LPCWSTR wzCompatibleProductCode = pPackage->compatiblePackage.compatibleEntry.sczId; + LPCWSTR wzCompatibleInstalledVersion = pPackage->compatiblePackage.Msi.sczVersion; + + ReleaseVerutilVersion(pVersion); + pVersion = NULL; + + hr = VerParseVersion(wzCompatibleInstalledVersion, 0, FALSE, &pVersion); + ExitOnFailure(hr, "Failed to parse dependency version: '%ls' for ProductCode: %ls", wzCompatibleInstalledVersion, wzCompatibleProductCode); + + if (pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_DETECTED_MSI_PACKAGE_INVALID_VERSION, wzCompatibleProductCode, wzCompatibleInstalledVersion); + } + + pPackage->compatiblePackage.Msi.pVersion = pVersion; + pVersion = NULL; + + // compare versions + hr = VerCompareParsedVersions(pPackage->Msi.pVersion, pPackage->compatiblePackage.Msi.pVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare version '%ls' to dependency version: '%ls'", pPackage->Msi.pVersion->sczVersion, wzCompatibleInstalledVersion); + + if (nCompareResult < 0) + { + pPackage->compatiblePackage.fPlannable = TRUE; + + LogId(REPORT_STANDARD, MSG_DETECTED_COMPATIBLE_PACKAGE_FROM_PROVIDER, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczProviderKey, wzCompatibleProductCode, wzCompatibleInstalledVersion, pPackage->Msi.sczProductCode); + + hr = UserExperienceOnDetectCompatibleMsiPackage(pUserExperience, pPackage->sczId, wzCompatibleProductCode, pPackage->compatiblePackage.Msi.pVersion); + ExitOnRootFailure(hr, "BA aborted detect compatible MSI package."); + } + } + } + LExit: ReleaseStr(sczInstalledLanguage); ReleaseStr(sczInstalledVersion); @@ -716,8 +759,49 @@ LExit: return hr; } +extern "C" HRESULT MsiEngineDetectCompatiblePackage( + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + LPWSTR sczVersion = NULL; + LPWSTR sczCacheId = NULL; + LPCWSTR wzCompatibleProductCode = pPackage->compatiblePackage.compatibleEntry.sczId; + + if (!pPackage->compatiblePackage.fDetected) + { + ExitFunction(); + } + + hr = WiuGetProductInfoEx(wzCompatibleProductCode, NULL, pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczVersion); + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr || HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to get product information for compatible ProductCode: %ls", wzCompatibleProductCode); + + // Assume the package used the default cache ID generation from the binder. + hr = StrAllocFormatted(&sczCacheId, L"%lsv%ls", wzCompatibleProductCode, sczVersion); + ExitOnFailure(hr, "Failed to format cache ID for compatible package."); + + pPackage->compatiblePackage.sczCacheId = sczCacheId; + sczCacheId = NULL; + + pPackage->compatiblePackage.Msi.sczVersion = sczVersion; + sczVersion = NULL; + + pPackage->compatiblePackage.type = BURN_PACKAGE_TYPE_MSI; + +LExit: + ReleaseStr(sczVersion); + ReleaseStr(sczCacheId); + + return hr; +} + extern "C" HRESULT MsiEnginePlanInitializePackage( __in BURN_PACKAGE* pPackage, + __in BOOTSTRAPPER_ACTION overallAction, __in BURN_VARIABLES* pVariables, __in BURN_USER_EXPERIENCE* pUserExperience ) @@ -747,6 +831,18 @@ extern "C" HRESULT MsiEnginePlanInitializePackage( } } + if (pPackage->compatiblePackage.fPlannable) + { + Assert(BURN_PACKAGE_TYPE_MSI == pPackage->compatiblePackage.type); + + pPackage->compatiblePackage.fDefaultRequested = BOOTSTRAPPER_ACTION_UNINSTALL == overallAction; + pPackage->compatiblePackage.fRequested = pPackage->compatiblePackage.fDefaultRequested; + + hr = UserExperienceOnPlanCompatibleMsiPackageBegin(pUserExperience, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->compatiblePackage.Msi.pVersion, &pPackage->compatiblePackage.fRequested); + UserExperienceOnPlanCompatibleMsiPackageComplete(pUserExperience, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, hr, pPackage->compatiblePackage.fRequested); + ExitOnRootFailure(hr, "BA aborted plan compatible MSI package begin."); + } + LExit: return hr; } @@ -989,6 +1085,17 @@ extern "C" HRESULT MsiEnginePlanAddPackage( LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, &pAction->msiPackage.sczLogPath); // ignore errors. pAction->msiPackage.dwLoggingAttributes = pLog->dwAttributes; } + else if (pPackage->compatiblePackage.fRemove) + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE; + pAction->uninstallMsiCompatiblePackage.pParentPackage = pPackage; + pAction->uninstallMsiCompatiblePackage.dwLoggingAttributes = pLog->dwAttributes; + + LoggingSetCompatiblePackageVariable(pPackage, pLog, pVariables, &pAction->uninstallMsiCompatiblePackage.sczLogPath); // ignore errors. + } LExit: ReleaseMem(rgFeatureActions); @@ -1246,6 +1353,80 @@ LExit: return hr; } +extern "C" HRESULT MsiEngineUninstallCompatiblePackage( + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_CACHE* /*pCache*/, + __in BURN_VARIABLES* /*pVariables*/, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + WIU_MSI_EXECUTE_CONTEXT context = { }; + WIU_RESTART restart = WIU_RESTART_NONE; + LPWSTR sczProperties = NULL; + BOOTSTRAPPER_ACTION_STATE action = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + BURN_MSI_PROPERTY burnMsiProperty = BURN_MSI_PROPERTY_NONE; + BOOTSTRAPPER_MSI_FILE_VERSIONING fileVersioning = BOOTSTRAPPER_MSI_FILE_VERSIONING_MISSING_OR_OLDER; + BURN_PACKAGE* pParentPackage = pExecuteAction->uninstallMsiCompatiblePackage.pParentPackage; + BURN_COMPATIBLE_PROVIDER_ENTRY* pCompatibleEntry = &pExecuteAction->uninstallMsiCompatiblePackage.pParentPackage->compatiblePackage.compatibleEntry; + + // Default to "verbose" logging and set extra debug mode only if explicitly required. + DWORD dwLogMode = WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE; + + if (pExecuteAction->uninstallMsiCompatiblePackage.dwLoggingAttributes & BURN_LOGGING_ATTRIBUTE_EXTRADEBUG) + { + dwLogMode |= INSTALLLOGMODE_EXTRADEBUG; + } + + hr = WiuInitializeExternalUI(pfnMessageHandler, INSTALLUILEVEL_NONE, hwndParent, pvContext, fRollback, &context); + ExitOnFailure(hr, "Failed to initialize external UI handler."); + + if (pExecuteAction->uninstallMsiCompatiblePackage.sczLogPath && *pExecuteAction->uninstallMsiCompatiblePackage.sczLogPath) + { + hr = WiuEnableLog(dwLogMode, pExecuteAction->uninstallMsiCompatiblePackage.sczLogPath, INSTALLLOGATTRIBUTES_APPEND); + ExitOnFailure(hr, "Failed to enable logging for compatible package: %ls to: %ls", pCompatibleEntry->sczId, pExecuteAction->uninstallMsiCompatiblePackage.sczLogPath); + } + + hr = MsiEngineConcatBurnProperties(action, burnMsiProperty, fileVersioning, TRUE, FALSE, &sczProperties); + ExitOnFailure(hr, "Failed to add action property to argument string."); + + LogId(REPORT_STANDARD, MSG_APPLYING_ORPHAN_COMPATIBLE_PACKAGE, LoggingRollbackOrExecute(fRollback), pCompatibleEntry->sczId, pParentPackage->sczId, LoggingActionStateToString(action), sczProperties ? sczProperties : L""); + + hr = WiuConfigureProductEx(pCompatibleEntry->sczId, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT, sczProperties, &restart); + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) + { + LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pCompatibleEntry->sczId); + hr = S_OK; + } + ExitOnFailure(hr, "Failed to uninstall compatible MSI package."); + +LExit: + WiuUninitializeExternalUI(&context); + + StrSecureZeroFreeString(sczProperties); + + switch (restart) + { + case WIU_RESTART_NONE: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + break; + + case WIU_RESTART_REQUIRED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + break; + + case WIU_RESTART_INITIATED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; + break; + } + + return hr; +} + extern "C" HRESULT MsiEngineConcatBurnProperties( __in BOOTSTRAPPER_ACTION_STATE action, __in BURN_MSI_PROPERTY actionMsiProperty, diff --git a/src/burn/engine/msiengine.h b/src/burn/engine/msiengine.h index fbb251e0..bc356fab 100644 --- a/src/burn/engine/msiengine.h +++ b/src/burn/engine/msiengine.h @@ -35,8 +35,12 @@ HRESULT MsiEngineDetectPackage( __in BURN_REGISTRATION* pRegistration, __in BURN_USER_EXPERIENCE* pUserExperience ); +HRESULT MsiEngineDetectCompatiblePackage( + __in BURN_PACKAGE* pPackage + ); HRESULT MsiEnginePlanInitializePackage( __in BURN_PACKAGE* pPackage, + __in BOOTSTRAPPER_ACTION overallAction, __in BURN_VARIABLES* pVariables, __in BURN_USER_EXPERIENCE* pUserExperience ); @@ -71,6 +75,16 @@ HRESULT MsiEngineExecutePackage( __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart ); +HRESULT MsiEngineUninstallCompatiblePackage( + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_CACHE* pCache, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); HRESULT MsiEngineConcatBurnProperties( __in BOOTSTRAPPER_ACTION_STATE action, __in BURN_MSI_PROPERTY actionMsiProperty, diff --git a/src/burn/engine/package.cpp b/src/burn/engine/package.cpp index d9087f79..d9f92d3d 100644 --- a/src/burn/engine/package.cpp +++ b/src/burn/engine/package.cpp @@ -366,6 +366,31 @@ extern "C" void PackageUninitialize( MsuEnginePackageUninitialize(pPackage); // TODO: Modularization break; } + + PackageUninitializeCompatible(&pPackage->compatiblePackage); +} + +extern "C" void PackageUninitializeCompatible( + __in BURN_COMPATIBLE_PACKAGE* pCompatiblePackage + ) +{ + ReleaseStr(pCompatiblePackage->compatibleEntry.sczId); + ReleaseStr(pCompatiblePackage->compatibleEntry.sczName); + ReleaseStr(pCompatiblePackage->compatibleEntry.sczProviderKey); + ReleaseStr(pCompatiblePackage->compatibleEntry.sczVersion); + + ReleaseStr(pCompatiblePackage->sczCacheId); + + switch (pCompatiblePackage->type) + { + case BURN_PACKAGE_TYPE_MSI: + ReleaseStr(pCompatiblePackage->Msi.sczVersion); + ReleaseVerutilVersion(pCompatiblePackage->Msi.pVersion); + break; + } + + // clear struct + memset(pCompatiblePackage, 0, sizeof(BURN_COMPATIBLE_PACKAGE)); } extern "C" void PackagesUninitialize( diff --git a/src/burn/engine/package.h b/src/burn/engine/package.h index 10b5f33c..934355e1 100644 --- a/src/burn/engine/package.h +++ b/src/burn/engine/package.h @@ -147,6 +147,14 @@ typedef struct _BURN_MSIFEATURE BOOTSTRAPPER_FEATURE_ACTION rollback; // only valid during Plan. } BURN_MSIFEATURE; +typedef struct _BURN_COMPATIBLE_PROVIDER_ENTRY +{ + LPWSTR sczProviderKey; + LPWSTR sczId; + LPWSTR sczName; + LPWSTR sczVersion; +} BURN_COMPATIBLE_PROVIDER_ENTRY; + typedef struct _BURN_RELATED_MSI { LPWSTR sczUpgradeCode; @@ -206,6 +214,27 @@ typedef struct _BURN_PATCH_TARGETCODE BURN_PATCH_TARGETCODE_TYPE type; } BURN_PATCH_TARGETCODE; +typedef struct _BURN_COMPATIBLE_PACKAGE +{ + BOOL fDetected; + BOOL fPlannable; + BOOL fDefaultRequested; + BOOL fRequested; + BOOL fRemove; + LPWSTR sczCacheId; + BURN_COMPATIBLE_PROVIDER_ENTRY compatibleEntry; + + BURN_PACKAGE_TYPE type; + union + { + struct + { + LPWSTR sczVersion; + VERUTIL_VERSION* pVersion; + } Msi; + }; +} BURN_COMPATIBLE_PACKAGE; + typedef struct _BURN_PACKAGE { LPWSTR sczId; @@ -259,6 +288,8 @@ typedef struct _BURN_PACKAGE BURN_DEPENDENCY_PROVIDER* rgDependencyProviders; DWORD cDependencyProviders; + BURN_COMPATIBLE_PACKAGE compatiblePackage; + BURN_PACKAGE_TYPE type; union { @@ -371,6 +402,9 @@ HRESULT PackagesParseFromXml( void PackageUninitialize( __in BURN_PACKAGE* pPackage ); +void PackageUninitializeCompatible( + __in BURN_COMPATIBLE_PACKAGE* pCompatiblePackage + ); void PackagesUninitialize( __in BURN_PACKAGES* pPackages ); diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp index e0e9a82b..1dd78773 100644 --- a/src/burn/engine/plan.cpp +++ b/src/burn/engine/plan.cpp @@ -96,6 +96,10 @@ static HRESULT AppendRollbackCacheAction( __in BURN_PLAN* pPlan, __out BURN_CACHE_ACTION** ppCacheAction ); +static HRESULT AppendCleanAction( + __in BURN_PLAN* pPlan, + __out BURN_CLEAN_ACTION** ppCleanAction + ); static HRESULT ProcessPayloadGroup( __in BURN_PLAN* pPlan, __in BURN_PAYLOAD_GROUP* pPayloadGroup @@ -286,6 +290,10 @@ extern "C" void PlanUninitializeExecuteAction( case BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY: ReleaseStr(pExecuteAction->packageDependency.sczBundleProviderKey); break; + + case BURN_EXECUTE_ACTION_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE: + ReleaseStr(pExecuteAction->uninstallMsiCompatiblePackage.sczLogPath); + break; } } @@ -823,6 +831,11 @@ static HRESULT PlanPackagesHelper( BURN_PACKAGE* pPackage = rgPackages + iPackage; UserExperienceOnPlannedPackage(pUX, pPackage->sczId, pPackage->execute, pPackage->rollback, pPackage->fPlannedCache, pPackage->fPlannedUncache); + + if (pPackage->compatiblePackage.fPlannable) + { + UserExperienceOnPlannedCompatiblePackage(pUX, pPackage->sczId, pPackage->compatiblePackage.compatibleEntry.sczId, pPackage->compatiblePackage.fRemove); + } } LExit: @@ -878,7 +891,7 @@ static HRESULT InitializePackage( if (BURN_PACKAGE_TYPE_MSI == pPackage->type) { - hr = MsiEnginePlanInitializePackage(pPackage, pVariables, pUX); + hr = MsiEnginePlanInitializePackage(pPackage, pPlan->action, pVariables, pUX); ExitOnFailure(hr, "Failed to initialize plan package: %ls", pPackage->sczId); } @@ -918,7 +931,7 @@ static HRESULT ProcessPackage( } else { - if (BOOTSTRAPPER_REQUEST_STATE_NONE != pPackage->requested) + if (BOOTSTRAPPER_REQUEST_STATE_NONE != pPackage->requested || pPackage->compatiblePackage.fRequested) { // If the package is in a requested state, plan it. hr = PlanExecutePackage(fBundlePerMachine, pUX, pPlan, pPackage, pLog, pVariables); @@ -1151,7 +1164,7 @@ extern "C" HRESULT PlanExecutePackage( ExitOnFailure(hr, "Failed to complete plan dependency actions for package: %ls", pPackage->sczId); // If we are going to take any action on this package, add progress for it. - if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute || BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute || BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback || pPackage->compatiblePackage.fRemove) { LoggingIncrementPackageSequence(); @@ -1560,12 +1573,10 @@ extern "C" HRESULT PlanCleanPackage( if (fPlanCleanPackage) { - hr = MemEnsureArraySize(reinterpret_cast(&pPlan->rgCleanActions), pPlan->cCleanActions + 1, sizeof(BURN_CLEAN_ACTION), 5); - ExitOnFailure(hr, "Failed to grow plan's array of clean actions."); - - pCleanAction = pPlan->rgCleanActions + pPlan->cCleanActions; - ++pPlan->cCleanActions; + hr = AppendCleanAction(pPlan, &pCleanAction); + ExitOnFailure(hr, "Failed to append clean action to plan."); + pCleanAction->type = BURN_CLEAN_ACTION_TYPE_PACKAGE; pCleanAction->pPackage = pPackage; pPackage->fPlannedUncache = TRUE; @@ -1576,6 +1587,15 @@ extern "C" HRESULT PlanCleanPackage( } } + if (pPackage->compatiblePackage.fRemove) + { + hr = AppendCleanAction(pPlan, &pCleanAction); + ExitOnFailure(hr, "Failed to append clean action to plan."); + + pCleanAction->type = BURN_CLEAN_ACTION_TYPE_COMPATIBLE_PACKAGE; + pCleanAction->pPackage = pPackage; + } + LExit: return hr; } @@ -2229,6 +2249,24 @@ LExit: return hr; } +static HRESULT AppendCleanAction( + __in BURN_PLAN* pPlan, + __out BURN_CLEAN_ACTION** ppCleanAction + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(&pPlan->rgCleanActions), pPlan->cCleanActions, 1, sizeof(BURN_CLEAN_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of clean actions."); + + + *ppCleanAction = pPlan->rgCleanActions + pPlan->cCleanActions; + ++pPlan->cCleanActions; + +LExit: + return hr; +} + static HRESULT ProcessPayloadGroup( __in BURN_PLAN* pPlan, __in BURN_PAYLOAD_GROUP* pPayloadGroup @@ -2505,6 +2543,8 @@ static HRESULT CalculateExecuteActions( ExitOnFailure(hr, "Invalid package type."); } + pPackage->compatiblePackage.fRemove = pPackage->compatiblePackage.fPlannable && pPackage->compatiblePackage.fRequested; + LExit: return hr; } @@ -2622,6 +2662,10 @@ static void ExecuteActionLog( } break; + case BURN_EXECUTE_ACTION_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: UNINSTALL_MSI_COMPATIBLE_PACKAGE package id: %ls, compatible package id: %ls, cache id: %ls, log path: %ls, logging attrib: %u", wzBase, iAction, pAction->uninstallMsiCompatiblePackage.pParentPackage->sczId, pAction->uninstallMsiCompatiblePackage.pParentPackage->compatiblePackage.compatibleEntry.sczId, pAction->uninstallMsiCompatiblePackage.pParentPackage->compatiblePackage.sczCacheId, pAction->uninstallMsiCompatiblePackage.sczLogPath, pAction->uninstallMsiCompatiblePackage.dwLoggingAttributes); + break; + case BURN_EXECUTE_ACTION_TYPE_MSP_TARGET: LogStringLine(PlanDumpLevel, "%ls action[%u]: MSP_TARGET package id: %ls, action: %hs, target product code: %ls, target per-machine: %hs, action msi property: %ls, ui level: %u, disable externaluihandler: %hs, file versioning: %hs, log path: %ls", wzBase, iAction, pAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pAction->mspTarget.action), pAction->mspTarget.sczTargetProductCode, LoggingBoolToString(pAction->mspTarget.fPerMachineTarget), LoggingBurnMsiPropertyToString(pAction->mspTarget.actionMsiProperty), pAction->mspTarget.uiLevel, LoggingBoolToString(pAction->mspTarget.fDisableExternalUiHandler), LoggingMsiFileVersioningToString(pAction->mspTarget.fileVersioning), pAction->mspTarget.sczLogPath); for (DWORD j = 0; j < pAction->mspTarget.cOrderedPatches; ++j) @@ -2669,6 +2713,27 @@ static void ExecuteActionLog( } } +static void CleanActionLog( + __in DWORD iAction, + __in BURN_CLEAN_ACTION* pAction + ) +{ + switch (pAction->type) + { + case BURN_CLEAN_ACTION_TYPE_COMPATIBLE_PACKAGE: + LogStringLine(PlanDumpLevel, " Clean action[%u]: CLEAN_COMPATIBLE_PACKAGE package id: %ls", iAction, pAction->pPackage->sczId); + break; + + case BURN_CLEAN_ACTION_TYPE_PACKAGE: + LogStringLine(PlanDumpLevel, " Clean action[%u]: CLEAN_PACKAGE package id: %ls", iAction, pAction->pPackage->sczId); + break; + + default: + AssertSz(FALSE, "Unknown clean action type."); + break; + } +} + extern "C" void PlanDump( __in BURN_PLAN* pPlan ) @@ -2709,7 +2774,7 @@ extern "C" void PlanDump( for (DWORD i = 0; i < pPlan->cCleanActions; ++i) { - LogStringLine(PlanDumpLevel, " Clean action[%u]: CLEAN_PACKAGE package id: %ls", i, pPlan->rgCleanActions[i].pPackage->sczId); + CleanActionLog(i, pPlan->rgCleanActions + i); } for (DWORD i = 0; i < pPlan->cPlannedProviders; ++i) diff --git a/src/burn/engine/plan.h b/src/burn/engine/plan.h index 6be19a10..9bf72828 100644 --- a/src/burn/engine/plan.h +++ b/src/burn/engine/plan.h @@ -61,12 +61,13 @@ enum BURN_EXECUTE_ACTION_TYPE BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY_END, BURN_EXECUTE_ACTION_TYPE_BEGIN_MSI_TRANSACTION, BURN_EXECUTE_ACTION_TYPE_COMMIT_MSI_TRANSACTION, + BURN_EXECUTE_ACTION_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE, }; enum BURN_CLEAN_ACTION_TYPE { BURN_CLEAN_ACTION_TYPE_NONE, - BURN_CLEAN_ACTION_TYPE_BUNDLE, + BURN_CLEAN_ACTION_TYPE_COMPATIBLE_PACKAGE, BURN_CLEAN_ACTION_TYPE_PACKAGE, }; @@ -227,11 +228,18 @@ typedef struct _BURN_EXECUTE_ACTION { BURN_ROLLBACK_BOUNDARY* pRollbackBoundary; } msiTransaction; + struct + { + BURN_PACKAGE* pParentPackage; + LPWSTR sczLogPath; + DWORD dwLoggingAttributes; + } uninstallMsiCompatiblePackage; }; } BURN_EXECUTE_ACTION; typedef struct _BURN_CLEAN_ACTION { + BURN_CLEAN_ACTION_TYPE type; BURN_PACKAGE* pPackage; } BURN_CLEAN_ACTION; diff --git a/src/burn/engine/registration.cpp b/src/burn/engine/registration.cpp index a5b061eb..c76dde53 100644 --- a/src/burn/engine/registration.cpp +++ b/src/burn/engine/registration.cpp @@ -423,6 +423,10 @@ extern "C" void RegistrationUninitialize( ReleaseStr(pRegistration->sczBundlePackageAncestors); RelatedBundlesUninitialize(&pRegistration->relatedBundles); + if (pRegistration->rgDependents) + { + ReleaseDependencyArray(pRegistration->rgDependents, pRegistration->cDependents); + } // clear struct memset(pRegistration, 0, sizeof(BURN_REGISTRATION)); } diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp index 6ea16905..1439f5f2 100644 --- a/src/burn/engine/userexperience.cpp +++ b/src/burn/engine/userexperience.cpp @@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad( args.pCommand = pCommand; args.pfnBootstrapperEngineProc = EngineForApplicationProc; args.pvBootstrapperEngineProcContext = pEngineContext; - args.qwEngineAPIVersion = MAKEQWORDVERSION(2021, 12, 30, 0); + args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 1, 10, 0); results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); @@ -1015,6 +1015,36 @@ LExit: return hr; } +EXTERN_C BAAPI UserExperienceOnDetectCompatibleMsiPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in VERUTIL_VERSION* pCompatiblePackageVersion + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTCOMPATIBLEMSIPACKAGE_ARGS args = { }; + BA_ONDETECTCOMPATIBLEMSIPACKAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzCompatiblePackageId = wzCompatiblePackageId; + args.wzCompatiblePackageVersion = pCompatiblePackageVersion->sczVersion; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTCOMPATIBLEMSIPACKAGE, &args, &results); + ExitOnFailure(hr, "BA OnDetectCompatibleMsiPackage failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + EXTERN_C BAAPI UserExperienceOnDetectComplete( __in BURN_USER_EXPERIENCE* pUserExperience, __in HRESULT hrStatus, @@ -1798,6 +1828,67 @@ LExit: return hr; } +EXTERN_C BAAPI UserExperienceOnPlanCompatibleMsiPackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in VERUTIL_VERSION* pCompatiblePackageVersion, + __inout BOOL* pfRequested + ) +{ + HRESULT hr = S_OK; + BA_ONPLANCOMPATIBLEMSIPACKAGEBEGIN_ARGS args = { }; + BA_ONPLANCOMPATIBLEMSIPACKAGEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzCompatiblePackageId = wzCompatiblePackageId; + args.wzCompatiblePackageVersion = pCompatiblePackageVersion->sczVersion; + args.fRecommendedRemove = *pfRequested; + + results.cbSize = sizeof(results); + results.fRequestRemove = *pfRequested; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANCOMPATIBLEMSIPACKAGEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnPlanCompatibleMsiPackageBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pfRequested = results.fRequestRemove; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanCompatibleMsiPackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in HRESULT hrStatus, + __in BOOL fRequested + ) +{ + HRESULT hr = S_OK; + BA_ONPLANCOMPATIBLEMSIPACKAGECOMPLETE_ARGS args = { }; + BA_ONPLANCOMPATIBLEMSIPACKAGECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzCompatiblePackageId = wzCompatiblePackageId; + args.hrStatus = hrStatus; + args.fRequestedRemove = fRequested; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANCOMPATIBLEMSIPACKAGECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnPlanCompatibleMsiPackageComplete failed."); + +LExit: + return hr; +} + EXTERN_C BAAPI UserExperienceOnPlanMsiFeature( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, @@ -1932,6 +2023,31 @@ LExit: return hr; } +EXTERN_C BAAPI UserExperienceOnPlannedCompatiblePackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in BOOL fRemove + ) +{ + HRESULT hr = S_OK; + BA_ONPLANNEDCOMPATIBLEPACKAGE_ARGS args = { }; + BA_ONPLANNEDCOMPATIBLEPACKAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzCompatiblePackageId = wzCompatiblePackageId; + args.fRemove = fRemove; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANNEDCOMPATIBLEPACKAGE, &args, &results); + ExitOnFailure(hr, "BA OnPlannedCompatiblePackage failed."); + +LExit: + return hr; +} + EXTERN_C BAAPI UserExperienceOnPlannedPackage( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h index e4c5d3ee..e8341120 100644 --- a/src/burn/engine/userexperience.h +++ b/src/burn/engine/userexperience.h @@ -248,6 +248,12 @@ BAAPI UserExperienceOnDetectBegin( __in BOOL fInstalled, __in DWORD cPackages ); +BAAPI UserExperienceOnDetectCompatibleMsiPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in VERUTIL_VERSION* pCompatiblePackageVersion + ); BAAPI UserExperienceOnDetectComplete( __in BURN_USER_EXPERIENCE* pUserExperience, __in HRESULT hrStatus, @@ -414,6 +420,20 @@ BAAPI UserExperienceOnPlanBegin( __in BURN_USER_EXPERIENCE* pUserExperience, __in DWORD cPackages ); +BAAPI UserExperienceOnPlanCompatibleMsiPackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in VERUTIL_VERSION* pCompatiblePackageVersion, + __inout BOOL* pfRequested + ); +BAAPI UserExperienceOnPlanCompatibleMsiPackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in HRESULT hrStatus, + __in BOOL fRequested + ); BAAPI UserExperienceOnPlanComplete( __in BURN_USER_EXPERIENCE* pUserExperience, __in HRESULT hrStatus @@ -443,6 +463,12 @@ BAAPI UserExperienceOnPlanMsiPackage( __inout BOOL* pfDisableExternalUiHandler, __inout BOOTSTRAPPER_MSI_FILE_VERSIONING* pFileVersioning ); +BAAPI UserExperienceOnPlannedCompatiblePackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCompatiblePackageId, + __in BOOL fRemove + ); BAAPI UserExperienceOnPlannedPackage( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp index 7e704c23..fc2b8472 100644 --- a/src/burn/test/BurnUnitTest/PlanTest.cpp +++ b/src/burn/test/BurnUnitTest/PlanTest.cpp @@ -287,6 +287,74 @@ namespace Bootstrapper ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageC", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); } + [Fact] + void OrphanCompatiblePackageTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectPackagesAsAbsent(pEngineState); + DetectCompatibleMsiPackage(pEngineState->packages.rgPackages, L"{C24F3903-38E7-4D44-8037-D9856B3C5046}", L"2.0.0.0"); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwEstimatedSize); + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateUninstallMsiCompatiblePackage(pPlan, fRollback, dwIndex++, L"PackageA", 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(1ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"PackageA"); + ValidateCleanCompatibleAction(pPlan, dwIndex++, L"PackageA"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{64633047-D172-4BBB-B202-64337D15C952}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + [Fact] void RelatedBundleMissingFromCacheTest() { @@ -1026,6 +1094,36 @@ namespace Bootstrapper } } + void DetectCompatibleMsiPackage(BURN_PACKAGE* pPackage, LPCWSTR wzProductCode, LPCWSTR wzVersion) + { + HRESULT hr = S_OK; + Assert(BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState); + Assert(0 < pPackage->cDependencyProviders); + BURN_DEPENDENCY_PROVIDER* pProvider = pPackage->rgDependencyProviders; + BURN_COMPATIBLE_PACKAGE* pCompatiblePackage = &pPackage->compatiblePackage; + pCompatiblePackage->fDetected = TRUE; + pCompatiblePackage->fPlannable = TRUE; + pCompatiblePackage->type = BURN_PACKAGE_TYPE_MSI; + + hr = StrAllocFormatted(&pCompatiblePackage->sczCacheId, L"%lsv%ls", wzProductCode, wzVersion); + NativeAssert::Succeeded(hr, "Failed to format cache id"); + + hr = StrAllocString(&pCompatiblePackage->Msi.sczVersion, wzVersion, 0); + NativeAssert::Succeeded(hr, "Failed to copy MSI version"); + + hr = VerParseVersion(wzVersion, 0, FALSE, &pCompatiblePackage->Msi.pVersion); + NativeAssert::Succeeded(hr, "Failed to parse MSI version"); + + hr = StrAllocString(&pCompatiblePackage->compatibleEntry.sczId, wzProductCode, 0); + NativeAssert::Succeeded(hr, "Failed to copy product code"); + + hr = StrAllocString(&pCompatiblePackage->compatibleEntry.sczVersion, wzVersion, 0); + NativeAssert::Succeeded(hr, "Failed to copy version"); + + hr = StrAllocString(&pCompatiblePackage->compatibleEntry.sczProviderKey, pProvider->sczKey, 0); + NativeAssert::Succeeded(hr, "Failed to copy provider key"); + } + void DetectPackageAsAbsent(BURN_PACKAGE* pPackage) { pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; @@ -1261,10 +1359,27 @@ namespace Bootstrapper __in DWORD dwIndex, __in LPCWSTR wzPackageId ) + { + BURN_CLEAN_ACTION* pCleanAction = ValidateCleanActionExists(pPlan, dwIndex); + Assert::Equal(BURN_CLEAN_ACTION_TYPE_PACKAGE, pCleanAction->type); + Assert::NotEqual((DWORD_PTR)0, (DWORD_PTR)pCleanAction->pPackage); + NativeAssert::StringEqual(wzPackageId, pCleanAction->pPackage->sczId); + } + + BURN_CLEAN_ACTION* ValidateCleanActionExists(BURN_PLAN* pPlan, DWORD dwIndex) { Assert::InRange(dwIndex + 1ul, 1ul, pPlan->cCleanActions); + return pPlan->rgCleanActions + dwIndex; + } - BURN_CLEAN_ACTION* pCleanAction = pPlan->rgCleanActions + dwIndex; + void ValidateCleanCompatibleAction( + __in BURN_PLAN* pPlan, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId + ) + { + BURN_CLEAN_ACTION* pCleanAction = ValidateCleanActionExists(pPlan, dwIndex); + Assert::Equal(BURN_CLEAN_ACTION_TYPE_COMPATIBLE_PACKAGE, pCleanAction->type); Assert::NotEqual((DWORD_PTR)0, (DWORD_PTR)pCleanAction->pPackage); NativeAssert::StringEqual(wzPackageId, pCleanAction->pPackage->sczId); } @@ -1537,6 +1652,22 @@ namespace Bootstrapper NativeAssert::StringEqual(wzKey, pProvider->sczKey); NativeAssert::StringEqual(wzName, pProvider->sczName); } + + void ValidateUninstallMsiCompatiblePackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in_z LPCWSTR wzPackageId, + __in DWORD dwLoggingAttributes + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_UNINSTALL_MSI_COMPATIBLE_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->uninstallMsiCompatiblePackage.pParentPackage->sczId); + NativeAssert::NotNull(pAction->msiPackage.sczLogPath); + Assert::Equal(dwLoggingAttributes, pAction->uninstallMsiCompatiblePackage.dwLoggingAttributes); + Assert::Equal(FALSE, pAction->fDeleted); + } }; } } -- cgit v1.2.3-55-g6feb