// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" // constants // structs struct POSSIBLE_TARGETPRODUCT { WCHAR wzProductCode[39]; LPWSTR pszLocalPackage; MSIINSTALLCONTEXT context; }; // internal function declarations static HRESULT GetPossibleTargetProductCodes( __in BURN_PACKAGES* pPackages, __deref_inout_ecount_opt(*pcPossibleTargetProductCodes) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProductCodes, __inout DWORD* pcPossibleTargetProductCodes ); static HRESULT AddPossibleTargetProduct( __in STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes, __in_z LPCWSTR wzPossibleTargetProductCode, __in MSIINSTALLCONTEXT context, __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, __inout DWORD* pcPossibleTargetProducts ); static HRESULT AddDetectedTargetProduct( __in BURN_PACKAGE* pPackage, __in DWORD dwOrder, __in_z LPCWSTR wzProductCode, __in MSIINSTALLCONTEXT context, __out DWORD* pdwTargetProductIndex ); static HRESULT AddMsiChainedPatch( __in BURN_PACKAGE* pPackage, __in BURN_PACKAGE* pMspPackage, __in DWORD dwMspTargetProductIndex, __out DWORD* pdwChainedPatchIndex ); static HRESULT DeterminePatchChainedTarget( __in BURN_PACKAGES* pPackages, __in BURN_PACKAGE* pMspPackage, __in LPCWSTR wzTargetProductCode, __in DWORD dwMspTargetProductIndex ); static HRESULT PlanTargetProduct( __in BOOTSTRAPPER_DISPLAY display, __in BURN_USER_EXPERIENCE* pUserExperience, __in BOOL fRollback, __in BURN_PLAN* pPlan, __in BURN_LOGGING* pLog, __in BURN_VARIABLES* pVariables, __in BOOTSTRAPPER_ACTION_STATE actionState, __in BURN_PACKAGE* pPackage, __in BURN_MSPTARGETPRODUCT* pTargetProduct ); // function definitions extern "C" HRESULT MspEngineParsePackageFromXml( __in IXMLDOMNode* pixnMspPackage, __in BURN_PACKAGE* pPackage ) { HRESULT hr = S_OK; // @PatchCode hr = XmlGetAttributeEx(pixnMspPackage, L"PatchCode", &pPackage->Msp.sczPatchCode); ExitOnFailure(hr, "Failed to get @PatchCode."); // @PatchXml hr = XmlGetAttributeEx(pixnMspPackage, L"PatchXml", &pPackage->Msp.sczApplicabilityXml); ExitOnFailure(hr, "Failed to get @PatchXml."); // Read properties. hr = MsiEngineParsePropertiesFromXml(pixnMspPackage, &pPackage->Msp.rgProperties, &pPackage->Msp.cProperties); ExitOnFailure(hr, "Failed to parse properties from XML."); LExit: return hr; } extern "C" void MspEnginePackageUninitialize( __in BURN_PACKAGE* pPackage ) { ReleaseStr(pPackage->Msp.sczPatchCode); ReleaseStr(pPackage->Msp.sczApplicabilityXml); // free properties if (pPackage->Msp.rgProperties) { for (DWORD i = 0; i < pPackage->Msp.cProperties; ++i) { BURN_MSIPROPERTY* pProperty = &pPackage->Msp.rgProperties[i]; ReleaseStr(pProperty->sczId); ReleaseStr(pProperty->sczValue); ReleaseStr(pProperty->sczRollbackValue); } MemFree(pPackage->Msp.rgProperties); } // free target products ReleaseMem(pPackage->Msp.rgTargetProducts); // clear struct memset(&pPackage->Msp, 0, sizeof(pPackage->Msp)); } extern "C" HRESULT MspEngineDetectInitialize( __in BURN_PACKAGES* pPackages ) { AssertSz(pPackages->cPatchInfo, "MspEngineDetectInitialize() should only be called if there are MSP packages."); HRESULT hr = S_OK; POSSIBLE_TARGETPRODUCT* rgPossibleTargetProducts = NULL; DWORD cPossibleTargetProducts = 0; #ifdef DEBUG // All patch info should be initialized to zero. for (DWORD i = 0; i < pPackages->cPatchInfo; ++i) { BURN_PACKAGE* pPackage = pPackages->rgPatchInfoToPackage[i]; Assert(!pPackage->Msp.cTargetProductCodes); Assert(!pPackage->Msp.rgTargetProducts); } #endif // Figure out which product codes to target on the machine. In the worst case all products on the machine // will be returned. hr = GetPossibleTargetProductCodes(pPackages, &rgPossibleTargetProducts, &cPossibleTargetProducts); ExitOnFailure(hr, "Failed to get possible target product codes."); // Loop through possible target products, testing the collective patch applicability against each product in // the appropriate context. Store the result with the appropriate patch package. for (DWORD iSearch = 0; iSearch < cPossibleTargetProducts; ++iSearch) { const POSSIBLE_TARGETPRODUCT* pPossibleTargetProduct = rgPossibleTargetProducts + iSearch; LogId(REPORT_STANDARD, MSG_DETECT_CALCULATE_PATCH_APPLICABILITY, pPossibleTargetProduct->wzProductCode, LoggingMsiInstallContext(pPossibleTargetProduct->context)); if (pPossibleTargetProduct->pszLocalPackage) { // Ignores current machine state to determine just patch applicability. // Superseded and obsolesced patches will be planned separately. hr = WiuDetermineApplicablePatches(pPossibleTargetProduct->pszLocalPackage, pPackages->rgPatchInfo, pPackages->cPatchInfo); } else { hr = WiuDeterminePatchSequence(pPossibleTargetProduct->wzProductCode, NULL, pPossibleTargetProduct->context, pPackages->rgPatchInfo, pPackages->cPatchInfo); } if (SUCCEEDED(hr)) { for (DWORD iPatchInfo = 0; iPatchInfo < pPackages->cPatchInfo; ++iPatchInfo) { hr = HRESULT_FROM_WIN32(pPackages->rgPatchInfo[iPatchInfo].uStatus); BURN_PACKAGE* pMspPackage = pPackages->rgPatchInfoToPackage[iPatchInfo]; Assert(BURN_PACKAGE_TYPE_MSP == pMspPackage->type); if (S_OK == hr) { // Note that we do add superseded and obsolete MSP packages. Package Detect and Plan will sort them out later. hr = MspEngineAddDetectedTargetProduct(pPackages, pMspPackage, pPackages->rgPatchInfo[iPatchInfo].dwOrder, pPossibleTargetProduct->wzProductCode, pPossibleTargetProduct->context); ExitOnFailure(hr, "Failed to add target product code to package: %ls", pMspPackage->sczId); } else { LogStringLine(REPORT_DEBUG, " 0x%x: Patch applicability failed for package: %ls", hr, pMspPackage->sczId); } } } else { LogId(REPORT_STANDARD, MSG_DETECT_FAILED_CALCULATE_PATCH_APPLICABILITY, pPossibleTargetProduct->wzProductCode, LoggingMsiInstallContext(pPossibleTargetProduct->context), hr); } hr = S_OK; // always reset so we test all possible target products. } LExit: if (rgPossibleTargetProducts) { for (DWORD i = 0; i < cPossibleTargetProducts; ++i) { ReleaseStr(rgPossibleTargetProducts[i].pszLocalPackage); } MemFree(rgPossibleTargetProducts); } return hr; } extern "C" HRESULT MspEngineAddDetectedTargetProduct( __in BURN_PACKAGES* pPackages, __in BURN_PACKAGE* pPackage, __in DWORD dwOrder, __in_z LPCWSTR wzProductCode, __in MSIINSTALLCONTEXT context ) { HRESULT hr = S_OK; DWORD dwTargetProductIndex = 0; hr = AddDetectedTargetProduct(pPackage, dwOrder, wzProductCode, context, &dwTargetProductIndex); ExitOnFailure(hr, "Failed to add detected target product."); hr = DeterminePatchChainedTarget(pPackages, pPackage, wzProductCode, dwTargetProductIndex); ExitOnFailure(hr, "Failed to determine patch chained target."); LExit: return hr; } extern "C" HRESULT MspEngineAddMissingSlipstreamTarget( __in BURN_PACKAGE* pMsiPackage, __in BURN_SLIPSTREAM_MSP* pSlipstreamMsp ) { HRESULT hr = S_OK; DWORD dwTargetProductIndex = 0; BURN_MSPTARGETPRODUCT* pTargetProduct = NULL; DWORD dwChainedPatchIndex = 0; hr = AddDetectedTargetProduct(pSlipstreamMsp->pMspPackage, 0, pMsiPackage->Msi.sczProductCode, pMsiPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, &dwTargetProductIndex); ExitOnFailure(hr, "Failed to add missing slipstream target."); pTargetProduct = pSlipstreamMsp->pMspPackage->Msp.rgTargetProducts + dwTargetProductIndex; pTargetProduct->fSlipstream = TRUE; pTargetProduct->fSlipstreamRequired = TRUE; pTargetProduct->pChainedTargetPackage = pMsiPackage; hr = AddMsiChainedPatch(pMsiPackage, pSlipstreamMsp->pMspPackage, dwTargetProductIndex, &dwChainedPatchIndex); ExitOnFailure(hr, "Failed to add chained patch."); pSlipstreamMsp->dwMsiChainedPatchIndex = dwChainedPatchIndex; LExit: return hr; } extern "C" HRESULT MspEngineDetectPackage( __in BURN_PACKAGE* pPackage, __in BURN_REGISTRATION* pRegistration, __in BURN_USER_EXPERIENCE* pUserExperience ) { HRESULT hr = S_OK; LPWSTR sczState = NULL; if (pPackage->fCanAffectRegistration) { pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; } if (0 == pPackage->Msp.cTargetProductCodes) { pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; } else { // Start the package state at the highest state then loop through all the // target product codes and end up setting the current state to the lowest // package state applied to the target product codes. pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) { BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; hr = WiuGetPatchInfoEx(pPackage->Msp.sczPatchCode, pTargetProduct->wzTargetProductCode, NULL, pTargetProduct->context, INSTALLPROPERTY_PATCHSTATE, &sczState); if (SUCCEEDED(hr)) { switch (*sczState) { case '1': pTargetProduct->fInstalled = TRUE; pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; break; case '2': pTargetProduct->fInstalled = TRUE; pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; break; case '4': pTargetProduct->fInstalled = TRUE; pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; break; default: pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; break; } } else if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PATCH) == hr || HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) { pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; hr = S_OK; } ExitOnFailure(hr, "Failed to get patch information for patch code: %ls, target product code: %ls", pPackage->Msp.sczPatchCode, pTargetProduct->wzTargetProductCode); if (pPackage->currentState > pTargetProduct->patchPackageState) { pPackage->currentState = pTargetProduct->patchPackageState; } if (pPackage->fCanAffectRegistration) { pTargetProduct->registrationState = pTargetProduct->fInstalled ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; if (pTargetProduct->fInstalled) { pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; } } hr = BACallbackOnDetectPatchTarget(pUserExperience, pPackage->sczId, pTargetProduct->wzTargetProductCode, pTargetProduct->patchPackageState); ExitOnRootFailure(hr, "BA aborted detect patch target."); } } hr = DependencyDetectChainPackage(pPackage, pRegistration); ExitOnFailure(hr, "Failed to detect dependencies for MSP package."); LExit: ReleaseStr(sczState); return hr; } extern "C" HRESULT MspEnginePlanInitializePackage( __in BURN_PACKAGE* pPackage, __in BURN_USER_EXPERIENCE* pUserExperience ) { HRESULT hr = S_OK; for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) { BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; if (!pTargetProduct->fInstalled && pTargetProduct->fSlipstreamRequired && BOOTSTRAPPER_REQUEST_STATE_PRESENT > pTargetProduct->pChainedTargetPackage->requested) { // There's no way to apply the patch if the target isn't installed. pTargetProduct->defaultRequested = pTargetProduct->requested = BOOTSTRAPPER_REQUEST_STATE_NONE; continue; } pTargetProduct->defaultRequested = pTargetProduct->requested = pPackage->requested; hr = BACallbackOnPlanPatchTarget(pUserExperience, pPackage->sczId, pTargetProduct->wzTargetProductCode, &pTargetProduct->requested); ExitOnRootFailure(hr, "BA aborted plan patch target."); } LExit: return hr; } // // PlanCalculate - calculates the execute and rollback state for the requested package state. // extern "C" HRESULT MspEnginePlanCalculatePackage( __in BURN_PACKAGE* pPackage, __in BOOL fInsideMsiTransaction ) { HRESULT hr = S_OK; BOOL fWillUninstallAll = TRUE; for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) { BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; // Calculate the execute action. switch (pTargetProduct->patchPackageState) { case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: switch (pTargetProduct->requested) { case BOOTSTRAPPER_REQUEST_STATE_REPAIR: execute = BOOTSTRAPPER_ACTION_STATE_REPAIR; fWillUninstallAll = FALSE; break; case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough; case BOOTSTRAPPER_REQUEST_STATE_CACHE: execute = !pPackage->fPermanent ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; break; case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; break; case BOOTSTRAPPER_REQUEST_STATE_FORCE_PRESENT: execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; fWillUninstallAll = FALSE; break; default: execute = BOOTSTRAPPER_ACTION_STATE_NONE; fWillUninstallAll = FALSE; break; } break; case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: switch (pTargetProduct->requested) { case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; case BOOTSTRAPPER_REQUEST_STATE_FORCE_PRESENT: __fallthrough; case BOOTSTRAPPER_REQUEST_STATE_REPAIR: execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; fWillUninstallAll = FALSE; break; case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; break; default: execute = BOOTSTRAPPER_ACTION_STATE_NONE; break; } break; default: switch (pTargetProduct->requested) { case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; break; case BOOTSTRAPPER_REQUEST_STATE_FORCE_PRESENT: execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; fWillUninstallAll = FALSE; break; default: execute = BOOTSTRAPPER_ACTION_STATE_NONE; if (pTargetProduct->fInstalled) { fWillUninstallAll = FALSE; } break; } break; } // Calculate the rollback action if there is an execute action. if (BOOTSTRAPPER_ACTION_STATE_NONE != execute && !fInsideMsiTransaction) { switch (pPackage->currentState) { case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: switch (pTargetProduct->requested) { case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; case BOOTSTRAPPER_REQUEST_STATE_ABSENT: rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; break; default: rollback = BOOTSTRAPPER_ACTION_STATE_NONE; break; } break; case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: __fallthrough; switch (pTargetProduct->requested) { case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; case BOOTSTRAPPER_REQUEST_STATE_FORCE_PRESENT: __fallthrough; case BOOTSTRAPPER_REQUEST_STATE_REPAIR: rollback = !pPackage->fPermanent ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; break; default: rollback = BOOTSTRAPPER_ACTION_STATE_NONE; break; } break; default: rollback = BOOTSTRAPPER_ACTION_STATE_NONE; break; } } pTargetProduct->execute = execute; pTargetProduct->rollback = rollback; // The highest aggregate action state found will be returned. if (pPackage->execute < execute) { pPackage->execute = execute; } if (pPackage->rollback < rollback) { pPackage->rollback = rollback; } } // The dependency manager will do the wrong thing if the package level action is UNINSTALL // when the patch will still be applied to at least one product. if (!fWillUninstallAll && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) { pPackage->execute = BOOTSTRAPPER_ACTION_STATE_NONE; } return hr; } // // PlanAdd - adds the calculated execute and rollback actions for the package. // extern "C" HRESULT MspEnginePlanAddPackage( __in BOOTSTRAPPER_DISPLAY display, __in BURN_USER_EXPERIENCE* pUserExperience, __in BURN_PACKAGE* pPackage, __in BURN_PLAN* pPlan, __in BURN_LOGGING* pLog, __in BURN_VARIABLES* pVariables ) { HRESULT hr = S_OK; hr = DependencyPlanPackage(NULL, pPackage, pPlan); ExitOnFailure(hr, "Failed to plan package dependency actions."); // Plan the actions for each target product code. for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) { BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; // If the dependency manager changed the action state for the patch, change the target product actions. if (pPackage->fDependencyManagerWasHere) { pTargetProduct->execute = pPackage->execute; pTargetProduct->rollback = pPackage->rollback; } if (BOOTSTRAPPER_ACTION_STATE_NONE != pTargetProduct->execute) { hr = PlanTargetProduct(display, pUserExperience, FALSE, pPlan, pLog, pVariables, pTargetProduct->execute, pPackage, pTargetProduct); ExitOnFailure(hr, "Failed to plan target product."); } if (BOOTSTRAPPER_ACTION_STATE_NONE != pTargetProduct->rollback) { hr = PlanTargetProduct(display, pUserExperience, TRUE, pPlan, pLog, pVariables, pTargetProduct->rollback, pPackage, pTargetProduct); ExitOnFailure(hr, "Failed to plan rollback target product."); } } LExit: return hr; } extern "C" HRESULT MspEngineExecutePackage( __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 sczCachedDirectory = NULL; LPWSTR sczMspPath = NULL; LPWSTR sczPatches = NULL; LPWSTR sczProperties = NULL; LPWSTR sczObfuscatedProperties = NULL; // default to "verbose" logging DWORD dwLogMode = WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE; // get cached MSP paths for (DWORD i = 0; i < pExecuteAction->mspTarget.cOrderedPatches; ++i) { LPCWSTR wzAppend = NULL; BURN_PACKAGE* pMspPackage = pExecuteAction->mspTarget.rgOrderedPatches[i].pPackage; BURN_PAYLOAD* pMspPackagePayload = pMspPackage->payloads.rgItems[0].pPayload; AssertSz(BURN_PACKAGE_TYPE_MSP == pMspPackage->type, "Invalid package type added to ordered patches."); if (BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->mspTarget.action) { hr = CacheGetCompletedPath(pCache, pMspPackage->fPerMachine, pMspPackage->sczCacheId, &sczCachedDirectory); ExitOnFailure(hr, "Failed to get cached path for MSP package: %ls", pMspPackage->sczId); // TODO: Figure out if this makes sense -- the variable is set to the last patch's path only // Best effort to set the execute package cache folder variable. VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE); hr = PathConcatRelativeToFullyQualifiedBase(sczCachedDirectory, pMspPackagePayload->sczFilePath, &sczMspPath); ExitOnFailure(hr, "Failed to build MSP path."); wzAppend = sczMspPath; } else // uninstall { wzAppend = pMspPackage->Msp.sczPatchCode; } if (sczPatches) { hr = StrAllocConcat(&sczPatches, L";", 0); ExitOnFailure(hr, "Failed to semi-colon delimit patches."); } hr = StrAllocConcat(&sczPatches, wzAppend, 0); ExitOnFailure(hr, "Failed to append patch."); } // Best effort to set the execute package action variable. VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, pExecuteAction->mspTarget.action, TRUE); // Wire up the external UI handler and logging. if (pExecuteAction->mspTarget.fDisableExternalUiHandler) { hr = WiuInitializeInternalUI(pExecuteAction->mspTarget.uiLevel, hwndParent, &context); ExitOnFailure(hr, "Failed to initialize internal UI for MSP package."); } else { hr = WiuInitializeExternalUI(pfnMessageHandler, pExecuteAction->mspTarget.uiLevel, hwndParent, pvContext, fRollback, &context); ExitOnFailure(hr, "Failed to initialize external UI handler."); } //if (BURN_LOGGING_LEVEL_DEBUG == logLevel) //{ // dwLogMode | INSTALLLOGMODE_EXTRADEBUG; //} if (pExecuteAction->mspTarget.sczLogPath && *pExecuteAction->mspTarget.sczLogPath) { hr = WiuEnableLog(dwLogMode, pExecuteAction->mspTarget.sczLogPath, INSTALLLOGATTRIBUTES_APPEND); ExitOnFailure(hr, "Failed to enable logging for package: %ls to: %ls", pExecuteAction->mspTarget.pPackage->sczId, pExecuteAction->mspTarget.sczLogPath); } // set up properties hr = MsiEngineConcatPackageProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczProperties, FALSE); ExitOnFailure(hr, "Failed to add properties to argument string."); hr = MsiEngineConcatPackageProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczObfuscatedProperties, TRUE); ExitOnFailure(hr, "Failed to add properties to obfuscated argument string."); if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL != pExecuteAction->mspTarget.action) { hr = StrAllocConcatFormattedSecure(&sczProperties, L" PATCH=\"%ls\"", sczPatches); ExitOnFailure(hr, "Failed to add PATCH property to argument string."); hr = StrAllocConcatFormatted(&sczObfuscatedProperties, L" PATCH=\"%ls\"", sczPatches); ExitOnFailure(hr, "Failed to add PATCH property to obfuscated argument string."); } // Always add Burn properties last. hr = MsiEngineConcatBurnProperties(pExecuteAction->mspTarget.action, pExecuteAction->mspTarget.actionMsiProperty, pExecuteAction->mspTarget.fileVersioning, FALSE, FALSE, &sczProperties); ExitOnFailure(hr, "Failed to add action property to argument string."); hr = MsiEngineConcatBurnProperties(pExecuteAction->mspTarget.action, pExecuteAction->mspTarget.actionMsiProperty, pExecuteAction->mspTarget.fileVersioning, FALSE, FALSE, &sczObfuscatedProperties); ExitOnFailure(hr, "Failed to add action property to obfuscated argument string."); LogId(REPORT_STANDARD, MSG_APPLYING_PATCH_PACKAGE, pExecuteAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pExecuteAction->mspTarget.action), sczPatches, sczObfuscatedProperties, pExecuteAction->mspTarget.sczTargetProductCode); // // Do the actual action. // switch (pExecuteAction->mspTarget.action) { case BOOTSTRAPPER_ACTION_STATE_INSTALL: __fallthrough; case BOOTSTRAPPER_ACTION_STATE_REPAIR: hr = WiuConfigureProductEx(pExecuteAction->mspTarget.sczTargetProductCode, INSTALLLEVEL_DEFAULT, INSTALLSTATE_DEFAULT, sczProperties, &restart); ExitOnFailure(hr, "Failed to install MSP package."); break; case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: hr = WiuRemovePatches(sczPatches, pExecuteAction->mspTarget.sczTargetProductCode, sczProperties, &restart); ExitOnFailure(hr, "Failed to uninstall MSP package."); break; } LExit: WiuUninitializeExternalUI(&context); ReleaseStr(sczCachedDirectory); ReleaseStr(sczMspPath); StrSecureZeroFreeString(sczProperties); ReleaseStr(sczObfuscatedProperties); ReleaseStr(sczPatches); 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; } // Best effort to clear the execute package cache folder and action variables. VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE); VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE, FALSE); return hr; } extern "C" void MspEngineUpdateInstallRegistrationState( __in BURN_EXECUTE_ACTION* pAction, __in HRESULT hrExecute, __in BOOL fInsideMsiTransaction ) { BURN_PACKAGE_REGISTRATION_STATE newState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; if (FAILED(hrExecute)) { ExitFunction(); } if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pAction->mspTarget.action) { newState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; } else { newState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; } for (DWORD i = 0; i < pAction->mspTarget.cOrderedPatches; ++i) { BURN_ORDERED_PATCHES* pOrderedPatches = pAction->mspTarget.rgOrderedPatches + i; BURN_PACKAGE* pPackage = pOrderedPatches->pPackage; BURN_MSPTARGETPRODUCT* pTargetProduct = NULL; Assert(BURN_PACKAGE_TYPE_MSP == pPackage->type); if (!pPackage->fCanAffectRegistration) { continue; } for (DWORD j = 0; j < pPackage->Msp.cTargetProductCodes; ++j) { pTargetProduct = pPackage->Msp.rgTargetProducts + j; if (pAction->mspTarget.fPerMachineTarget == (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context) && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pAction->mspTarget.sczTargetProductCode, -1, pTargetProduct->wzTargetProductCode, -1)) { break; } pTargetProduct = NULL; } if (!pTargetProduct) { AssertSz(pTargetProduct, "Ordered patch didn't have corresponding target product"); continue; } if (fInsideMsiTransaction) { pTargetProduct->transactionRegistrationState = newState; } else { pTargetProduct->registrationState = newState; } } LExit: return; } extern "C" void MspEngineFinalizeInstallRegistrationState( __in BURN_PACKAGE* pPackage ) { if (!pPackage->fCanAffectRegistration) { ExitFunction(); } if (!pPackage->Msp.cTargetProductCodes) { pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; } else { pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) { BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; if (pPackage->installRegistrationState < pTargetProduct->registrationState) { pPackage->installRegistrationState = pTargetProduct->registrationState; } } } LExit: return; } // internal helper functions static HRESULT GetPossibleTargetProductCodes( __in BURN_PACKAGES* pPackages, __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, __inout DWORD* pcPossibleTargetProducts ) { HRESULT hr = S_OK; STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes = NULL; BOOL fCheckAll = FALSE; WCHAR wzPossibleTargetProductCode[MAX_GUID_CHARS + 1]; // Use a dictionary to ensure we capture unique product codes. Otherwise, we could end up // doing patch applicability for the same product code multiple times and that would confuse // everything down stream. hr = DictCreateStringList(&sdUniquePossibleTargetProductCodes, 5, DICT_FLAG_NONE); ExitOnFailure(hr, "Failed to create unique target product codes."); // If the patches target a specific set of product/upgrade codes, search only those. This // should be much faster than searching all packages on the machine. if (pPackages->rgPatchTargetCodes) { for (DWORD i = 0; i < pPackages->cPatchTargetCodes; ++i) { BURN_PATCH_TARGETCODE* pTargetCode = pPackages->rgPatchTargetCodes + i; // If targeting a product, add the unique product code to the list. if (BURN_PATCH_TARGETCODE_TYPE_PRODUCT == pTargetCode->type) { hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, pTargetCode->sczTargetCode, MSIINSTALLCONTEXT_NONE, prgPossibleTargetProducts, pcPossibleTargetProducts); ExitOnFailure(hr, "Failed to add product code to possible target product codes."); } else if (BURN_PATCH_TARGETCODE_TYPE_UPGRADE == pTargetCode->type) { // Enumerate all unique related products to the target upgrade code. for (DWORD iProduct = 0; SUCCEEDED(hr); ++iProduct) { hr = WiuEnumRelatedProducts(pTargetCode->sczTargetCode, iProduct, wzPossibleTargetProductCode); if (SUCCEEDED(hr)) { hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode, MSIINSTALLCONTEXT_NONE, prgPossibleTargetProducts, pcPossibleTargetProducts); ExitOnFailure(hr, "Failed to add upgrade product code to possible target product codes."); } else if (E_BADCONFIGURATION == hr) { // Skip product's with bad configuration and continue. LogId(REPORT_STANDARD, MSG_DETECT_BAD_PRODUCT_CONFIGURATION, wzPossibleTargetProductCode); hr = S_OK; } } if (E_NOMOREITEMS == hr) { hr = S_OK; } ExitOnFailure(hr, "Failed to enumerate all products to patch related to upgrade code: %ls", pTargetCode->sczTargetCode); } else { // The element does not target a specific product. fCheckAll = TRUE; break; } } } else { fCheckAll = TRUE; } // One or more of the patches do not target a specific product so search everything on the machine. if (fCheckAll) { for (DWORD iProduct = 0; SUCCEEDED(hr); ++iProduct) { MSIINSTALLCONTEXT context = MSIINSTALLCONTEXT_NONE; hr = WiuEnumProductsEx(NULL, NULL, MSIINSTALLCONTEXT_ALL, iProduct, wzPossibleTargetProductCode, &context, NULL, NULL); if (SUCCEEDED(hr)) { hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode, context, prgPossibleTargetProducts, pcPossibleTargetProducts); ExitOnFailure(hr, "Failed to add product code to search product codes."); } else if (E_BADCONFIGURATION == hr) { // Skip products with bad configuration and continue. LogId(REPORT_STANDARD, MSG_DETECT_BAD_PRODUCT_CONFIGURATION, wzPossibleTargetProductCode); hr = S_OK; } } if (E_NOMOREITEMS == hr) { hr = S_OK; } ExitOnFailure(hr, "Failed to enumerate all products on the machine for patches applicability."); } LExit: ReleaseDict(sdUniquePossibleTargetProductCodes); return hr; } static HRESULT AddPossibleTargetProduct( __in STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes, __in_z LPCWSTR wzPossibleTargetProductCode, __in MSIINSTALLCONTEXT context, __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, __inout DWORD* pcPossibleTargetProducts ) { HRESULT hr = S_OK; LPWSTR pszLocalPackage = NULL; // Only add this possible target code if we haven't queried for it already. if (E_NOTFOUND == DictKeyExists(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode)) { // If the install context is not known, ask the Windows Installer for it. If we can't get the context // then bail. if (MSIINSTALLCONTEXT_NONE == context) { hr = WiuEnumProductsEx(wzPossibleTargetProductCode, NULL, MSIINSTALLCONTEXT_ALL, 0, NULL, &context, NULL, NULL); if (FAILED(hr)) { ExitFunction1(hr = S_OK); } } hr = DictAddKey(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode); ExitOnFailure(hr, "Failed to add possible target code to unique product codes."); hr = MemEnsureArraySize(reinterpret_cast(prgPossibleTargetProducts), *pcPossibleTargetProducts + 1, sizeof(POSSIBLE_TARGETPRODUCT), 3); ExitOnFailure(hr, "Failed to grow array of possible target products."); POSSIBLE_TARGETPRODUCT *const pPossibleTargetProduct = *prgPossibleTargetProducts + *pcPossibleTargetProducts; hr = ::StringCchCopyW(pPossibleTargetProduct->wzProductCode, countof(pPossibleTargetProduct->wzProductCode), wzPossibleTargetProductCode); ExitOnFailure(hr, "Failed to copy possible target product code."); // Attempt to get the local package path so we can more quickly determine patch applicability later. hr = WiuGetProductInfoEx(wzPossibleTargetProductCode, NULL, context, INSTALLPROPERTY_LOCALPACKAGE, &pszLocalPackage); if (SUCCEEDED(hr)) { pPossibleTargetProduct->pszLocalPackage = pszLocalPackage; pszLocalPackage = NULL; } else { // Will instead call MsiDeterminePatchSequence later. hr = S_OK; } pPossibleTargetProduct->context = context; ++(*pcPossibleTargetProducts); } LExit: ReleaseStr(pszLocalPackage); return hr; } static HRESULT AddDetectedTargetProduct( __in BURN_PACKAGE* pPackage, __in DWORD dwOrder, __in_z LPCWSTR wzProductCode, __in MSIINSTALLCONTEXT context, __out DWORD* pdwTargetProductIndex ) { HRESULT hr = S_OK; BURN_MSPTARGETPRODUCT* pTargetProduct = NULL; *pdwTargetProductIndex = BURN_PACKAGE_INVALID_PATCH_INDEX; hr = MemEnsureArraySize(reinterpret_cast(&pPackage->Msp.rgTargetProducts), pPackage->Msp.cTargetProductCodes + 1, sizeof(BURN_MSPTARGETPRODUCT), 5); ExitOnFailure(hr, "Failed to ensure enough target product codes were allocated."); pTargetProduct = pPackage->Msp.rgTargetProducts + pPackage->Msp.cTargetProductCodes; hr = ::StringCchCopyW(pTargetProduct->wzTargetProductCode, countof(pTargetProduct->wzTargetProductCode), wzProductCode); ExitOnFailure(hr, "Failed to copy target product code."); pTargetProduct->context = context; pTargetProduct->dwOrder = dwOrder; *pdwTargetProductIndex = pPackage->Msp.cTargetProductCodes; ++pPackage->Msp.cTargetProductCodes; LExit: return hr; } static HRESULT AddMsiChainedPatch( __in BURN_PACKAGE* pPackage, __in BURN_PACKAGE* pMspPackage, __in DWORD dwMspTargetProductIndex, __out DWORD* pdwChainedPatchIndex ) { HRESULT hr = S_OK; hr = MemEnsureArraySize(reinterpret_cast(&pPackage->Msi.rgChainedPatches), pPackage->Msi.cChainedPatches + 1, sizeof(BURN_CHAINED_PATCH), 5); ExitOnFailure(hr, "Failed to ensure enough chained patches were allocated."); BURN_CHAINED_PATCH* pChainedPatch = pPackage->Msi.rgChainedPatches + pPackage->Msi.cChainedPatches; pChainedPatch->pMspPackage = pMspPackage; pChainedPatch->dwMspTargetProductIndex = dwMspTargetProductIndex; *pdwChainedPatchIndex = pPackage->Msi.cChainedPatches; ++pPackage->Msi.cChainedPatches; LExit: return hr; } static HRESULT DeterminePatchChainedTarget( __in BURN_PACKAGES* pPackages, __in BURN_PACKAGE* pMspPackage, __in LPCWSTR wzTargetProductCode, __in DWORD dwMspTargetProductIndex ) { HRESULT hr = S_OK; DWORD dwChainedPatchIndex = 0; BURN_MSPTARGETPRODUCT* pTargetProduct = pMspPackage->Msp.rgTargetProducts + dwMspTargetProductIndex; for (DWORD iPackage = 0; iPackage < pPackages->cPackages; ++iPackage) { BURN_PACKAGE* pPackage = pPackages->rgPackages + iPackage; if (BURN_PACKAGE_TYPE_MSI == pPackage->type && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzTargetProductCode, -1, pPackage->Msi.sczProductCode, -1)) { pTargetProduct->pChainedTargetPackage = pPackage; hr = AddMsiChainedPatch(pPackage, pMspPackage, dwMspTargetProductIndex, &dwChainedPatchIndex); ExitOnFailure(hr, "Failed to add chained patch."); for (DWORD j = 0; j < pPackage->Msi.cSlipstreamMspPackages; ++j) { BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pPackage->Msi.rgSlipstreamMsps + j; if (pSlipstreamMsp->pMspPackage == pMspPackage) { AssertSz(BURN_PACKAGE_INVALID_PATCH_INDEX == pSlipstreamMsp->dwMsiChainedPatchIndex, "An MSP should only show up as a slipstreamed patch in an MSI once."); pTargetProduct->fSlipstream = TRUE; pSlipstreamMsp->dwMsiChainedPatchIndex = dwChainedPatchIndex; break; } } break; } } LExit: return hr; } static HRESULT PlanTargetProduct( __in BOOTSTRAPPER_DISPLAY display, __in BURN_USER_EXPERIENCE* pUserExperience, __in BOOL fRollback, __in BURN_PLAN* pPlan, __in BURN_LOGGING* pLog, __in BURN_VARIABLES* pVariables, __in BOOTSTRAPPER_ACTION_STATE actionState, __in BURN_PACKAGE* pPackage, __in BURN_MSPTARGETPRODUCT* pTargetProduct ) { HRESULT hr = S_OK; BURN_EXECUTE_ACTION* rgActions = fRollback ? pPlan->rgRollbackActions : pPlan->rgExecuteActions; DWORD cActions = fRollback ? pPlan->cRollbackActions : pPlan->cExecuteActions; BURN_EXECUTE_ACTION* pAction = NULL; DWORD dwInsertSequence = 0; // Try to find another MSP action with the exact same action (install or uninstall) targeting // the same product in the same machine context (per-user or per-machine). for (DWORD i = 0; i < cActions; ++i) { pAction = rgActions + i; if (BURN_EXECUTE_ACTION_TYPE_MSP_TARGET == pAction->type && pAction->mspTarget.action == actionState && pAction->mspTarget.fPerMachineTarget == (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context) && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pAction->mspTarget.sczTargetProductCode, -1, pTargetProduct->wzTargetProductCode, -1)) { dwInsertSequence = i; break; } pAction = NULL; } // If we didn't find an MSP target action already updating the product, create a new action. if (!pAction) { if (fRollback) { hr = PlanAppendRollbackAction(pPlan, &pAction); } else { hr = PlanAppendExecuteAction(pPlan, &pAction); } ExitOnFailure(hr, "Failed to plan action for target product."); pAction->type = BURN_EXECUTE_ACTION_TYPE_MSP_TARGET; pAction->mspTarget.action = actionState; pAction->mspTarget.pPackage = pPackage; pAction->mspTarget.fPerMachineTarget = (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context); pAction->mspTarget.pChainedTargetPackage = pTargetProduct->pChainedTargetPackage; pAction->mspTarget.fSlipstream = pTargetProduct->fSlipstream; hr = StrAllocString(&pAction->mspTarget.sczTargetProductCode, pTargetProduct->wzTargetProductCode, 0); ExitOnFailure(hr, "Failed to copy target product code."); hr = MsiEnginePlanPackageOptions(display, pUserExperience, pPackage->sczId, !fRollback, pAction->mspTarget.action, &pAction->mspTarget.actionMsiProperty, &pAction->mspTarget.uiLevel, &pAction->mspTarget.fDisableExternalUiHandler, &pAction->mspTarget.fileVersioning); ExitOnFailure(hr, "Failed to get msp ui options."); // If this is a per-machine target product, then the plan needs to be per-machine as well. if (pAction->mspTarget.fPerMachineTarget) { pPlan->fPerMachine = TRUE; } LoggingSetPackageVariable(pPackage, pAction->mspTarget.sczTargetProductCode, fRollback, pLog, pVariables, &pAction->mspTarget.sczLogPath); // ignore errors. } else { if (!fRollback && pPackage->hCacheEvent) { // TODO: need to properly handle rolling back the caching of the package since this causes cache and execute plans to get out of sync. // Since a previous MSP target action is being updated with the new MSP, // insert a wait syncpoint to before this action since we need to cache the current MSI before using it. BURN_EXECUTE_ACTION* pWaitSyncPointAction = NULL; hr = PlanInsertExecuteAction(dwInsertSequence, pPlan, &pWaitSyncPointAction); ExitOnFailure(hr, "Failed to insert execute action."); pWaitSyncPointAction->type = BURN_EXECUTE_ACTION_TYPE_WAIT_CACHE_PACKAGE; pWaitSyncPointAction->waitCachePackage.pPackage = pPackage; // Since we inserted an action before the MSP target action that we will be updating, need to update the pointer. pAction = pPlan->rgExecuteActions + (dwInsertSequence + 1); } } // Add our target product to the array and sort based on their order determined during detection. hr = MemEnsureArraySize(reinterpret_cast(&pAction->mspTarget.rgOrderedPatches), pAction->mspTarget.cOrderedPatches + 1, sizeof(BURN_ORDERED_PATCHES), 2); ExitOnFailure(hr, "Failed to grow array of ordered patches."); pAction->mspTarget.rgOrderedPatches[pAction->mspTarget.cOrderedPatches].pTargetProduct = pTargetProduct; pAction->mspTarget.rgOrderedPatches[pAction->mspTarget.cOrderedPatches].pPackage = pPackage; ++pAction->mspTarget.cOrderedPatches; // Insertion sort to keep the patches ordered. for (DWORD i = pAction->mspTarget.cOrderedPatches - 1; i > 0; --i) { if (pAction->mspTarget.rgOrderedPatches[i].pTargetProduct->dwOrder < pAction->mspTarget.rgOrderedPatches[i - 1].pTargetProduct->dwOrder) { BURN_ORDERED_PATCHES temp = pAction->mspTarget.rgOrderedPatches[i - 1]; pAction->mspTarget.rgOrderedPatches[i - 1] = pAction->mspTarget.rgOrderedPatches[i]; pAction->mspTarget.rgOrderedPatches[i] = temp; } else // no swap necessary, we're done. { break; } } LExit: return hr; }