aboutsummaryrefslogtreecommitdiff
path: root/src/burn/engine/engine.cpp
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-04-22 17:06:54 -0700
committerRob Mensching <rob@firegiant.com>2021-04-29 16:36:06 -0700
commitaf10c45d7b3a44af0b461a557847fe03263dcc10 (patch)
tree6a5c1532304782c36ffe4200b38f3afb76789a43 /src/burn/engine/engine.cpp
parent9c2aed97299fb96aeee3f1471ce40225437aaecf (diff)
downloadwix-af10c45d7b3a44af0b461a557847fe03263dcc10.tar.gz
wix-af10c45d7b3a44af0b461a557847fe03263dcc10.tar.bz2
wix-af10c45d7b3a44af0b461a557847fe03263dcc10.zip
Move burn into burn
Diffstat (limited to 'src/burn/engine/engine.cpp')
-rw-r--r--src/burn/engine/engine.cpp992
1 files changed, 992 insertions, 0 deletions
diff --git a/src/burn/engine/engine.cpp b/src/burn/engine/engine.cpp
new file mode 100644
index 00000000..8f024e98
--- /dev/null
+++ b/src/burn/engine/engine.cpp
@@ -0,0 +1,992 @@
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
8const DWORD RESTART_RETRIES = 10;
9
10// internal function declarations
11
12static HRESULT InitializeEngineState(
13 __in BURN_ENGINE_STATE* pEngineState,
14 __in HANDLE hEngineFile
15 );
16static void UninitializeEngineState(
17 __in BURN_ENGINE_STATE* pEngineState
18 );
19static HRESULT RunUntrusted(
20 __in LPCWSTR wzCommandLine,
21 __in BURN_ENGINE_STATE* pEngineState
22 );
23static HRESULT RunNormal(
24 __in HINSTANCE hInstance,
25 __in BURN_ENGINE_STATE* pEngineState
26 );
27static HRESULT RunElevated(
28 __in HINSTANCE hInstance,
29 __in LPCWSTR wzCommandLine,
30 __in BURN_ENGINE_STATE* pEngineState
31 );
32static HRESULT RunEmbedded(
33 __in HINSTANCE hInstance,
34 __in BURN_ENGINE_STATE* pEngineState
35 );
36static HRESULT RunRunOnce(
37 __in const BURN_REGISTRATION* pRegistration,
38 __in int nCmdShow
39 );
40static HRESULT RunApplication(
41 __in BURN_ENGINE_STATE* pEngineState,
42 __out BOOL* pfReloadApp,
43 __out BOOL* pfSkipCleanup
44 );
45static HRESULT ProcessMessage(
46 __in BURN_ENGINE_STATE* pEngineState,
47 __in const MSG* pmsg
48 );
49static HRESULT DAPI RedirectLoggingOverPipe(
50 __in_z LPCSTR szString,
51 __in_opt LPVOID pvContext
52 );
53static HRESULT Restart();
54
55
56// function definitions
57
58extern "C" BOOL EngineInCleanRoom(
59 __in_z_opt LPCWSTR wzCommandLine
60 )
61{
62 // Be very careful with the functions you call from here.
63 // This function will be called before ::SetDefaultDllDirectories()
64 // has been called so dependencies outside of kernel32.dll are
65 // very likely to introduce DLL hijacking opportunities.
66
67 static DWORD cchCleanRoomSwitch = lstrlenW(BURN_COMMANDLINE_SWITCH_CLEAN_ROOM);
68
69 // This check is wholly dependent on the clean room command line switch being
70 // present at the beginning of the command line. Since Burn is the only thing
71 // that should be setting this command line option, that is in our control.
72 BOOL fInCleanRoom = (wzCommandLine &&
73 (wzCommandLine[0] == L'-' || wzCommandLine[0] == L'/') &&
74 CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzCommandLine + 1, cchCleanRoomSwitch, BURN_COMMANDLINE_SWITCH_CLEAN_ROOM, cchCleanRoomSwitch) &&
75 wzCommandLine[1 + cchCleanRoomSwitch] == L'='
76 );
77
78 return fInCleanRoom;
79}
80
81extern "C" HRESULT EngineRun(
82 __in HINSTANCE hInstance,
83 __in HANDLE hEngineFile,
84 __in_z_opt LPCWSTR wzCommandLine,
85 __in int nCmdShow,
86 __out DWORD* pdwExitCode
87 )
88{
89 HRESULT hr = S_OK;
90 BOOL fComInitialized = FALSE;
91 BOOL fLogInitialized = FALSE;
92 BOOL fCrypInitialized = FALSE;
93 BOOL fDpiuInitialized = FALSE;
94 BOOL fRegInitialized = FALSE;
95 BOOL fWiuInitialized = FALSE;
96 BOOL fXmlInitialized = FALSE;
97 SYSTEM_INFO si = { };
98 RTL_OSVERSIONINFOEXW ovix = { };
99 LPWSTR sczExePath = NULL;
100 BOOL fRunNormal = FALSE;
101 BOOL fRestart = FALSE;
102
103 BURN_ENGINE_STATE engineState = { };
104 engineState.command.cbSize = sizeof(BOOTSTRAPPER_COMMAND);
105
106 // Always initialize logging first
107 LogInitialize(::GetModuleHandleW(NULL));
108 fLogInitialized = TRUE;
109
110 // Ensure that log contains approriate level of information
111#ifdef _DEBUG
112 LogSetLevel(REPORT_DEBUG, FALSE);
113#else
114 LogSetLevel(REPORT_VERBOSE, FALSE); // FALSE means don't write an additional text line to the log saying the level changed
115#endif
116
117 hr = AppParseCommandLine(wzCommandLine, &engineState.argc, &engineState.argv);
118 ExitOnFailure(hr, "Failed to parse command line.");
119
120 hr = InitializeEngineState(&engineState, hEngineFile);
121 ExitOnFailure(hr, "Failed to initialize engine state.");
122
123 engineState.command.nCmdShow = nCmdShow;
124
125 // initialize platform layer
126 PlatformInitialize();
127
128 // initialize COM
129 hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
130 ExitOnFailure(hr, "Failed to initialize COM.");
131 fComInitialized = TRUE;
132
133 // Initialize dutil.
134 hr = CrypInitialize();
135 ExitOnFailure(hr, "Failed to initialize Cryputil.");
136 fCrypInitialized = TRUE;
137
138 DpiuInitialize();
139 fDpiuInitialized = TRUE;
140
141 hr = RegInitialize();
142 ExitOnFailure(hr, "Failed to initialize Regutil.");
143 fRegInitialized = TRUE;
144
145 hr = WiuInitialize();
146 ExitOnFailure(hr, "Failed to initialize Wiutil.");
147 fWiuInitialized = TRUE;
148
149 hr = XmlInitialize();
150 ExitOnFailure(hr, "Failed to initialize XML util.");
151 fXmlInitialized = TRUE;
152
153 hr = OsRtlGetVersion(&ovix);
154 ExitOnFailure(hr, "Failed to get OS info.");
155
156#if defined(_M_ARM64)
157 LPCSTR szBurnPlatform = "ARM64";
158#elif defined(_M_AMD64)
159 LPCSTR szBurnPlatform = "x64";
160#else
161 LPCSTR szBurnPlatform = "x86";
162#endif
163
164 LPCSTR szMachinePlatform = "unknown architecture";
165 ::GetNativeSystemInfo(&si);
166 switch (si.wProcessorArchitecture)
167 {
168 case PROCESSOR_ARCHITECTURE_AMD64:
169 szMachinePlatform = "x64";
170 break;
171 case PROCESSOR_ARCHITECTURE_ARM:
172 szMachinePlatform = "ARM";
173 break;
174 case PROCESSOR_ARCHITECTURE_ARM64:
175 szMachinePlatform = "ARM64";
176 break;
177 case PROCESSOR_ARCHITECTURE_INTEL:
178 szMachinePlatform = "x86";
179 break;
180 }
181
182 PathForCurrentProcess(&sczExePath, NULL); // Ignore failure.
183 LogId(REPORT_STANDARD, MSG_BURN_INFO, szVerMajorMinorBuild, ovix.dwMajorVersion, ovix.dwMinorVersion, ovix.dwBuildNumber, ovix.wServicePackMajor, sczExePath, szBurnPlatform, szMachinePlatform);
184 ReleaseNullStr(sczExePath);
185
186 // initialize core
187 hr = CoreInitialize(&engineState);
188 ExitOnFailure(hr, "Failed to initialize core.");
189
190 // Select run mode.
191 switch (engineState.mode)
192 {
193 case BURN_MODE_UNTRUSTED:
194 hr = RunUntrusted(wzCommandLine, &engineState);
195 ExitOnFailure(hr, "Failed to run untrusted mode.");
196 break;
197
198 case BURN_MODE_NORMAL:
199 fRunNormal = TRUE;
200
201 hr = RunNormal(hInstance, &engineState);
202 ExitOnFailure(hr, "Failed to run per-user mode.");
203 break;
204
205 case BURN_MODE_ELEVATED:
206 hr = RunElevated(hInstance, wzCommandLine, &engineState);
207 ExitOnFailure(hr, "Failed to run per-machine mode.");
208 break;
209
210 case BURN_MODE_EMBEDDED:
211 fRunNormal = TRUE;
212
213 hr = RunEmbedded(hInstance, &engineState);
214 ExitOnFailure(hr, "Failed to run embedded mode.");
215 break;
216
217 case BURN_MODE_RUNONCE:
218 hr = RunRunOnce(&engineState.registration, nCmdShow);
219 ExitOnFailure(hr, "Failed to run RunOnce mode.");
220 break;
221
222 default:
223 hr = E_UNEXPECTED;
224 ExitOnFailure(hr, "Invalid run mode.");
225 }
226
227 // set exit code and remember if we are supposed to restart.
228 *pdwExitCode = engineState.userExperience.dwExitCode;
229 fRestart = engineState.fRestart;
230
231LExit:
232 ReleaseStr(sczExePath);
233
234 // If anything went wrong but the log was never open, try to open a "failure" log
235 // and that will dump anything captured in the log memory buffer to the log.
236 if (FAILED(hr) && BURN_LOGGING_STATE_CLOSED == engineState.log.state)
237 {
238 LoggingOpenFailed();
239 }
240
241 UserExperienceRemove(&engineState.userExperience);
242
243 CacheRemoveWorkingFolder(engineState.registration.sczId);
244 CacheUninitialize();
245
246 // If this is a related bundle (but not an update) suppress restart and return the standard restart error code.
247 if (fRestart && BOOTSTRAPPER_RELATION_NONE != engineState.command.relationType && BOOTSTRAPPER_RELATION_UPDATE != engineState.command.relationType)
248 {
249 LogId(REPORT_STANDARD, MSG_RESTART_ABORTED, LoggingRelationTypeToString(engineState.command.relationType));
250
251 fRestart = FALSE;
252 hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED);
253 }
254
255 UninitializeEngineState(&engineState);
256
257 if (fXmlInitialized)
258 {
259 XmlUninitialize();
260 }
261
262 if (fWiuInitialized)
263 {
264 WiuUninitialize();
265 }
266
267 if (fRegInitialized)
268 {
269 RegUninitialize();
270 }
271
272 if (fDpiuInitialized)
273 {
274 DpiuUninitialize();
275 }
276
277 if (fCrypInitialized)
278 {
279 CrypUninitialize();
280 }
281
282 if (fComInitialized)
283 {
284 ::CoUninitialize();
285 }
286
287 if (fRunNormal)
288 {
289 LogId(REPORT_STANDARD, MSG_EXITING, FAILED(hr) ? (int)hr : *pdwExitCode, LoggingBoolToString(fRestart));
290
291 if (fRestart)
292 {
293 LogId(REPORT_STANDARD, MSG_RESTARTING);
294 }
295 }
296
297 if (fLogInitialized)
298 {
299 LogClose(FALSE);
300 }
301
302 if (fRestart)
303 {
304 Restart();
305 }
306
307 if (fLogInitialized)
308 {
309 LogUninitialize(FALSE);
310 }
311
312 return hr;
313}
314
315
316// internal function definitions
317
318static HRESULT InitializeEngineState(
319 __in BURN_ENGINE_STATE* pEngineState,
320 __in HANDLE hEngineFile
321 )
322{
323 HRESULT hr = S_OK;
324 LPCWSTR wzParam = NULL;
325 HANDLE hSectionFile = hEngineFile;
326 HANDLE hSourceEngineFile = INVALID_HANDLE_VALUE;
327 DWORD64 qw = 0;
328
329 pEngineState->automaticUpdates = BURN_AU_PAUSE_ACTION_IFELEVATED;
330 pEngineState->dwElevatedLoggingTlsId = TLS_OUT_OF_INDEXES;
331 ::InitializeCriticalSection(&pEngineState->userExperience.csEngineActive);
332 PipeConnectionInitialize(&pEngineState->companionConnection);
333 PipeConnectionInitialize(&pEngineState->embeddedConnection);
334
335 for (int i = 0; i < pEngineState->argc; ++i)
336 {
337 if (pEngineState->argv[i][0] == L'-')
338 {
339 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &pEngineState->argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED), BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED, lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED)))
340 {
341 wzParam = &pEngineState->argv[i][2 + lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED)];
342 if (L'=' != wzParam[-1] || L'\0' == wzParam[0])
343 {
344 ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED);
345 }
346
347 hr = StrStringToUInt64(wzParam, 0, &qw);
348 ExitOnFailure(hr, "Failed to parse file handle: '%ls'", (wzParam));
349
350 hSourceEngineFile = (HANDLE)qw;
351 }
352 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &pEngineState->argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF), BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF, lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF)))
353 {
354 wzParam = &pEngineState->argv[i][2 + lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF)];
355 if (L'=' != wzParam[-1] || L'\0' == wzParam[0])
356 {
357 ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF);
358 }
359
360 hr = StrStringToUInt64(wzParam, 0, &qw);
361 ExitOnFailure(hr, "Failed to parse file handle: '%ls'", (wzParam));
362
363 hSectionFile = (HANDLE)qw;
364 }
365 }
366 }
367
368 hr = SectionInitialize(&pEngineState->section, hSectionFile, hSourceEngineFile);
369 ExitOnFailure(hr, "Failed to initialize engine section.");
370
371LExit:
372 return hr;
373}
374
375static void UninitializeEngineState(
376 __in BURN_ENGINE_STATE* pEngineState
377 )
378{
379 if (pEngineState->argv)
380 {
381 AppFreeCommandLineArgs(pEngineState->argv);
382 }
383
384 ReleaseStr(pEngineState->sczIgnoreDependencies);
385
386 PipeConnectionUninitialize(&pEngineState->embeddedConnection);
387 PipeConnectionUninitialize(&pEngineState->companionConnection);
388 ReleaseStr(pEngineState->sczBundleEngineWorkingPath)
389
390 ReleaseHandle(pEngineState->hMessageWindowThread);
391
392 BurnExtensionUninitialize(&pEngineState->extensions);
393
394 ::DeleteCriticalSection(&pEngineState->userExperience.csEngineActive);
395 UserExperienceUninitialize(&pEngineState->userExperience);
396
397 ApprovedExesUninitialize(&pEngineState->approvedExes);
398 UpdateUninitialize(&pEngineState->update);
399 VariablesUninitialize(&pEngineState->variables);
400 SearchesUninitialize(&pEngineState->searches);
401 RegistrationUninitialize(&pEngineState->registration);
402 PayloadsUninitialize(&pEngineState->payloads);
403 PackagesUninitialize(&pEngineState->packages);
404 SectionUninitialize(&pEngineState->section);
405 ContainersUninitialize(&pEngineState->containers);
406
407 ReleaseStr(pEngineState->command.wzBootstrapperApplicationDataPath);
408 ReleaseStr(pEngineState->command.wzBootstrapperWorkingFolder);
409 ReleaseStr(pEngineState->command.wzLayoutDirectory);
410 ReleaseStr(pEngineState->command.wzCommandLine);
411
412 ReleaseStr(pEngineState->log.sczExtension);
413 ReleaseStr(pEngineState->log.sczPrefix);
414 ReleaseStr(pEngineState->log.sczPath);
415 ReleaseStr(pEngineState->log.sczPathVariable);
416
417 if (TLS_OUT_OF_INDEXES != pEngineState->dwElevatedLoggingTlsId)
418 {
419 ::TlsFree(pEngineState->dwElevatedLoggingTlsId);
420 }
421
422 // clear struct
423 memset(pEngineState, 0, sizeof(BURN_ENGINE_STATE));
424}
425
426static HRESULT RunUntrusted(
427 __in LPCWSTR wzCommandLine,
428 __in BURN_ENGINE_STATE* pEngineState
429 )
430{
431 HRESULT hr = S_OK;
432 LPWSTR sczCurrentProcessPath = NULL;
433 LPWSTR wzCleanRoomBundlePath = NULL;
434 LPWSTR sczCachedCleanRoomBundlePath = NULL;
435 LPWSTR sczParameters = NULL;
436 LPWSTR sczFullCommandLine = NULL;
437 STARTUPINFOW si = { };
438 PROCESS_INFORMATION pi = { };
439 HANDLE hFileAttached = NULL;
440 HANDLE hFileSelf = NULL;
441 HANDLE hProcess = NULL;
442
443 hr = PathForCurrentProcess(&sczCurrentProcessPath, NULL);
444 ExitOnFailure(hr, "Failed to get path for current process.");
445
446 BOOL fRunningFromCache = CacheBundleRunningFromCache();
447
448 // If we're running from the package cache, we're in a secure
449 // folder (DLLs cannot be inserted here for hijacking purposes)
450 // so just launch the current process's path as the clean room
451 // process. Technically speaking, we'd be able to skip creating
452 // a clean room process at all (since we're already running from
453 // a secure folder) but it makes the code that only wants to run
454 // in clean room more complicated if we don't launch an explicit
455 // clean room process.
456 if (fRunningFromCache)
457 {
458 wzCleanRoomBundlePath = sczCurrentProcessPath;
459 }
460 else
461 {
462 hr = CacheBundleToCleanRoom(&pEngineState->section, &sczCachedCleanRoomBundlePath);
463 ExitOnFailure(hr, "Failed to cache to clean room.");
464
465 wzCleanRoomBundlePath = sczCachedCleanRoomBundlePath;
466 }
467
468 // The clean room switch must always be at the front of the command line so
469 // the EngineInCleanRoom function will operate correctly.
470 hr = StrAllocFormatted(&sczParameters, L"-%ls=\"%ls\"", BURN_COMMANDLINE_SWITCH_CLEAN_ROOM, sczCurrentProcessPath);
471 ExitOnFailure(hr, "Failed to allocate parameters for unelevated process.");
472
473 // Send a file handle for the child Burn process to access the attached container.
474 hr = CoreAppendFileHandleAttachedToCommandLine(pEngineState->section.hEngineFile, &hFileAttached, &sczParameters);
475 ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED);
476
477 // Grab a file handle for the child Burn process.
478 hr = CoreAppendFileHandleSelfToCommandLine(wzCleanRoomBundlePath, &hFileSelf, &sczParameters, NULL);
479 ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF);
480
481 hr = StrAllocFormattedSecure(&sczParameters, L"%ls %ls", sczParameters, wzCommandLine);
482 ExitOnFailure(hr, "Failed to append original command line.");
483
484#ifdef ENABLE_UNELEVATE
485 // TODO: Pass file handle to unelevated process if this ever gets reenabled.
486 if (!pEngineState->fDisableUnelevate)
487 {
488 // Try to launch unelevated and if that fails for any reason, we'll launch our process normally (even though that may make it elevated).
489 hr = ProcExecuteAsInteractiveUser(wzCleanRoomBundlePath, sczParameters, &hProcess);
490 }
491#endif
492
493 if (!hProcess)
494 {
495 hr = StrAllocFormattedSecure(&sczFullCommandLine, L"\"%ls\" %ls", wzCleanRoomBundlePath, sczParameters);
496 ExitOnFailure(hr, "Failed to allocate full command-line.");
497
498 si.cb = sizeof(si);
499 si.wShowWindow = static_cast<WORD>(pEngineState->command.nCmdShow);
500 if (!::CreateProcessW(wzCleanRoomBundlePath, sczFullCommandLine, NULL, NULL, TRUE, 0, 0, NULL, &si, &pi))
501 {
502 ExitWithLastError(hr, "Failed to launch clean room process: %ls", sczFullCommandLine);
503 }
504
505 hProcess = pi.hProcess;
506 pi.hProcess = NULL;
507 }
508
509 hr = ProcWaitForCompletion(hProcess, INFINITE, &pEngineState->userExperience.dwExitCode);
510 ExitOnFailure(hr, "Failed to wait for clean room process: %ls", wzCleanRoomBundlePath);
511
512LExit:
513 ReleaseHandle(pi.hThread);
514 ReleaseFileHandle(hFileSelf);
515 ReleaseFileHandle(hFileAttached);
516 ReleaseHandle(hProcess);
517 StrSecureZeroFreeString(sczFullCommandLine);
518 StrSecureZeroFreeString(sczParameters);
519 ReleaseStr(sczCachedCleanRoomBundlePath);
520 ReleaseStr(sczCurrentProcessPath);
521
522 return hr;
523}
524
525static HRESULT RunNormal(
526 __in HINSTANCE hInstance,
527 __in BURN_ENGINE_STATE* pEngineState
528 )
529{
530 HRESULT hr = S_OK;
531 LPWSTR sczOriginalSource = NULL;
532 LPWSTR sczCopiedOriginalSource = NULL;
533 BOOL fContinueExecution = TRUE;
534 BOOL fReloadApp = FALSE;
535 BOOL fSkipCleanup = FALSE;
536 BURN_EXTENSION_ENGINE_CONTEXT extensionEngineContext = { };
537
538 // Initialize logging.
539 hr = LoggingOpen(&pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->registration.sczDisplayName);
540 ExitOnFailure(hr, "Failed to open log.");
541
542 // Ensure we're on a supported operating system.
543 hr = ConditionGlobalCheck(&pEngineState->variables, &pEngineState->condition, pEngineState->command.display, pEngineState->registration.sczDisplayName, &pEngineState->userExperience.dwExitCode, &fContinueExecution);
544 ExitOnFailure(hr, "Failed to check global conditions");
545
546 if (!fContinueExecution)
547 {
548 LogId(REPORT_STANDARD, MSG_FAILED_CONDITION_CHECK);
549
550 // If the block told us to abort, abort!
551 ExitFunction1(hr = S_OK);
552 }
553
554 if (pEngineState->userExperience.fSplashScreen && BOOTSTRAPPER_DISPLAY_NONE < pEngineState->command.display)
555 {
556 SplashScreenCreate(hInstance, NULL, &pEngineState->command.hwndSplashScreen);
557 }
558
559 // Create a top-level window to handle system messages.
560 hr = UiCreateMessageWindow(hInstance, pEngineState);
561 ExitOnFailure(hr, "Failed to create the message window.");
562
563 // Query registration state.
564 hr = CoreQueryRegistration(pEngineState);
565 ExitOnFailure(hr, "Failed to query registration.");
566
567 // Best effort to set the source of attached containers to BURN_BUNDLE_ORIGINAL_SOURCE.
568 hr = VariableGetString(&pEngineState->variables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource);
569 if (SUCCEEDED(hr))
570 {
571 for (DWORD i = 0; i < pEngineState->containers.cContainers; ++i)
572 {
573 BURN_CONTAINER* pContainer = pEngineState->containers.rgContainers + i;
574 if (pContainer->fAttached)
575 {
576 hr = StrAllocString(&sczCopiedOriginalSource, sczOriginalSource, 0);
577 if (SUCCEEDED(hr))
578 {
579 ReleaseNullStr(pContainer->sczSourcePath);
580 pContainer->sczSourcePath = sczCopiedOriginalSource;
581 sczCopiedOriginalSource = NULL;
582 }
583 }
584 }
585 }
586 hr = S_OK;
587
588 // Set some built-in variables before loading the BA.
589 hr = PlanSetVariables(pEngineState->command.action, &pEngineState->variables);
590 ExitOnFailure(hr, "Failed to set action variables.");
591
592 hr = RegistrationSetVariables(&pEngineState->registration, &pEngineState->variables);
593 ExitOnFailure(hr, "Failed to set registration variables.");
594
595 // If a layout directory was specified on the command-line, set it as a well-known variable.
596 if (pEngineState->command.wzLayoutDirectory && *pEngineState->command.wzLayoutDirectory)
597 {
598 hr = VariableSetString(&pEngineState->variables, BURN_BUNDLE_LAYOUT_DIRECTORY, pEngineState->command.wzLayoutDirectory, FALSE, FALSE);
599 ExitOnFailure(hr, "Failed to set layout directory variable to value provided from command-line.");
600 }
601
602 // Setup the extension engine.
603 extensionEngineContext.pEngineState = pEngineState;
604
605 // Load the extensions.
606 hr = BurnExtensionLoad(&pEngineState->extensions, &extensionEngineContext);
607 ExitOnFailure(hr, "Failed to load BundleExtensions.");
608
609 do
610 {
611 fReloadApp = FALSE;
612 pEngineState->fQuit = FALSE;
613
614 hr = RunApplication(pEngineState, &fReloadApp, &fSkipCleanup);
615 ExitOnFailure(hr, "Failed while running ");
616 } while (fReloadApp);
617
618LExit:
619 if (!fSkipCleanup)
620 {
621 CoreCleanup(pEngineState);
622 }
623
624 BurnExtensionUnload(&pEngineState->extensions);
625
626 // If the message window is still around, close it.
627 UiCloseMessageWindow(pEngineState);
628
629 VariablesDump(&pEngineState->variables);
630
631 // end per-machine process if running
632 if (INVALID_HANDLE_VALUE != pEngineState->companionConnection.hPipe)
633 {
634 PipeTerminateChildProcess(&pEngineState->companionConnection, pEngineState->userExperience.dwExitCode, FALSE);
635 }
636
637 // If the splash screen is still around, close it.
638 if (::IsWindow(pEngineState->command.hwndSplashScreen))
639 {
640 ::PostMessageW(pEngineState->command.hwndSplashScreen, WM_CLOSE, 0, 0);
641 }
642
643 ReleaseStr(sczOriginalSource);
644 ReleaseStr(sczCopiedOriginalSource);
645
646 return hr;
647}
648
649static HRESULT RunElevated(
650 __in HINSTANCE hInstance,
651 __in LPCWSTR /*wzCommandLine*/,
652 __in BURN_ENGINE_STATE* pEngineState
653 )
654{
655 HRESULT hr = S_OK;
656 HANDLE hLock = NULL;
657 BOOL fDisabledAutomaticUpdates = FALSE;
658
659 // connect to per-user process
660 hr = PipeChildConnect(&pEngineState->companionConnection, TRUE);
661 ExitOnFailure(hr, "Failed to connect to unelevated process.");
662
663 // Set up the thread local storage to store the correct pipe to communicate logging then
664 // override logging to write over the pipe.
665 pEngineState->dwElevatedLoggingTlsId = ::TlsAlloc();
666 if (TLS_OUT_OF_INDEXES == pEngineState->dwElevatedLoggingTlsId)
667 {
668 ExitWithLastError(hr, "Failed to allocate thread local storage for logging.");
669 }
670
671 if (!::TlsSetValue(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe))
672 {
673 ExitWithLastError(hr, "Failed to set elevated pipe into thread local storage for logging.");
674 }
675
676 LogRedirect(RedirectLoggingOverPipe, pEngineState);
677
678 // Create a top-level window to prevent shutting down the elevated process.
679 hr = UiCreateMessageWindow(hInstance, pEngineState);
680 ExitOnFailure(hr, "Failed to create the message window.");
681
682 SrpInitialize(TRUE);
683
684 // Pump messages from parent process.
685 hr = ElevationChildPumpMessages(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe, pEngineState->companionConnection.hCachePipe, &pEngineState->approvedExes, &pEngineState->containers, &pEngineState->packages, &pEngineState->payloads, &pEngineState->variables, &pEngineState->registration, &pEngineState->userExperience, &hLock, &fDisabledAutomaticUpdates, &pEngineState->userExperience.dwExitCode, &pEngineState->fRestart);
686 LogRedirect(NULL, NULL); // reset logging so the next failure gets written to "log buffer" for the failure log.
687 ExitOnFailure(hr, "Failed to pump messages from parent process.");
688
689LExit:
690 LogRedirect(NULL, NULL); // we're done talking to the child so always reset logging now.
691
692 // If the message window is still around, close it.
693 UiCloseMessageWindow(pEngineState);
694
695 if (fDisabledAutomaticUpdates)
696 {
697 ElevationChildResumeAutomaticUpdates();
698 }
699
700 if (hLock)
701 {
702 ::ReleaseMutex(hLock);
703 ::CloseHandle(hLock);
704 }
705
706 return hr;
707}
708
709static HRESULT RunEmbedded(
710 __in HINSTANCE hInstance,
711 __in BURN_ENGINE_STATE* pEngineState
712 )
713{
714 HRESULT hr = S_OK;
715
716 // Disable system restore since the parent bundle may have done it.
717 pEngineState->fDisableSystemRestore = TRUE;
718
719 // Connect to parent process.
720 hr = PipeChildConnect(&pEngineState->embeddedConnection, FALSE);
721 ExitOnFailure(hr, "Failed to connect to parent of embedded process.");
722
723 // Do not register the bundle to automatically restart if embedded.
724 if (BOOTSTRAPPER_DISPLAY_EMBEDDED == pEngineState->command.display)
725 {
726 pEngineState->registration.fDisableResume = TRUE;
727 }
728
729 // Now run the application like normal.
730 hr = RunNormal(hInstance, pEngineState);
731 ExitOnFailure(hr, "Failed to run bootstrapper application embedded.");
732
733LExit:
734 return hr;
735}
736
737static HRESULT RunRunOnce(
738 __in const BURN_REGISTRATION* pRegistration,
739 __in int nCmdShow
740 )
741{
742 HRESULT hr = S_OK;
743 LPWSTR sczNewCommandLine = NULL;
744 LPWSTR sczBurnPath = NULL;
745 HANDLE hProcess = NULL;
746
747 hr = RegistrationGetResumeCommandLine(pRegistration, &sczNewCommandLine);
748 ExitOnFailure(hr, "Unable to get resume command line from the registry");
749
750 // and re-launch
751 hr = PathForCurrentProcess(&sczBurnPath, NULL);
752 ExitOnFailure(hr, "Failed to get current process path.");
753
754 hr = ProcExec(sczBurnPath, 0 < sczNewCommandLine ? sczNewCommandLine : L"", nCmdShow, &hProcess);
755 ExitOnFailure(hr, "Failed to re-launch bundle process after RunOnce: %ls", sczBurnPath);
756
757LExit:
758 ReleaseHandle(hProcess);
759 ReleaseStr(sczNewCommandLine);
760 ReleaseStr(sczBurnPath);
761
762 return hr;
763}
764
765static HRESULT RunApplication(
766 __in BURN_ENGINE_STATE* pEngineState,
767 __out BOOL* pfReloadApp,
768 __out BOOL* pfSkipCleanup
769 )
770{
771 HRESULT hr = S_OK;
772 BOOTSTRAPPER_ENGINE_CONTEXT engineContext = { };
773 BOOL fStartupCalled = FALSE;
774 BOOL fRet = FALSE;
775 MSG msg = { };
776 BOOTSTRAPPER_SHUTDOWN_ACTION shutdownAction = BOOTSTRAPPER_SHUTDOWN_ACTION_NONE;
777
778 ::PeekMessageW(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
779
780 // Setup the bootstrapper engine.
781 engineContext.dwThreadId = ::GetCurrentThreadId();
782 engineContext.pEngineState = pEngineState;
783
784 // Load the bootstrapper application.
785 hr = UserExperienceLoad(&pEngineState->userExperience, &engineContext, &pEngineState->command);
786 ExitOnFailure(hr, "Failed to load BA.");
787
788 fStartupCalled = TRUE;
789 hr = UserExperienceOnStartup(&pEngineState->userExperience);
790 ExitOnFailure(hr, "Failed to start bootstrapper application.");
791
792 // Enter the message pump.
793 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
794 {
795 if (-1 == fRet)
796 {
797 hr = E_UNEXPECTED;
798 ExitOnRootFailure(hr, "Unexpected return value from message pump.");
799 }
800 else
801 {
802 // When the BA makes a request from its own thread, it's common for the PostThreadMessage in externalengine.cpp
803 // to block until this thread waits on something. It's also common for Detect and Plan to never wait on something.
804 // In the extreme case, the engine could be elevating in Apply before the Detect call returned to the BA.
805 // This helps to avoid that situation, which could be blocking a UI thread.
806 ::Sleep(0);
807
808 ProcessMessage(pEngineState, &msg);
809 }
810 }
811
812 // Get exit code.
813 pEngineState->userExperience.dwExitCode = (DWORD)msg.wParam;
814
815LExit:
816 if (fStartupCalled)
817 {
818 UserExperienceOnShutdown(&pEngineState->userExperience, &shutdownAction);
819 if (BOOTSTRAPPER_SHUTDOWN_ACTION_RESTART == shutdownAction)
820 {
821 LogId(REPORT_STANDARD, MSG_BA_REQUESTED_RESTART, LoggingBoolToString(pEngineState->fRestart));
822 pEngineState->fRestart = TRUE;
823 }
824 else if (BOOTSTRAPPER_SHUTDOWN_ACTION_RELOAD_BOOTSTRAPPER == shutdownAction)
825 {
826 LogId(REPORT_STANDARD, MSG_BA_REQUESTED_RELOAD);
827 *pfReloadApp = TRUE;
828 }
829 else if (BOOTSTRAPPER_SHUTDOWN_ACTION_SKIP_CLEANUP == shutdownAction)
830 {
831 LogId(REPORT_STANDARD, MSG_BA_REQUESTED_SKIP_CLEANUP);
832 *pfSkipCleanup = TRUE;
833 }
834 }
835
836 // Unload BA.
837 UserExperienceUnload(&pEngineState->userExperience);
838
839 return hr;
840}
841
842static HRESULT ProcessMessage(
843 __in BURN_ENGINE_STATE* pEngineState,
844 __in const MSG* pmsg
845 )
846{
847 HRESULT hr = S_OK;
848
849 UserExperienceActivateEngine(&pEngineState->userExperience);
850
851 if (pEngineState->fQuit)
852 {
853 LogId(REPORT_WARNING, MSG_IGNORE_OPERATION_AFTER_QUIT, LoggingBurnMessageToString(pmsg->message));
854 ExitFunction1(hr = E_INVALIDSTATE);
855 }
856
857 switch (pmsg->message)
858 {
859 case WM_BURN_DETECT:
860 hr = CoreDetect(pEngineState, reinterpret_cast<HWND>(pmsg->lParam));
861 break;
862
863 case WM_BURN_PLAN:
864 hr = CorePlan(pEngineState, static_cast<BOOTSTRAPPER_ACTION>(pmsg->lParam));
865 break;
866
867 case WM_BURN_ELEVATE:
868 hr = CoreElevate(pEngineState, reinterpret_cast<HWND>(pmsg->lParam));
869 break;
870
871 case WM_BURN_APPLY:
872 hr = CoreApply(pEngineState, reinterpret_cast<HWND>(pmsg->lParam));
873 break;
874
875 case WM_BURN_LAUNCH_APPROVED_EXE:
876 hr = CoreLaunchApprovedExe(pEngineState, reinterpret_cast<BURN_LAUNCH_APPROVED_EXE*>(pmsg->lParam));
877 break;
878
879 case WM_BURN_QUIT:
880 hr = CoreQuit(pEngineState, static_cast<int>(pmsg->wParam));
881 break;
882 }
883
884LExit:
885 UserExperienceDeactivateEngine(&pEngineState->userExperience);
886
887 return hr;
888}
889
890static HRESULT DAPI RedirectLoggingOverPipe(
891 __in_z LPCSTR szString,
892 __in_opt LPVOID pvContext
893 )
894{
895 static BOOL s_fCurrentlyLoggingToPipe = FALSE;
896
897 HRESULT hr = S_OK;
898 BURN_ENGINE_STATE* pEngineState = static_cast<BURN_ENGINE_STATE*>(pvContext);
899 BOOL fStartedLogging = FALSE;
900 HANDLE hPipe = INVALID_HANDLE_VALUE;
901 BYTE* pbData = NULL;
902 SIZE_T cbData = 0;
903 DWORD dwResult = 0;
904
905 // Prevent this function from being called recursively.
906 if (s_fCurrentlyLoggingToPipe)
907 {
908 ExitFunction();
909 }
910
911 s_fCurrentlyLoggingToPipe = TRUE;
912 fStartedLogging = TRUE;
913
914 // Make sure the current thread set the pipe in TLS.
915 hPipe = ::TlsGetValue(pEngineState->dwElevatedLoggingTlsId);
916 if (!hPipe || INVALID_HANDLE_VALUE == hPipe)
917 {
918 hr = HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED);
919 ExitFunction();
920 }
921
922 // Do not log or use ExitOnFailure() macro here because they will be discarded
923 // by the recursive block at the top of this function.
924 hr = BuffWriteStringAnsi(&pbData, &cbData, szString);
925 if (SUCCEEDED(hr))
926 {
927 hr = PipeSendMessage(hPipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_LOG), pbData, cbData, NULL, NULL, &dwResult);
928 if (SUCCEEDED(hr))
929 {
930 hr = (HRESULT)dwResult;
931 }
932 }
933
934LExit:
935 ReleaseBuffer(pbData);
936
937 // We started logging so remember to say we are no longer logging.
938 if (fStartedLogging)
939 {
940 s_fCurrentlyLoggingToPipe = FALSE;
941 }
942
943 return hr;
944}
945
946static HRESULT Restart()
947{
948 HRESULT hr = S_OK;
949 HANDLE hProcessToken = NULL;
950 TOKEN_PRIVILEGES priv = { };
951 DWORD dwRetries = 0;
952
953 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken))
954 {
955 ExitWithLastError(hr, "Failed to get process token.");
956 }
957
958 priv.PrivilegeCount = 1;
959 priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
960 if (!::LookupPrivilegeValueW(NULL, L"SeShutdownPrivilege", &priv.Privileges[0].Luid))
961 {
962 ExitWithLastError(hr, "Failed to get shutdown privilege LUID.");
963 }
964
965 if (!::AdjustTokenPrivileges(hProcessToken, FALSE, &priv, sizeof(TOKEN_PRIVILEGES), NULL, 0))
966 {
967 ExitWithLastError(hr, "Failed to adjust token to add shutdown privileges.");
968 }
969
970 do
971 {
972 hr = S_OK;
973
974 // Wait a second to let the companion process (assuming we did an elevated install) to get to the
975 // point where it too is thinking about restarting the computer. Only one will schedule the restart
976 // but both will have their log files closed and otherwise be ready to exit.
977 //
978 // On retry, we'll also wait a second to let the OS try to get to a place where the restart can
979 // be initiated.
980 ::Sleep(1000);
981
982 if (!vpfnInitiateSystemShutdownExW(NULL, NULL, 0, FALSE, TRUE, SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_INSTALLATION | SHTDN_REASON_FLAG_PLANNED))
983 {
984 hr = HRESULT_FROM_WIN32(::GetLastError());
985 }
986 } while (dwRetries++ < RESTART_RETRIES && (HRESULT_FROM_WIN32(ERROR_MACHINE_LOCKED) == hr || HRESULT_FROM_WIN32(ERROR_NOT_READY) == hr));
987 ExitOnRootFailure(hr, "Failed to schedule restart.");
988
989LExit:
990 ReleaseHandle(hProcessToken);
991 return hr;
992}