aboutsummaryrefslogtreecommitdiff
path: root/src/engine/mspengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/mspengine.cpp')
-rw-r--r--src/engine/mspengine.cpp980
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
11struct POSSIBLE_TARGETPRODUCT
12{
13 WCHAR wzProductCode[39];
14 LPWSTR pszLocalPackage;
15 MSIINSTALLCONTEXT context;
16};
17
18// internal function declarations
19
20static HRESULT GetPossibleTargetProductCodes(
21 __in BURN_PACKAGES* pPackages,
22 __deref_inout_ecount_opt(*pcPossibleTargetProductCodes) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProductCodes,
23 __inout DWORD* pcPossibleTargetProductCodes
24 );
25static 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 );
32static 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 );
39static 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 );
46static 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
61extern "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
84LExit:
85
86 return hr;
87}
88
89extern "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
117extern "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
185LExit:
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
198extern "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
260LExit:
261 ReleaseStr(sczState);
262
263 return hr;
264}
265
266//
267// PlanCalculate - calculates the execute and rollback state for the requested package state.
268//
269extern "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
394LExit:
395
396 return hr;
397}
398
399//
400// PlanAdd - adds the calculated execute and rollback actions for the package.
401//
402extern "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
451LExit:
452
453 return hr;
454}
455
456extern "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
576LExit:
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
607extern "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
636static 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
736LExit:
737 ReleaseDict(sdUniquePossibleTargetProductCodes);
738
739 return hr;
740}
741
742static 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
796LExit:
797 ReleaseStr(pszLocalPackage);
798
799 return hr;
800}
801
802static 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
826LExit:
827 return hr;
828}
829
830static 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
870static 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
978LExit:
979 return hr;
980}