aboutsummaryrefslogtreecommitdiff
path: root/src/burn/engine/bundlepackageengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/burn/engine/bundlepackageengine.cpp')
-rw-r--r--src/burn/engine/bundlepackageengine.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp
new file mode 100644
index 00000000..10022b6a
--- /dev/null
+++ b/src/burn/engine/bundlepackageengine.cpp
@@ -0,0 +1,460 @@
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
7// function definitions
8
9extern "C" void BundlePackageEnginePackageUninitialize(
10 __in BURN_PACKAGE* pPackage
11 )
12{
13 ReleaseStr(pPackage->Bundle.sczInstallArguments);
14 ReleaseStr(pPackage->Bundle.sczRepairArguments);
15 ReleaseStr(pPackage->Bundle.sczUninstallArguments);
16 ReleaseStr(pPackage->Bundle.sczIgnoreDependencies);
17 ReleaseMem(pPackage->Bundle.rgExitCodes);
18
19 // free command-line arguments
20 if (pPackage->Bundle.rgCommandLineArguments)
21 {
22 for (DWORD i = 0; i < pPackage->Bundle.cCommandLineArguments; ++i)
23 {
24 ExeEngineCommandLineArgumentUninitialize(pPackage->Bundle.rgCommandLineArguments + i);
25 }
26 MemFree(pPackage->Bundle.rgCommandLineArguments);
27 }
28
29 // clear struct
30 memset(&pPackage->Bundle, 0, sizeof(pPackage->Bundle));
31}
32
33//
34// PlanCalculate - calculates the execute and rollback state for the requested package state.
35//
36extern "C" HRESULT BundlePackageEnginePlanCalculatePackage(
37 __in BURN_PACKAGE* pPackage
38 )
39{
40 HRESULT hr = S_OK;
41 BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE;
42 BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
43
44 // execute action
45 switch (pPackage->currentState)
46 {
47 case BOOTSTRAPPER_PACKAGE_STATE_PRESENT:
48 switch (pPackage->requested)
49 {
50 case BOOTSTRAPPER_REQUEST_STATE_PRESENT:
51 execute = pPackage->Bundle.fPseudoBundle ? BOOTSTRAPPER_ACTION_STATE_INSTALL : BOOTSTRAPPER_ACTION_STATE_NONE;
52 break;
53 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
54 execute = pPackage->Bundle.fRepairable ? BOOTSTRAPPER_ACTION_STATE_REPAIR : BOOTSTRAPPER_ACTION_STATE_NONE;
55 break;
56 case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough;
57 case BOOTSTRAPPER_REQUEST_STATE_CACHE:
58 execute = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE;
59 break;
60 case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT:
61 execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL;
62 break;
63 default:
64 execute = BOOTSTRAPPER_ACTION_STATE_NONE;
65 break;
66 }
67 break;
68
69 case BOOTSTRAPPER_PACKAGE_STATE_ABSENT:
70 switch (pPackage->requested)
71 {
72 case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough;
73 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
74 execute = BOOTSTRAPPER_ACTION_STATE_INSTALL;
75 break;
76 default:
77 execute = BOOTSTRAPPER_ACTION_STATE_NONE;
78 break;
79 }
80 break;
81
82 default:
83 hr = E_INVALIDARG;
84 ExitOnRootFailure(hr, "Invalid package current state: %d.", pPackage->currentState);
85 }
86
87 // Calculate the rollback action if there is an execute action.
88 if (BOOTSTRAPPER_ACTION_STATE_NONE != execute)
89 {
90 switch (pPackage->currentState)
91 {
92 case BOOTSTRAPPER_PACKAGE_STATE_PRESENT:
93 switch (pPackage->requested)
94 {
95 case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough;
96 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
97 rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
98 break;
99 case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough;
100 case BOOTSTRAPPER_REQUEST_STATE_ABSENT:
101 rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL;
102 break;
103 default:
104 rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
105 break;
106 }
107 break;
108
109 case BOOTSTRAPPER_PACKAGE_STATE_ABSENT:
110 switch (pPackage->requested)
111 {
112 case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough;
113 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
114 rollback = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE;
115 break;
116 case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough;
117 case BOOTSTRAPPER_REQUEST_STATE_ABSENT:
118 rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
119 break;
120 default:
121 rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
122 break;
123 }
124 break;
125
126 default:
127 hr = E_INVALIDARG;
128 ExitOnRootFailure(hr, "Invalid package expected state.");
129 }
130 }
131
132 // return values
133 pPackage->execute = execute;
134 pPackage->rollback = rollback;
135
136LExit:
137 return hr;
138}
139
140//
141// PlanAdd - adds the calculated execute and rollback actions for the package.
142//
143extern "C" HRESULT BundlePackageEnginePlanAddRelatedBundle(
144 __in_opt DWORD *pdwInsertSequence,
145 __in BURN_RELATED_BUNDLE* pRelatedBundle,
146 __in BURN_PLAN* pPlan,
147 __in BURN_LOGGING* pLog,
148 __in BURN_VARIABLES* pVariables
149 )
150{
151 HRESULT hr = S_OK;
152 BURN_EXECUTE_ACTION* pAction = NULL;
153 BURN_PACKAGE* pPackage = &pRelatedBundle->package;
154
155 hr = DependencyPlanPackage(pdwInsertSequence, pPackage, pPlan);
156 ExitOnFailure(hr, "Failed to plan package dependency actions.");
157
158 // add execute action
159 if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute)
160 {
161 if (pdwInsertSequence)
162 {
163 hr = PlanInsertExecuteAction(*pdwInsertSequence, pPlan, &pAction);
164 ExitOnFailure(hr, "Failed to insert execute action.");
165 }
166 else
167 {
168 hr = PlanAppendExecuteAction(pPlan, &pAction);
169 ExitOnFailure(hr, "Failed to append execute action.");
170 }
171
172 pAction->type = BURN_EXECUTE_ACTION_TYPE_RELATED_BUNDLE;
173 pAction->relatedBundle.pRelatedBundle = pRelatedBundle;
174 pAction->relatedBundle.action = pPackage->execute;
175
176 if (pPackage->Bundle.sczIgnoreDependencies)
177 {
178 hr = StrAllocString(&pAction->relatedBundle.sczIgnoreDependencies, pPackage->Bundle.sczIgnoreDependencies, 0);
179 ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore.");
180 }
181
182 if (pPackage->Bundle.wzAncestors)
183 {
184 hr = StrAllocString(&pAction->relatedBundle.sczAncestors, pPackage->Bundle.wzAncestors, 0);
185 ExitOnFailure(hr, "Failed to allocate the list of ancestors.");
186 }
187
188 if (pPackage->Bundle.wzEngineWorkingDirectory)
189 {
190 hr = StrAllocString(&pAction->relatedBundle.sczEngineWorkingDirectory, pPackage->Bundle.wzEngineWorkingDirectory, 0);
191 ExitOnFailure(hr, "Failed to allocate the custom working directory.");
192 }
193
194 LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, NULL); // ignore errors.
195 }
196
197 // add rollback action
198 if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback)
199 {
200 hr = PlanAppendRollbackAction(pPlan, &pAction);
201 ExitOnFailure(hr, "Failed to append rollback action.");
202
203 pAction->type = BURN_EXECUTE_ACTION_TYPE_RELATED_BUNDLE;
204 pAction->relatedBundle.pRelatedBundle = pRelatedBundle;
205 pAction->relatedBundle.action = pPackage->rollback;
206
207 if (pPackage->Bundle.sczIgnoreDependencies)
208 {
209 hr = StrAllocString(&pAction->relatedBundle.sczIgnoreDependencies, pPackage->Bundle.sczIgnoreDependencies, 0);
210 ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore.");
211 }
212
213 if (pPackage->Bundle.wzAncestors)
214 {
215 hr = StrAllocString(&pAction->relatedBundle.sczAncestors, pPackage->Bundle.wzAncestors, 0);
216 ExitOnFailure(hr, "Failed to allocate the list of ancestors.");
217 }
218
219 if (pPackage->Bundle.wzEngineWorkingDirectory)
220 {
221 hr = StrAllocString(&pAction->relatedBundle.sczEngineWorkingDirectory, pPackage->Bundle.wzEngineWorkingDirectory, 0);
222 ExitOnFailure(hr, "Failed to allocate the custom working directory.");
223 }
224
225 LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, NULL); // ignore errors.
226 }
227
228LExit:
229 return hr;
230}
231
232extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
233 __in BURN_EXECUTE_ACTION* pExecuteAction,
234 __in BURN_CACHE* pCache,
235 __in BURN_VARIABLES* pVariables,
236 __in BOOL fRollback,
237 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
238 __in LPVOID pvContext,
239 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
240 )
241{
242 HRESULT hr = S_OK;
243 int nResult = IDNOACTION;
244 LPCWSTR wzArguments = NULL;
245 LPWSTR sczArguments = NULL;
246 LPWSTR sczArgumentsFormatted = NULL;
247 LPWSTR sczArgumentsObfuscated = NULL;
248 LPWSTR sczCachedDirectory = NULL;
249 LPWSTR sczExecutablePath = NULL;
250 LPWSTR sczCommand = NULL;
251 LPWSTR sczCommandObfuscated = NULL;
252 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
253 STARTUPINFOW si = { };
254 PROCESS_INFORMATION pi = { };
255 DWORD dwExitCode = 0;
256 GENERIC_EXECUTE_MESSAGE message = { };
257 BOOTSTRAPPER_ACTION_STATE action = pExecuteAction->relatedBundle.action;
258 BURN_RELATED_BUNDLE* pRelatedBundle = pExecuteAction->relatedBundle.pRelatedBundle;
259 BOOTSTRAPPER_RELATION_TYPE relationType = pRelatedBundle->relationType;
260 BURN_PACKAGE* pPackage = &pRelatedBundle->package;
261 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload;
262 LPCWSTR wzRelationTypeCommandLine = CoreRelationTypeToCommandLineString(relationType);
263 LPCWSTR wzOperationCommandLine = NULL;
264 BOOL fRunEmbedded = pPackage->Bundle.fSupportsBurnProtocol;
265
266 // get cached executable path
267 hr = CacheGetCompletedPath(pCache, pPackage->fPerMachine, pPackage->sczCacheId, &sczCachedDirectory);
268 ExitOnFailure(hr, "Failed to get cached path for package: %ls", pPackage->sczId);
269
270 // Best effort to set the execute package cache folder and action variables.
271 VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE);
272 VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, action, TRUE);
273
274 hr = PathConcat(sczCachedDirectory, pPackagePayload->sczFilePath, &sczExecutablePath);
275 ExitOnFailure(hr, "Failed to build executable path.");
276
277 // pick arguments
278 switch (action)
279 {
280 case BOOTSTRAPPER_ACTION_STATE_INSTALL:
281 wzArguments = pPackage->Bundle.sczInstallArguments;
282 break;
283
284 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
285 wzOperationCommandLine = L"-uninstall";
286 wzArguments = pPackage->Bundle.sczUninstallArguments;
287 break;
288
289 case BOOTSTRAPPER_ACTION_STATE_REPAIR:
290 wzOperationCommandLine = L"-repair";
291 wzArguments = pPackage->Bundle.sczRepairArguments;
292 break;
293
294 default:
295 hr = E_INVALIDARG;
296 ExitOnFailure(hr, "Invalid Bundle package action: %d.", action);
297 }
298
299 // now add optional arguments
300 if (wzArguments && *wzArguments)
301 {
302 hr = StrAllocString(&sczArguments, wzArguments, 0);
303 ExitOnFailure(hr, "Failed to copy package arguments.");
304 }
305
306 for (DWORD i = 0; i < pPackage->Bundle.cCommandLineArguments; ++i)
307 {
308 BURN_EXE_COMMAND_LINE_ARGUMENT* commandLineArgument = &pPackage->Bundle.rgCommandLineArguments[i];
309 BOOL fCondition = FALSE;
310
311 hr = ConditionEvaluate(pVariables, commandLineArgument->sczCondition, &fCondition);
312 ExitOnFailure(hr, "Failed to evaluate bundle package command-line condition.");
313
314 if (fCondition)
315 {
316 if (sczArguments)
317 {
318 hr = StrAllocConcat(&sczArguments, L" ", 0);
319 ExitOnFailure(hr, "Failed to separate command-line arguments.");
320 }
321
322 switch (action)
323 {
324 case BOOTSTRAPPER_ACTION_STATE_INSTALL:
325 hr = StrAllocConcat(&sczArguments, commandLineArgument->sczInstallArgument, 0);
326 ExitOnFailure(hr, "Failed to get command-line argument for install.");
327 break;
328
329 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
330 hr = StrAllocConcat(&sczArguments, commandLineArgument->sczUninstallArgument, 0);
331 ExitOnFailure(hr, "Failed to get command-line argument for uninstall.");
332 break;
333
334 case BOOTSTRAPPER_ACTION_STATE_REPAIR:
335 hr = StrAllocConcat(&sczArguments, commandLineArgument->sczRepairArgument, 0);
336 ExitOnFailure(hr, "Failed to get command-line argument for repair.");
337 break;
338
339 default:
340 hr = E_INVALIDARG;
341 ExitOnFailure(hr, "Invalid Bundle package action: %d.", action);
342 }
343 }
344 }
345
346 // build command
347 AppAppendCommandLineArgument(&sczCommand, sczExecutablePath);
348 ExitOnFailure(hr, "Failed to create executable command.");
349
350 if (!fRunEmbedded)
351 {
352 hr = StrAllocConcat(&sczCommand, L" -quiet", 0);
353 ExitOnFailure(hr, "Failed to append quiet argument.");
354 }
355
356 if (wzOperationCommandLine)
357 {
358 hr = StrAllocConcatFormatted(&sczCommand, L" %ls", wzOperationCommandLine);
359 ExitOnFailure(hr, "Failed to append operation argument.");
360 }
361
362 if (wzRelationTypeCommandLine)
363 {
364 hr = StrAllocConcatFormatted(&sczCommand, L" -%ls", wzRelationTypeCommandLine);
365 ExitOnFailure(hr, "Failed to append relation type argument.");
366 }
367
368 // Add the list of dependencies to ignore, if any, to the burn command line.
369 if (pExecuteAction->relatedBundle.sczIgnoreDependencies)
370 {
371 hr = StrAllocConcatFormatted(&sczCommand, L" -%ls=%ls", BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, pExecuteAction->relatedBundle.sczIgnoreDependencies);
372 ExitOnFailure(hr, "Failed to append the list of dependencies to ignore to the command line.");
373 }
374
375 // Add the list of ancestors, if any, to the burn command line.
376 if (pExecuteAction->relatedBundle.sczAncestors)
377 {
378 hr = StrAllocConcatFormatted(&sczCommand, L" -%ls=%ls", BURN_COMMANDLINE_SWITCH_ANCESTORS, pExecuteAction->relatedBundle.sczAncestors);
379 ExitOnFailure(hr, "Failed to append the list of ancestors to the command line.");
380 }
381
382 hr = CoreAppendEngineWorkingDirectoryToCommandLine(pExecuteAction->relatedBundle.sczEngineWorkingDirectory, &sczCommand, NULL);
383 ExitOnFailure(hr, "Failed to append the custom working directory to the bundlepackage command line.");
384
385 hr = CoreAppendFileHandleSelfToCommandLine(sczExecutablePath, &hExecutableFile, &sczCommand, NULL);
386 ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF);
387
388 // Always add user supplied arguments last.
389 if (sczArguments && *sczArguments)
390 {
391 hr = VariableFormatString(pVariables, sczArguments, &sczArgumentsFormatted, NULL);
392 ExitOnFailure(hr, "Failed to format argument string.");
393
394 hr = VariableFormatStringObfuscated(pVariables, sczArguments, &sczArgumentsObfuscated, NULL);
395 ExitOnFailure(hr, "Failed to format obfuscated argument string.");
396
397 hr = StrAllocFormatted(&sczCommandObfuscated, L"%ls %ls", sczCommand, sczArgumentsObfuscated);
398 ExitOnFailure(hr, "Failed to copy obfuscated formatted arguments.");
399
400 hr = StrAllocConcatFormattedSecure(&sczCommand, L" %ls", sczArgumentsFormatted);
401 ExitOnFailure(hr, "Failed to copy formatted arguments.");
402 }
403
404 // Log before we add the secret pipe name and client token for embedded processes.
405 LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, LoggingActionStateToString(action), sczExecutablePath, sczCommandObfuscated);
406
407 if (fRunEmbedded)
408 {
409 hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode);
410 ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath);
411 }
412 else // create and wait for the executable process while sending fake progress to allow cancel.
413 {
414 // Make the cache location of the executable the current directory to help those executables
415 // that expect stuff to be relative to them.
416 si.cb = sizeof(si);
417 if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi))
418 {
419 ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath);
420 }
421
422 do
423 {
424 message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS;
425 message.dwUIHint = MB_OKCANCEL;
426 message.progress.dwPercentage = 50;
427 nResult = pfnGenericMessageHandler(&message, pvContext);
428 hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE);
429 ExitOnRootFailure(hr, "Bootstrapper application aborted during BUNDLE progress.");
430
431 hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode);
432 if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr)
433 {
434 ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath);
435 }
436 } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr);
437 }
438
439 hr = ExeEngineHandleExitCode(pPackage->Bundle.rgExitCodes, pPackage->Bundle.cExitCodes, dwExitCode, pRestart);
440 ExitOnRootFailure(hr, "Process returned error: 0x%x", dwExitCode);
441
442LExit:
443 StrSecureZeroFreeString(sczArguments);
444 StrSecureZeroFreeString(sczArgumentsFormatted);
445 ReleaseStr(sczArgumentsObfuscated);
446 ReleaseStr(sczCachedDirectory);
447 ReleaseStr(sczExecutablePath);
448 StrSecureZeroFreeString(sczCommand);
449 ReleaseStr(sczCommandObfuscated);
450
451 ReleaseHandle(pi.hThread);
452 ReleaseHandle(pi.hProcess);
453 ReleaseFileHandle(hExecutableFile);
454
455 // Best effort to clear the execute package cache folder and action variables.
456 VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE);
457 VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE, FALSE);
458
459 return hr;
460}