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