diff options
Diffstat (limited to 'src/engine/mspengine.cpp')
-rw-r--r-- | src/engine/mspengine.cpp | 980 |
1 files changed, 980 insertions, 0 deletions
diff --git a/src/engine/mspengine.cpp b/src/engine/mspengine.cpp new file mode 100644 index 00000000..463799e6 --- /dev/null +++ b/src/engine/mspengine.cpp | |||
@@ -0,0 +1,980 @@ | |||
1 | // 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. | ||
2 | |||
3 | #include "precomp.h" | ||
4 | |||
5 | |||
6 | // constants | ||
7 | |||
8 | |||
9 | // structs | ||
10 | |||
11 | struct POSSIBLE_TARGETPRODUCT | ||
12 | { | ||
13 | WCHAR wzProductCode[39]; | ||
14 | LPWSTR pszLocalPackage; | ||
15 | MSIINSTALLCONTEXT context; | ||
16 | }; | ||
17 | |||
18 | // internal function declarations | ||
19 | |||
20 | static HRESULT GetPossibleTargetProductCodes( | ||
21 | __in BURN_PACKAGES* pPackages, | ||
22 | __deref_inout_ecount_opt(*pcPossibleTargetProductCodes) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProductCodes, | ||
23 | __inout DWORD* pcPossibleTargetProductCodes | ||
24 | ); | ||
25 | static HRESULT AddPossibleTargetProduct( | ||
26 | __in STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes, | ||
27 | __in_z LPCWSTR wzPossibleTargetProductCode, | ||
28 | __in MSIINSTALLCONTEXT context, | ||
29 | __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, | ||
30 | __inout DWORD* pcPossibleTargetProducts | ||
31 | ); | ||
32 | static HRESULT AddDetectedTargetProduct( | ||
33 | __in BURN_PACKAGES* pPackages, | ||
34 | __in BURN_PACKAGE* pPackage, | ||
35 | __in DWORD dwOrder, | ||
36 | __in_z LPCWSTR wzProductCode, | ||
37 | __in MSIINSTALLCONTEXT context | ||
38 | ); | ||
39 | static void DeterminePatchChainedTarget( | ||
40 | __in BURN_PACKAGES* pPackages, | ||
41 | __in BURN_PACKAGE* pMspPackage, | ||
42 | __in LPCWSTR wzTargetProductCode, | ||
43 | __out BURN_PACKAGE** ppChainedTargetPackage, | ||
44 | __out BOOL* pfSlipstreamed | ||
45 | ); | ||
46 | static HRESULT PlanTargetProduct( | ||
47 | __in BOOTSTRAPPER_DISPLAY display, | ||
48 | __in BOOL fRollback, | ||
49 | __in BURN_PLAN* pPlan, | ||
50 | __in BURN_LOGGING* pLog, | ||
51 | __in BURN_VARIABLES* pVariables, | ||
52 | __in BOOTSTRAPPER_ACTION_STATE actionState, | ||
53 | __in BURN_PACKAGE* pPackage, | ||
54 | __in BURN_MSPTARGETPRODUCT* pTargetProduct, | ||
55 | __in_opt HANDLE hCacheEvent | ||
56 | ); | ||
57 | |||
58 | |||
59 | // function definitions | ||
60 | |||
61 | extern "C" HRESULT MspEngineParsePackageFromXml( | ||
62 | __in IXMLDOMNode* pixnMspPackage, | ||
63 | __in BURN_PACKAGE* pPackage | ||
64 | ) | ||
65 | { | ||
66 | HRESULT hr = S_OK; | ||
67 | |||
68 | // @PatchCode | ||
69 | hr = XmlGetAttributeEx(pixnMspPackage, L"PatchCode", &pPackage->Msp.sczPatchCode); | ||
70 | ExitOnFailure(hr, "Failed to get @PatchCode."); | ||
71 | |||
72 | // @PatchXml | ||
73 | hr = XmlGetAttributeEx(pixnMspPackage, L"PatchXml", &pPackage->Msp.sczApplicabilityXml); | ||
74 | ExitOnFailure(hr, "Failed to get @PatchXml."); | ||
75 | |||
76 | // @DisplayInternalUI | ||
77 | hr = XmlGetYesNoAttribute(pixnMspPackage, L"DisplayInternalUI", &pPackage->Msp.fDisplayInternalUI); | ||
78 | ExitOnFailure(hr, "Failed to get @DisplayInternalUI."); | ||
79 | |||
80 | // Read properties. | ||
81 | hr = MsiEngineParsePropertiesFromXml(pixnMspPackage, &pPackage->Msp.rgProperties, &pPackage->Msp.cProperties); | ||
82 | ExitOnFailure(hr, "Failed to parse properties from XML."); | ||
83 | |||
84 | LExit: | ||
85 | |||
86 | return hr; | ||
87 | } | ||
88 | |||
89 | extern "C" void MspEnginePackageUninitialize( | ||
90 | __in BURN_PACKAGE* pPackage | ||
91 | ) | ||
92 | { | ||
93 | ReleaseStr(pPackage->Msp.sczPatchCode); | ||
94 | ReleaseStr(pPackage->Msp.sczApplicabilityXml); | ||
95 | |||
96 | // free properties | ||
97 | if (pPackage->Msp.rgProperties) | ||
98 | { | ||
99 | for (DWORD i = 0; i < pPackage->Msp.cProperties; ++i) | ||
100 | { | ||
101 | BURN_MSIPROPERTY* pProperty = &pPackage->Msp.rgProperties[i]; | ||
102 | |||
103 | ReleaseStr(pProperty->sczId); | ||
104 | ReleaseStr(pProperty->sczValue); | ||
105 | ReleaseStr(pProperty->sczRollbackValue); | ||
106 | } | ||
107 | MemFree(pPackage->Msp.rgProperties); | ||
108 | } | ||
109 | |||
110 | // free target products | ||
111 | ReleaseMem(pPackage->Msp.rgTargetProducts); | ||
112 | |||
113 | // clear struct | ||
114 | memset(&pPackage->Msp, 0, sizeof(pPackage->Msp)); | ||
115 | } | ||
116 | |||
117 | extern "C" HRESULT MspEngineDetectInitialize( | ||
118 | __in BURN_PACKAGES* pPackages | ||
119 | ) | ||
120 | { | ||
121 | AssertSz(pPackages->cPatchInfo, "MspEngineDetectInitialize() should only be called if there are MSP packages."); | ||
122 | |||
123 | HRESULT hr = S_OK; | ||
124 | POSSIBLE_TARGETPRODUCT* rgPossibleTargetProducts = NULL; | ||
125 | DWORD cPossibleTargetProducts = 0; | ||
126 | |||
127 | #ifdef DEBUG | ||
128 | // All patch info should be initialized to zero. | ||
129 | for (DWORD i = 0; i < pPackages->cPatchInfo; ++i) | ||
130 | { | ||
131 | BURN_PACKAGE* pPackage = pPackages->rgPatchInfoToPackage[i]; | ||
132 | Assert(!pPackage->Msp.cTargetProductCodes); | ||
133 | Assert(!pPackage->Msp.rgTargetProducts); | ||
134 | } | ||
135 | #endif | ||
136 | |||
137 | // Figure out which product codes to target on the machine. In the worst case all products on the machine | ||
138 | // will be returned. | ||
139 | hr = GetPossibleTargetProductCodes(pPackages, &rgPossibleTargetProducts, &cPossibleTargetProducts); | ||
140 | ExitOnFailure(hr, "Failed to get possible target product codes."); | ||
141 | |||
142 | // Loop through possible target products, testing the collective patch applicability against each product in | ||
143 | // the appropriate context. Store the result with the appropriate patch package. | ||
144 | for (DWORD iSearch = 0; iSearch < cPossibleTargetProducts; ++iSearch) | ||
145 | { | ||
146 | const POSSIBLE_TARGETPRODUCT* pPossibleTargetProduct = rgPossibleTargetProducts + iSearch; | ||
147 | |||
148 | LogId(REPORT_STANDARD, MSG_DETECT_CALCULATE_PATCH_APPLICABILITY, pPossibleTargetProduct->wzProductCode, LoggingMsiInstallContext(pPossibleTargetProduct->context)); | ||
149 | |||
150 | if (pPossibleTargetProduct->pszLocalPackage) | ||
151 | { | ||
152 | // Ignores current machine state to determine just patch applicability. | ||
153 | // Superseded and obsolesced patches will be planned separately. | ||
154 | hr = WiuDetermineApplicablePatches(pPossibleTargetProduct->pszLocalPackage, pPackages->rgPatchInfo, pPackages->cPatchInfo); | ||
155 | } | ||
156 | else | ||
157 | { | ||
158 | hr = WiuDeterminePatchSequence(pPossibleTargetProduct->wzProductCode, NULL, pPossibleTargetProduct->context, pPackages->rgPatchInfo, pPackages->cPatchInfo); | ||
159 | } | ||
160 | |||
161 | if (SUCCEEDED(hr)) | ||
162 | { | ||
163 | for (DWORD iPatchInfo = 0; iPatchInfo < pPackages->cPatchInfo; ++iPatchInfo) | ||
164 | { | ||
165 | if (ERROR_SUCCESS == pPackages->rgPatchInfo[iPatchInfo].uStatus) | ||
166 | { | ||
167 | BURN_PACKAGE* pMspPackage = pPackages->rgPatchInfoToPackage[iPatchInfo]; | ||
168 | Assert(BURN_PACKAGE_TYPE_MSP == pMspPackage->type); | ||
169 | |||
170 | // Note that we do add superseded and obsolete MSP packages. Package Detect and Plan will sort them out later. | ||
171 | hr = AddDetectedTargetProduct(pPackages, pMspPackage, pPackages->rgPatchInfo[iPatchInfo].dwOrder, pPossibleTargetProduct->wzProductCode, pPossibleTargetProduct->context); | ||
172 | ExitOnFailure(hr, "Failed to add target product code to package: %ls", pMspPackage->sczId); | ||
173 | } | ||
174 | // TODO: should we log something for this error case? | ||
175 | } | ||
176 | } | ||
177 | else | ||
178 | { | ||
179 | LogId(REPORT_STANDARD, MSG_DETECT_FAILED_CALCULATE_PATCH_APPLICABILITY, pPossibleTargetProduct->wzProductCode, LoggingMsiInstallContext(pPossibleTargetProduct->context), hr); | ||
180 | } | ||
181 | |||
182 | hr = S_OK; // always reset so we test all possible target products. | ||
183 | } | ||
184 | |||
185 | LExit: | ||
186 | if (rgPossibleTargetProducts) | ||
187 | { | ||
188 | for (DWORD i = 0; i < cPossibleTargetProducts; ++i) | ||
189 | { | ||
190 | ReleaseStr(rgPossibleTargetProducts[i].pszLocalPackage); | ||
191 | } | ||
192 | MemFree(rgPossibleTargetProducts); | ||
193 | } | ||
194 | |||
195 | return hr; | ||
196 | } | ||
197 | |||
198 | extern "C" HRESULT MspEngineDetectPackage( | ||
199 | __in BURN_PACKAGE* pPackage, | ||
200 | __in BURN_USER_EXPERIENCE* pUserExperience | ||
201 | ) | ||
202 | { | ||
203 | HRESULT hr = S_OK; | ||
204 | LPWSTR sczState = NULL; | ||
205 | |||
206 | if (0 == pPackage->Msp.cTargetProductCodes) | ||
207 | { | ||
208 | pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; | ||
209 | } | ||
210 | else | ||
211 | { | ||
212 | // Start the package state at the the highest state then loop through all the | ||
213 | // target product codes and end up setting the current state to the lowest | ||
214 | // package state applied to the the target product codes. | ||
215 | pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; | ||
216 | |||
217 | for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) | ||
218 | { | ||
219 | BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; | ||
220 | |||
221 | hr = WiuGetPatchInfoEx(pPackage->Msp.sczPatchCode, pTargetProduct->wzTargetProductCode, NULL, pTargetProduct->context, INSTALLPROPERTY_PATCHSTATE, &sczState); | ||
222 | if (SUCCEEDED(hr)) | ||
223 | { | ||
224 | switch (*sczState) | ||
225 | { | ||
226 | case '1': | ||
227 | pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; | ||
228 | break; | ||
229 | |||
230 | case '2': | ||
231 | pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; | ||
232 | break; | ||
233 | |||
234 | case '4': | ||
235 | pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; | ||
236 | break; | ||
237 | |||
238 | default: | ||
239 | pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; | ||
240 | break; | ||
241 | } | ||
242 | } | ||
243 | else if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PATCH) == hr) | ||
244 | { | ||
245 | pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; | ||
246 | hr = S_OK; | ||
247 | } | ||
248 | ExitOnFailure(hr, "Failed to get patch information for patch code: %ls, target product code: %ls", pPackage->Msp.sczPatchCode, pTargetProduct->wzTargetProductCode); | ||
249 | |||
250 | if (pPackage->currentState > pTargetProduct->patchPackageState) | ||
251 | { | ||
252 | pPackage->currentState = pTargetProduct->patchPackageState; | ||
253 | } | ||
254 | |||
255 | hr = UserExperienceOnDetectTargetMsiPackage(pUserExperience, pPackage->sczId, pTargetProduct->wzTargetProductCode, pTargetProduct->patchPackageState); | ||
256 | ExitOnRootFailure(hr, "BA aborted detect target MSI package."); | ||
257 | } | ||
258 | } | ||
259 | |||
260 | LExit: | ||
261 | ReleaseStr(sczState); | ||
262 | |||
263 | return hr; | ||
264 | } | ||
265 | |||
266 | // | ||
267 | // PlanCalculate - calculates the execute and rollback state for the requested package state. | ||
268 | // | ||
269 | extern "C" HRESULT MspEnginePlanCalculatePackage( | ||
270 | __in BURN_PACKAGE* pPackage, | ||
271 | __in BURN_USER_EXPERIENCE* pUserExperience, | ||
272 | __out BOOL* pfBARequestedCache | ||
273 | ) | ||
274 | { | ||
275 | HRESULT hr = S_OK; | ||
276 | BOOL fBARequestedCache = FALSE; | ||
277 | |||
278 | for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) | ||
279 | { | ||
280 | BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; | ||
281 | |||
282 | BOOTSTRAPPER_REQUEST_STATE requested = pPackage->requested; | ||
283 | BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
284 | BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
285 | |||
286 | hr = UserExperienceOnPlanTargetMsiPackage(pUserExperience, pPackage->sczId, pTargetProduct->wzTargetProductCode, &requested); | ||
287 | ExitOnRootFailure(hr, "BA aborted plan target MSI package."); | ||
288 | |||
289 | // Calculate the execute action. | ||
290 | switch (pTargetProduct->patchPackageState) | ||
291 | { | ||
292 | case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: | ||
293 | switch (requested) | ||
294 | { | ||
295 | case BOOTSTRAPPER_REQUEST_STATE_REPAIR: | ||
296 | execute = BOOTSTRAPPER_ACTION_STATE_REPAIR; | ||
297 | break; | ||
298 | |||
299 | case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough; | ||
300 | case BOOTSTRAPPER_REQUEST_STATE_CACHE: | ||
301 | execute = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; | ||
302 | break; | ||
303 | |||
304 | case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: | ||
305 | execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; | ||
306 | break; | ||
307 | |||
308 | default: | ||
309 | execute = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
310 | break; | ||
311 | } | ||
312 | break; | ||
313 | |||
314 | case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: | ||
315 | switch (requested) | ||
316 | { | ||
317 | case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; | ||
318 | case BOOTSTRAPPER_REQUEST_STATE_REPAIR: | ||
319 | execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; | ||
320 | break; | ||
321 | |||
322 | case BOOTSTRAPPER_REQUEST_STATE_CACHE: | ||
323 | execute = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
324 | fBARequestedCache = TRUE; | ||
325 | break; | ||
326 | |||
327 | default: | ||
328 | execute = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
329 | break; | ||
330 | } | ||
331 | break; | ||
332 | } | ||
333 | |||
334 | // Calculate the rollback action if there is an execute action. | ||
335 | if (BOOTSTRAPPER_ACTION_STATE_NONE != execute) | ||
336 | { | ||
337 | switch (BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN != pPackage->expected ? pPackage->expected : pPackage->currentState) | ||
338 | { | ||
339 | case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: | ||
340 | switch (requested) | ||
341 | { | ||
342 | case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; | ||
343 | case BOOTSTRAPPER_REQUEST_STATE_ABSENT: | ||
344 | rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; | ||
345 | break; | ||
346 | |||
347 | default: | ||
348 | rollback = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
349 | break; | ||
350 | } | ||
351 | break; | ||
352 | |||
353 | case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: __fallthrough; | ||
354 | case BOOTSTRAPPER_PACKAGE_STATE_CACHED: | ||
355 | switch (requested) | ||
356 | { | ||
357 | case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; | ||
358 | case BOOTSTRAPPER_REQUEST_STATE_REPAIR: | ||
359 | rollback = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; | ||
360 | break; | ||
361 | |||
362 | default: | ||
363 | rollback = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
364 | break; | ||
365 | } | ||
366 | break; | ||
367 | |||
368 | default: | ||
369 | rollback = BOOTSTRAPPER_ACTION_STATE_NONE; | ||
370 | break; | ||
371 | } | ||
372 | } | ||
373 | |||
374 | pTargetProduct->execute = execute; | ||
375 | pTargetProduct->rollback = rollback; | ||
376 | |||
377 | // The highest aggregate action state found will be returned. | ||
378 | if (pPackage->execute < execute) | ||
379 | { | ||
380 | pPackage->execute = execute; | ||
381 | } | ||
382 | |||
383 | if (pPackage->rollback < rollback) | ||
384 | { | ||
385 | pPackage->rollback = rollback; | ||
386 | } | ||
387 | } | ||
388 | |||
389 | if (pfBARequestedCache) | ||
390 | { | ||
391 | *pfBARequestedCache = fBARequestedCache; | ||
392 | } | ||
393 | |||
394 | LExit: | ||
395 | |||
396 | return hr; | ||
397 | } | ||
398 | |||
399 | // | ||
400 | // PlanAdd - adds the calculated execute and rollback actions for the package. | ||
401 | // | ||
402 | extern "C" HRESULT MspEnginePlanAddPackage( | ||
403 | __in BOOTSTRAPPER_DISPLAY display, | ||
404 | __in BURN_PACKAGE* pPackage, | ||
405 | __in BURN_PLAN* pPlan, | ||
406 | __in BURN_LOGGING* pLog, | ||
407 | __in BURN_VARIABLES* pVariables, | ||
408 | __in_opt HANDLE hCacheEvent, | ||
409 | __in BOOL fPlanPackageCacheRollback | ||
410 | ) | ||
411 | { | ||
412 | HRESULT hr = S_OK; | ||
413 | |||
414 | // TODO: need to handle the case where this patch adds itself to an earlier patch's list of target products. That would | ||
415 | // essentially bump this patch earlier in the plan and we need to make sure this patch is downloaded. | ||
416 | // add wait for cache | ||
417 | if (hCacheEvent) | ||
418 | { | ||
419 | hr = PlanExecuteCacheSyncAndRollback(pPlan, pPackage, hCacheEvent, fPlanPackageCacheRollback); | ||
420 | ExitOnFailure(hr, "Failed to plan package cache syncpoint"); | ||
421 | } | ||
422 | |||
423 | hr = DependencyPlanPackage(NULL, pPackage, pPlan); | ||
424 | ExitOnFailure(hr, "Failed to plan package dependency actions."); | ||
425 | |||
426 | // Plan the actions for each target product code. | ||
427 | for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) | ||
428 | { | ||
429 | BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; | ||
430 | |||
431 | // If the dependency manager changed the action state for the patch, change the target product actions. | ||
432 | if (pPackage->fDependencyManagerWasHere) | ||
433 | { | ||
434 | pTargetProduct->execute = pPackage->execute; | ||
435 | pTargetProduct->rollback = pPackage->rollback; | ||
436 | } | ||
437 | |||
438 | if (BOOTSTRAPPER_ACTION_STATE_NONE != pTargetProduct->execute) | ||
439 | { | ||
440 | hr = PlanTargetProduct(display, FALSE, pPlan, pLog, pVariables, pTargetProduct->execute, pPackage, pTargetProduct, hCacheEvent); | ||
441 | ExitOnFailure(hr, "Failed to plan target product."); | ||
442 | } | ||
443 | |||
444 | if (BOOTSTRAPPER_ACTION_STATE_NONE != pTargetProduct->rollback) | ||
445 | { | ||
446 | hr = PlanTargetProduct(display, TRUE, pPlan, pLog, pVariables, pTargetProduct->rollback, pPackage, pTargetProduct, hCacheEvent); | ||
447 | ExitOnFailure(hr, "Failed to plan rollack target product."); | ||
448 | } | ||
449 | } | ||
450 | |||
451 | LExit: | ||
452 | |||
453 | return hr; | ||
454 | } | ||
455 | |||
456 | extern "C" HRESULT MspEngineExecutePackage( | ||
457 | __in_opt HWND hwndParent, | ||
458 | __in BURN_EXECUTE_ACTION* pExecuteAction, | ||
459 | __in BURN_VARIABLES* pVariables, | ||
460 | __in BOOL fRollback, | ||
461 | __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, | ||
462 | __in LPVOID pvContext, | ||
463 | __out BOOTSTRAPPER_APPLY_RESTART* pRestart | ||
464 | ) | ||
465 | { | ||
466 | HRESULT hr = S_OK; | ||
467 | INSTALLUILEVEL uiLevel = pExecuteAction->mspTarget.pPackage->Msp.fDisplayInternalUI ? INSTALLUILEVEL_DEFAULT : static_cast<INSTALLUILEVEL>(INSTALLUILEVEL_NONE | INSTALLUILEVEL_SOURCERESONLY); | ||
468 | WIU_MSI_EXECUTE_CONTEXT context = { }; | ||
469 | WIU_RESTART restart = WIU_RESTART_NONE; | ||
470 | |||
471 | LPWSTR sczCachedDirectory = NULL; | ||
472 | LPWSTR sczMspPath = NULL; | ||
473 | LPWSTR sczPatches = NULL; | ||
474 | LPWSTR sczProperties = NULL; | ||
475 | LPWSTR sczObfuscatedProperties = NULL; | ||
476 | |||
477 | // default to "verbose" logging | ||
478 | DWORD dwLogMode = WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE; | ||
479 | |||
480 | // get cached MSP paths | ||
481 | for (DWORD i = 0; i < pExecuteAction->mspTarget.cOrderedPatches; ++i) | ||
482 | { | ||
483 | LPCWSTR wzAppend = NULL; | ||
484 | BURN_PACKAGE* pMspPackage = pExecuteAction->mspTarget.rgOrderedPatches[i].pPackage; | ||
485 | AssertSz(BURN_PACKAGE_TYPE_MSP == pMspPackage->type, "Invalid package type added to ordered patches."); | ||
486 | |||
487 | if (BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->mspTarget.action) | ||
488 | { | ||
489 | hr = CacheGetCompletedPath(pMspPackage->fPerMachine, pMspPackage->sczCacheId, &sczCachedDirectory); | ||
490 | ExitOnFailure(hr, "Failed to get cached path for MSP package: %ls", pMspPackage->sczId); | ||
491 | |||
492 | // TODO: Figure out if this makes sense -- the variable is set to the last patch's path only | ||
493 | // Best effort to set the execute package cache folder variable. | ||
494 | VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE); | ||
495 | |||
496 | hr = PathConcat(sczCachedDirectory, pMspPackage->rgPayloads[0].pPayload->sczFilePath, &sczMspPath); | ||
497 | ExitOnFailure(hr, "Failed to build MSP path."); | ||
498 | |||
499 | wzAppend = sczMspPath; | ||
500 | } | ||
501 | else // uninstall | ||
502 | { | ||
503 | wzAppend = pMspPackage->Msp.sczPatchCode; | ||
504 | } | ||
505 | |||
506 | if (NULL != sczPatches) | ||
507 | { | ||
508 | hr = StrAllocConcat(&sczPatches, L";", 0); | ||
509 | ExitOnFailure(hr, "Failed to semi-colon delimit patches."); | ||
510 | } | ||
511 | |||
512 | hr = StrAllocConcat(&sczPatches, wzAppend, 0); | ||
513 | ExitOnFailure(hr, "Failed to append patch."); | ||
514 | } | ||
515 | |||
516 | // Best effort to set the execute package action variable. | ||
517 | VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, pExecuteAction->mspTarget.action, TRUE); | ||
518 | |||
519 | // Wire up the external UI handler and logging. | ||
520 | hr = WiuInitializeExternalUI(pfnMessageHandler, uiLevel, hwndParent, pvContext, fRollback, &context); | ||
521 | ExitOnFailure(hr, "Failed to initialize external UI handler."); | ||
522 | |||
523 | //if (BURN_LOGGING_LEVEL_DEBUG == logLevel) | ||
524 | //{ | ||
525 | // dwLogMode | INSTALLLOGMODE_EXTRADEBUG; | ||
526 | //} | ||
527 | |||
528 | if (pExecuteAction->mspTarget.sczLogPath && *pExecuteAction->mspTarget.sczLogPath) | ||
529 | { | ||
530 | hr = WiuEnableLog(dwLogMode, pExecuteAction->mspTarget.sczLogPath, 0); | ||
531 | ExitOnFailure(hr, "Failed to enable logging for package: %ls to: %ls", pExecuteAction->mspTarget.pPackage->sczId, pExecuteAction->mspTarget.sczLogPath); | ||
532 | } | ||
533 | |||
534 | // set up properties | ||
535 | hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczProperties, FALSE); | ||
536 | ExitOnFailure(hr, "Failed to add properties to argument string."); | ||
537 | |||
538 | hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczObfuscatedProperties, TRUE); | ||
539 | ExitOnFailure(hr, "Failed to add properties to obfuscated argument string."); | ||
540 | |||
541 | LogId(REPORT_STANDARD, MSG_APPLYING_PATCH_PACKAGE, pExecuteAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pExecuteAction->mspTarget.action), sczPatches, sczObfuscatedProperties, pExecuteAction->mspTarget.sczTargetProductCode); | ||
542 | |||
543 | // | ||
544 | // Do the actual action. | ||
545 | // | ||
546 | switch (pExecuteAction->mspTarget.action) | ||
547 | { | ||
548 | case BOOTSTRAPPER_ACTION_STATE_INSTALL: __fallthrough; | ||
549 | case BOOTSTRAPPER_ACTION_STATE_REPAIR: | ||
550 | hr = StrAllocConcatSecure(&sczProperties, L" PATCH=\"", 0); | ||
551 | ExitOnFailure(hr, "Failed to add PATCH property on install."); | ||
552 | |||
553 | hr = StrAllocConcatSecure(&sczProperties, sczPatches, 0); | ||
554 | ExitOnFailure(hr, "Failed to add patches to PATCH property on install."); | ||
555 | |||
556 | hr = StrAllocConcatSecure(&sczProperties, L"\" REBOOT=ReallySuppress", 0); | ||
557 | ExitOnFailure(hr, "Failed to add reboot suppression property on install."); | ||
558 | |||
559 | hr = WiuConfigureProductEx(pExecuteAction->mspTarget.sczTargetProductCode, INSTALLLEVEL_DEFAULT, INSTALLSTATE_DEFAULT, sczProperties, &restart); | ||
560 | ExitOnFailure(hr, "Failed to install MSP package."); | ||
561 | break; | ||
562 | |||
563 | case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: | ||
564 | hr = StrAllocConcatSecure(&sczProperties, L" REBOOT=ReallySuppress", 0); | ||
565 | ExitOnFailure(hr, "Failed to add reboot suppression property on uninstall."); | ||
566 | |||
567 | // Ignore all dependencies, since the Burn engine already performed the check. | ||
568 | hr = StrAllocFormattedSecure(&sczProperties, L"%ls %ls=ALL", sczProperties, DEPENDENCY_IGNOREDEPENDENCIES); | ||
569 | ExitOnFailure(hr, "Failed to add the list of dependencies to ignore to the properties."); | ||
570 | |||
571 | hr = WiuRemovePatches(sczPatches, pExecuteAction->mspTarget.sczTargetProductCode, sczProperties, &restart); | ||
572 | ExitOnFailure(hr, "Failed to uninstall MSP package."); | ||
573 | break; | ||
574 | } | ||
575 | |||
576 | LExit: | ||
577 | WiuUninitializeExternalUI(&context); | ||
578 | |||
579 | ReleaseStr(sczCachedDirectory); | ||
580 | ReleaseStr(sczMspPath); | ||
581 | StrSecureZeroFreeString(sczProperties); | ||
582 | ReleaseStr(sczObfuscatedProperties); | ||
583 | ReleaseStr(sczPatches); | ||
584 | |||
585 | switch (restart) | ||
586 | { | ||
587 | case WIU_RESTART_NONE: | ||
588 | *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; | ||
589 | break; | ||
590 | |||
591 | case WIU_RESTART_REQUIRED: | ||
592 | *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; | ||
593 | break; | ||
594 | |||
595 | case WIU_RESTART_INITIATED: | ||
596 | *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; | ||
597 | break; | ||
598 | } | ||
599 | |||
600 | // Best effort to clear the execute package cache folder and action variables. | ||
601 | VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE); | ||
602 | VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE); | ||
603 | |||
604 | return hr; | ||
605 | } | ||
606 | |||
607 | extern "C" void MspEngineSlipstreamUpdateState( | ||
608 | __in BURN_PACKAGE* pPackage, | ||
609 | __in BOOTSTRAPPER_ACTION_STATE execute, | ||
610 | __in BOOTSTRAPPER_ACTION_STATE rollback | ||
611 | ) | ||
612 | { | ||
613 | Assert(BURN_PACKAGE_TYPE_MSP == pPackage->type); | ||
614 | |||
615 | // If the dependency manager set our state then that means something else | ||
616 | // is dependent on our package. That trumps whatever the slipstream update | ||
617 | // state might set. | ||
618 | if (!pPackage->fDependencyManagerWasHere) | ||
619 | { | ||
620 | // The highest aggregate action state found will be returned. | ||
621 | if (pPackage->execute < execute) | ||
622 | { | ||
623 | pPackage->execute = execute; | ||
624 | } | ||
625 | |||
626 | if (pPackage->rollback < rollback) | ||
627 | { | ||
628 | pPackage->rollback = rollback; | ||
629 | } | ||
630 | } | ||
631 | } | ||
632 | |||
633 | |||
634 | // internal helper functions | ||
635 | |||
636 | static HRESULT GetPossibleTargetProductCodes( | ||
637 | __in BURN_PACKAGES* pPackages, | ||
638 | __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, | ||
639 | __inout DWORD* pcPossibleTargetProducts | ||
640 | ) | ||
641 | { | ||
642 | HRESULT hr = S_OK; | ||
643 | STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes = NULL; | ||
644 | BOOL fCheckAll = FALSE; | ||
645 | WCHAR wzPossibleTargetProductCode[MAX_GUID_CHARS + 1]; | ||
646 | |||
647 | // Use a dictionary to ensure we capture unique product codes. Otherwise, we could end up | ||
648 | // doing patch applicability for the same product code multiple times and that would confuse | ||
649 | // everything down stream. | ||
650 | hr = DictCreateStringList(&sdUniquePossibleTargetProductCodes, 5, DICT_FLAG_NONE); | ||
651 | ExitOnFailure(hr, "Failed to create unique target product codes."); | ||
652 | |||
653 | // If the patches target a specific set of product/upgrade codes, search only those. This | ||
654 | // should be much faster than searching all packages on the machine. | ||
655 | if (pPackages->rgPatchTargetCodes) | ||
656 | { | ||
657 | for (DWORD i = 0; i < pPackages->cPatchTargetCodes; ++i) | ||
658 | { | ||
659 | BURN_PATCH_TARGETCODE* pTargetCode = pPackages->rgPatchTargetCodes + i; | ||
660 | |||
661 | // If targeting a product, add the unique product code to the list. | ||
662 | if (BURN_PATCH_TARGETCODE_TYPE_PRODUCT == pTargetCode->type) | ||
663 | { | ||
664 | hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, pTargetCode->sczTargetCode, MSIINSTALLCONTEXT_NONE, prgPossibleTargetProducts, pcPossibleTargetProducts); | ||
665 | ExitOnFailure(hr, "Failed to add product code to possible target product codes."); | ||
666 | } | ||
667 | else if (BURN_PATCH_TARGETCODE_TYPE_UPGRADE == pTargetCode->type) | ||
668 | { | ||
669 | // Enumerate all unique related products to the target upgrade code. | ||
670 | for (DWORD iProduct = 0; SUCCEEDED(hr); ++iProduct) | ||
671 | { | ||
672 | hr = WiuEnumRelatedProducts(pTargetCode->sczTargetCode, iProduct, wzPossibleTargetProductCode); | ||
673 | if (SUCCEEDED(hr)) | ||
674 | { | ||
675 | hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode, MSIINSTALLCONTEXT_NONE, prgPossibleTargetProducts, pcPossibleTargetProducts); | ||
676 | ExitOnFailure(hr, "Failed to add upgrade product code to possible target product codes."); | ||
677 | } | ||
678 | else if (E_BADCONFIGURATION == hr) | ||
679 | { | ||
680 | // Skip product's with bad configuration and continue. | ||
681 | LogId(REPORT_STANDARD, MSG_DETECT_BAD_PRODUCT_CONFIGURATION, wzPossibleTargetProductCode); | ||
682 | |||
683 | hr = S_OK; | ||
684 | } | ||
685 | } | ||
686 | |||
687 | if (E_NOMOREITEMS == hr) | ||
688 | { | ||
689 | hr = S_OK; | ||
690 | } | ||
691 | ExitOnFailure(hr, "Failed to enumerate all products to patch related to upgrade code: %ls", pTargetCode->sczTargetCode); | ||
692 | } | ||
693 | else | ||
694 | { | ||
695 | // The element does not target a specific product. | ||
696 | fCheckAll = TRUE; | ||
697 | |||
698 | break; | ||
699 | } | ||
700 | } | ||
701 | } | ||
702 | else | ||
703 | { | ||
704 | fCheckAll = TRUE; | ||
705 | } | ||
706 | |||
707 | // One or more of the patches do not target a specific product so search everything on the machine. | ||
708 | if (fCheckAll) | ||
709 | { | ||
710 | for (DWORD iProduct = 0; SUCCEEDED(hr); ++iProduct) | ||
711 | { | ||
712 | MSIINSTALLCONTEXT context = MSIINSTALLCONTEXT_NONE; | ||
713 | |||
714 | hr = WiuEnumProductsEx(NULL, NULL, MSIINSTALLCONTEXT_ALL, iProduct, wzPossibleTargetProductCode, &context, NULL, NULL); | ||
715 | if (SUCCEEDED(hr)) | ||
716 | { | ||
717 | hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode, context, prgPossibleTargetProducts, pcPossibleTargetProducts); | ||
718 | ExitOnFailure(hr, "Failed to add product code to search product codes."); | ||
719 | } | ||
720 | else if (E_BADCONFIGURATION == hr) | ||
721 | { | ||
722 | // Skip products with bad configuration and continue. | ||
723 | LogId(REPORT_STANDARD, MSG_DETECT_BAD_PRODUCT_CONFIGURATION, wzPossibleTargetProductCode); | ||
724 | |||
725 | hr = S_OK; | ||
726 | } | ||
727 | } | ||
728 | |||
729 | if (E_NOMOREITEMS == hr) | ||
730 | { | ||
731 | hr = S_OK; | ||
732 | } | ||
733 | ExitOnFailure(hr, "Failed to enumerate all products on the machine for patches applicability."); | ||
734 | } | ||
735 | |||
736 | LExit: | ||
737 | ReleaseDict(sdUniquePossibleTargetProductCodes); | ||
738 | |||
739 | return hr; | ||
740 | } | ||
741 | |||
742 | static HRESULT AddPossibleTargetProduct( | ||
743 | __in STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes, | ||
744 | __in_z LPCWSTR wzPossibleTargetProductCode, | ||
745 | __in MSIINSTALLCONTEXT context, | ||
746 | __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, | ||
747 | __inout DWORD* pcPossibleTargetProducts | ||
748 | ) | ||
749 | { | ||
750 | HRESULT hr = S_OK; | ||
751 | LPWSTR pszLocalPackage = NULL; | ||
752 | |||
753 | // Only add this possible target code if we haven't queried for it already. | ||
754 | if (E_NOTFOUND == DictKeyExists(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode)) | ||
755 | { | ||
756 | // If the install context is not known, ask the Windows Installer for it. If we can't get the context | ||
757 | // then bail. | ||
758 | if (MSIINSTALLCONTEXT_NONE == context) | ||
759 | { | ||
760 | hr = WiuEnumProductsEx(wzPossibleTargetProductCode, NULL, MSIINSTALLCONTEXT_ALL, 0, NULL, &context, NULL, NULL); | ||
761 | if (FAILED(hr)) | ||
762 | { | ||
763 | ExitFunction1(hr = S_OK); | ||
764 | } | ||
765 | } | ||
766 | |||
767 | hr = DictAddKey(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode); | ||
768 | ExitOnFailure(hr, "Failed to add possible target code to unique product codes."); | ||
769 | |||
770 | hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(prgPossibleTargetProducts), *pcPossibleTargetProducts + 1, sizeof(POSSIBLE_TARGETPRODUCT), 3); | ||
771 | ExitOnFailure(hr, "Failed to grow array of possible target products."); | ||
772 | |||
773 | POSSIBLE_TARGETPRODUCT *const pPossibleTargetProduct = *prgPossibleTargetProducts + *pcPossibleTargetProducts; | ||
774 | |||
775 | hr = ::StringCchCopyW(pPossibleTargetProduct->wzProductCode, countof(pPossibleTargetProduct->wzProductCode), wzPossibleTargetProductCode); | ||
776 | ExitOnFailure(hr, "Failed to copy possible target product code."); | ||
777 | |||
778 | // Attempt to get the local package path so we can more quickly determine patch applicability later. | ||
779 | hr = WiuGetProductInfoEx(wzPossibleTargetProductCode, NULL, context, INSTALLPROPERTY_LOCALPACKAGE, &pszLocalPackage); | ||
780 | if (SUCCEEDED(hr)) | ||
781 | { | ||
782 | pPossibleTargetProduct->pszLocalPackage = pszLocalPackage; | ||
783 | pszLocalPackage = NULL; | ||
784 | } | ||
785 | else | ||
786 | { | ||
787 | // Will instead call MsiDeterminePatchSequence later. | ||
788 | hr = S_OK; | ||
789 | } | ||
790 | |||
791 | pPossibleTargetProduct->context = context; | ||
792 | |||
793 | ++(*pcPossibleTargetProducts); | ||
794 | } | ||
795 | |||
796 | LExit: | ||
797 | ReleaseStr(pszLocalPackage); | ||
798 | |||
799 | return hr; | ||
800 | } | ||
801 | |||
802 | static HRESULT AddDetectedTargetProduct( | ||
803 | __in BURN_PACKAGES* pPackages, | ||
804 | __in BURN_PACKAGE* pPackage, | ||
805 | __in DWORD dwOrder, | ||
806 | __in_z LPCWSTR wzProductCode, | ||
807 | __in MSIINSTALLCONTEXT context | ||
808 | ) | ||
809 | { | ||
810 | HRESULT hr = S_OK; | ||
811 | |||
812 | hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pPackage->Msp.rgTargetProducts), pPackage->Msp.cTargetProductCodes + 1, sizeof(BURN_MSPTARGETPRODUCT), 5); | ||
813 | ExitOnFailure(hr, "Failed to ensure enough target product codes were allocated."); | ||
814 | |||
815 | hr = ::StringCchCopyW(pPackage->Msp.rgTargetProducts[pPackage->Msp.cTargetProductCodes].wzTargetProductCode, countof(pPackage->Msp.rgTargetProducts[pPackage->Msp.cTargetProductCodes].wzTargetProductCode), wzProductCode); | ||
816 | ExitOnFailure(hr, "Failed to copy target product code."); | ||
817 | |||
818 | DeterminePatchChainedTarget(pPackages, pPackage, wzProductCode, | ||
819 | &pPackage->Msp.rgTargetProducts[pPackage->Msp.cTargetProductCodes].pChainedTargetPackage, | ||
820 | &pPackage->Msp.rgTargetProducts[pPackage->Msp.cTargetProductCodes].fSlipstream); | ||
821 | |||
822 | pPackage->Msp.rgTargetProducts[pPackage->Msp.cTargetProductCodes].context = context; | ||
823 | pPackage->Msp.rgTargetProducts[pPackage->Msp.cTargetProductCodes].dwOrder = dwOrder; | ||
824 | ++pPackage->Msp.cTargetProductCodes; | ||
825 | |||
826 | LExit: | ||
827 | return hr; | ||
828 | } | ||
829 | |||
830 | static void DeterminePatchChainedTarget( | ||
831 | __in BURN_PACKAGES* pPackages, | ||
832 | __in BURN_PACKAGE* pMspPackage, | ||
833 | __in LPCWSTR wzTargetProductCode, | ||
834 | __out BURN_PACKAGE** ppChainedTargetPackage, | ||
835 | __out BOOL* pfSlipstreamed | ||
836 | ) | ||
837 | { | ||
838 | BURN_PACKAGE* pTargetMsiPackage = NULL; | ||
839 | BOOL fSlipstreamed = FALSE; | ||
840 | |||
841 | for (DWORD iPackage = 0; iPackage < pPackages->cPackages; ++iPackage) | ||
842 | { | ||
843 | BURN_PACKAGE* pPackage = pPackages->rgPackages + iPackage; | ||
844 | |||
845 | if (BURN_PACKAGE_TYPE_MSI == pPackage->type && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzTargetProductCode, -1, pPackage->Msi.sczProductCode, -1)) | ||
846 | { | ||
847 | pTargetMsiPackage = pPackage; | ||
848 | |||
849 | for (DWORD j = 0; j < pPackage->Msi.cSlipstreamMspPackages; ++j) | ||
850 | { | ||
851 | BURN_PACKAGE* pSlipstreamMsp = pPackage->Msi.rgpSlipstreamMspPackages[j]; | ||
852 | if (pSlipstreamMsp == pMspPackage) | ||
853 | { | ||
854 | AssertSz(!fSlipstreamed, "An MSP should only show up as a slipstreamed patch in an MSI once."); | ||
855 | fSlipstreamed = TRUE; | ||
856 | break; | ||
857 | } | ||
858 | } | ||
859 | |||
860 | break; | ||
861 | } | ||
862 | } | ||
863 | |||
864 | *ppChainedTargetPackage = pTargetMsiPackage; | ||
865 | *pfSlipstreamed = fSlipstreamed; | ||
866 | |||
867 | return; | ||
868 | } | ||
869 | |||
870 | static HRESULT PlanTargetProduct( | ||
871 | __in BOOTSTRAPPER_DISPLAY display, | ||
872 | __in BOOL fRollback, | ||
873 | __in BURN_PLAN* pPlan, | ||
874 | __in BURN_LOGGING* pLog, | ||
875 | __in BURN_VARIABLES* pVariables, | ||
876 | __in BOOTSTRAPPER_ACTION_STATE actionState, | ||
877 | __in BURN_PACKAGE* pPackage, | ||
878 | __in BURN_MSPTARGETPRODUCT* pTargetProduct, | ||
879 | __in_opt HANDLE hCacheEvent | ||
880 | ) | ||
881 | { | ||
882 | HRESULT hr = S_OK; | ||
883 | BURN_EXECUTE_ACTION* rgActions = fRollback ? pPlan->rgRollbackActions : pPlan->rgExecuteActions; | ||
884 | DWORD cActions = fRollback ? pPlan->cRollbackActions : pPlan->cExecuteActions; | ||
885 | BURN_EXECUTE_ACTION* pAction = NULL; | ||
886 | DWORD dwInsertSequence = 0; | ||
887 | |||
888 | // Try to find another MSP action with the exact same action (install or uninstall) targeting | ||
889 | // the same product in the same machine context (per-user or per-machine). | ||
890 | for (DWORD i = 0; i < cActions; ++i) | ||
891 | { | ||
892 | pAction = rgActions + i; | ||
893 | |||
894 | if (BURN_EXECUTE_ACTION_TYPE_MSP_TARGET == pAction->type && | ||
895 | pAction->mspTarget.action == actionState && | ||
896 | pAction->mspTarget.fPerMachineTarget == (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context) && | ||
897 | CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pAction->mspTarget.sczTargetProductCode, -1, pTargetProduct->wzTargetProductCode, -1)) | ||
898 | { | ||
899 | dwInsertSequence = i; | ||
900 | break; | ||
901 | } | ||
902 | |||
903 | pAction = NULL; | ||
904 | } | ||
905 | |||
906 | // If we didn't find an MSP target action already updating the product, create a new action. | ||
907 | if (!pAction) | ||
908 | { | ||
909 | if (fRollback) | ||
910 | { | ||
911 | hr = PlanAppendRollbackAction(pPlan, &pAction); | ||
912 | } | ||
913 | else | ||
914 | { | ||
915 | hr = PlanAppendExecuteAction(pPlan, &pAction); | ||
916 | } | ||
917 | ExitOnFailure(hr, "Failed to plan action for target product."); | ||
918 | |||
919 | pAction->type = BURN_EXECUTE_ACTION_TYPE_MSP_TARGET; | ||
920 | pAction->mspTarget.action = actionState; | ||
921 | pAction->mspTarget.pPackage = pPackage; | ||
922 | pAction->mspTarget.fPerMachineTarget = (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context); | ||
923 | pAction->mspTarget.uiLevel = MsiEngineCalculateInstallUiLevel(pPackage->Msp.fDisplayInternalUI, display, pAction->mspTarget.action); | ||
924 | pAction->mspTarget.pChainedTargetPackage = pTargetProduct->pChainedTargetPackage; | ||
925 | pAction->mspTarget.fSlipstream = pTargetProduct->fSlipstream; | ||
926 | hr = StrAllocString(&pAction->mspTarget.sczTargetProductCode, pTargetProduct->wzTargetProductCode, 0); | ||
927 | ExitOnFailure(hr, "Failed to copy target product code."); | ||
928 | |||
929 | // If this is a per-machine target product, then the plan needs to be per-machine as well. | ||
930 | if (pAction->mspTarget.fPerMachineTarget) | ||
931 | { | ||
932 | pPlan->fPerMachine = TRUE; | ||
933 | } | ||
934 | |||
935 | LoggingSetPackageVariable(pPackage, pAction->mspTarget.sczTargetProductCode, fRollback, pLog, pVariables, &pAction->mspTarget.sczLogPath); // ignore errors. | ||
936 | } | ||
937 | else | ||
938 | { | ||
939 | if (!fRollback && hCacheEvent) | ||
940 | { | ||
941 | // Since a previouse MSP target action is being updated with the new MSP, | ||
942 | // insert a wait syncpoint to before this action since we need to cache the current MSI before using it. | ||
943 | BURN_EXECUTE_ACTION* pWaitSyncPointAction = NULL; | ||
944 | hr = PlanInsertExecuteAction(dwInsertSequence, pPlan, &pWaitSyncPointAction); | ||
945 | ExitOnFailure(hr, "Failed to insert execute action."); | ||
946 | |||
947 | pWaitSyncPointAction->type = BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT; | ||
948 | pWaitSyncPointAction->syncpoint.hEvent = hCacheEvent; | ||
949 | |||
950 | // Since we inserted an action before the MSP target action that we will be updating, need to update the pointer. | ||
951 | pAction = pPlan->rgExecuteActions + (dwInsertSequence + 1); | ||
952 | } | ||
953 | } | ||
954 | |||
955 | // Add our target product to the array and sort based on their order determined during detection. | ||
956 | hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pAction->mspTarget.rgOrderedPatches), pAction->mspTarget.cOrderedPatches + 1, sizeof(BURN_ORDERED_PATCHES), 2); | ||
957 | ExitOnFailure(hr, "Failed grow array of ordered patches."); | ||
958 | |||
959 | pAction->mspTarget.rgOrderedPatches[pAction->mspTarget.cOrderedPatches].dwOrder = pTargetProduct->dwOrder; | ||
960 | pAction->mspTarget.rgOrderedPatches[pAction->mspTarget.cOrderedPatches].pPackage = pPackage; | ||
961 | ++pAction->mspTarget.cOrderedPatches; | ||
962 | |||
963 | // Insertion sort to keep the patches ordered. | ||
964 | for (DWORD i = pAction->mspTarget.cOrderedPatches - 1; i > 0; --i) | ||
965 | { | ||
966 | if (pAction->mspTarget.rgOrderedPatches[i].dwOrder < pAction->mspTarget.rgOrderedPatches[i - 1].dwOrder) | ||
967 | { | ||
968 | BURN_ORDERED_PATCHES temp = pAction->mspTarget.rgOrderedPatches[i - 1]; | ||
969 | pAction->mspTarget.rgOrderedPatches[i - 1] = pAction->mspTarget.rgOrderedPatches[i]; | ||
970 | pAction->mspTarget.rgOrderedPatches[i] = temp; | ||
971 | } | ||
972 | else // no swap necessary, we're done. | ||
973 | { | ||
974 | break; | ||
975 | } | ||
976 | } | ||
977 | |||
978 | LExit: | ||
979 | return hr; | ||
980 | } | ||