diff options
Diffstat (limited to 'src/engine/engine.cpp')
-rw-r--r-- | src/engine/engine.cpp | 889 |
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 | |||
8 | const DWORD RESTART_RETRIES = 10; | ||
9 | |||
10 | // internal function declarations | ||
11 | |||
12 | static HRESULT InitializeEngineState( | ||
13 | __in BURN_ENGINE_STATE* pEngineState, | ||
14 | __in HANDLE hEngineFile | ||
15 | ); | ||
16 | static void UninitializeEngineState( | ||
17 | __in BURN_ENGINE_STATE* pEngineState | ||
18 | ); | ||
19 | static HRESULT RunUntrusted( | ||
20 | __in LPCWSTR wzCommandLine, | ||
21 | __in BURN_ENGINE_STATE* pEngineState | ||
22 | ); | ||
23 | static HRESULT RunNormal( | ||
24 | __in HINSTANCE hInstance, | ||
25 | __in BURN_ENGINE_STATE* pEngineState | ||
26 | ); | ||
27 | static HRESULT RunElevated( | ||
28 | __in HINSTANCE hInstance, | ||
29 | __in LPCWSTR wzCommandLine, | ||
30 | __in BURN_ENGINE_STATE* pEngineState | ||
31 | ); | ||
32 | static HRESULT RunEmbedded( | ||
33 | __in HINSTANCE hInstance, | ||
34 | __in BURN_ENGINE_STATE* pEngineState | ||
35 | ); | ||
36 | static HRESULT RunRunOnce( | ||
37 | __in const BURN_REGISTRATION* pRegistration, | ||
38 | __in int nCmdShow | ||
39 | ); | ||
40 | static HRESULT RunApplication( | ||
41 | __in BURN_ENGINE_STATE* pEngineState, | ||
42 | __out BOOL* pfReloadApp | ||
43 | ); | ||
44 | static HRESULT ProcessMessage( | ||
45 | __in BURN_ENGINE_STATE* pEngineState, | ||
46 | __in const MSG* pmsg | ||
47 | ); | ||
48 | static HRESULT DAPI RedirectLoggingOverPipe( | ||
49 | __in_z LPCSTR szString, | ||
50 | __in_opt LPVOID pvContext | ||
51 | ); | ||
52 | static HRESULT Restart(); | ||
53 | |||
54 | |||
55 | // function definitions | ||
56 | |||
57 | extern "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 | |||
80 | extern "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 | |||
201 | LExit: | ||
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 | |||
283 | static 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 | |||
332 | LExit: | ||
333 | return hr; | ||
334 | } | ||
335 | |||
336 | static 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 | |||
386 | static 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 | |||
472 | LExit: | ||
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 | |||
485 | static 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 | |||
546 | LExit: | ||
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 | |||
569 | static 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 | |||
609 | LExit: | ||
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 | |||
629 | static 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 | |||
653 | LExit: | ||
654 | return hr; | ||
655 | } | ||
656 | |||
657 | static 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 | |||
677 | LExit: | ||
678 | ReleaseHandle(hProcess); | ||
679 | ReleaseStr(sczNewCommandLine); | ||
680 | ReleaseStr(sczBurnPath); | ||
681 | |||
682 | return hr; | ||
683 | } | ||
684 | |||
685 | static 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 | |||
728 | LExit: | ||
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 | |||
750 | static 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 | |||
787 | static 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 | |||
831 | LExit: | ||
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 | |||
843 | static 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 | |||
886 | LExit: | ||
887 | ReleaseHandle(hProcessToken); | ||
888 | return hr; | ||
889 | } | ||