main
cpp 380 lines 17.1 KB
Raw
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 namespace Microsoft
6 {
7 namespace Tools
8 {
9 namespace WindowsInstallerXml
10 {
11 namespace Test
12 {
13 namespace Bootstrapper
14 {
15 using namespace System;
16 using namespace System::IO;
17 using namespace System::Threading;
18 using namespace Xunit;
19
20 struct EXIT_CODE_ITEM
21 {
22 DWORD dwExitCode;
23 HRESULT hrExpectedResult;
24 BOOTSTRAPPER_APPLY_RESTART expectedRestart;
25 LPCWSTR wzPackageId;
26 HRESULT hrResultPerUser;
27 BOOTSTRAPPER_APPLY_RESTART restartPerUser;
28 HRESULT hrResultPerMachine;
29 BOOTSTRAPPER_APPLY_RESTART restartPerMachine;
30 };
31
32 static BOOL STDAPICALLTYPE ExitCodeTest_ShellExecuteExW(
33 __inout LPSHELLEXECUTEINFOW lpExecInfo
34 );
35 static DWORD CALLBACK ExitCodeTest_ElevationThreadProc(
36 __in LPVOID lpThreadParameter
37 );
38 static BOOL STDAPICALLTYPE ExitCodeTest_CreateProcessW(
39 __in_opt LPCWSTR lpApplicationName,
40 __inout_opt LPWSTR lpCommandLine,
41 __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
42 __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
43 __in BOOL bInheritHandles,
44 __in DWORD dwCreationFlags,
45 __in_opt LPVOID lpEnvironment,
46 __in_opt LPCWSTR lpCurrentDirectory,
47 __in LPSTARTUPINFOW lpStartupInfo,
48 __out LPPROCESS_INFORMATION lpProcessInformation
49 );
50 static DWORD CALLBACK ExitCodeTest_PackageThreadProc(
51 __in LPVOID lpThreadParameter
52 );
53 static int ExitCodeTest_GenericMessageHandler(
54 __in GENERIC_EXECUTE_MESSAGE* pMessage,
55 __in LPVOID pvContext
56 );
57 static void LoadEngineState(
58 __in BURN_ENGINE_STATE* pEngineState
59 );
60
61 public ref class ExitCodeTest : BurnUnitTest
62 {
63 public:
64 ExitCodeTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture)
65 {
66 }
67
68 [Fact]
69 void ExitCodeHandlingTest()
70 {
71 HRESULT hr = S_OK;
72 BURN_ENGINE_STATE engineState = { };
73 BURN_PIPE_CONNECTION* pConnection = &engineState.companionConnection;
74 EXIT_CODE_ITEM rgExitCodeItems[] =
75 {
76 { 0, S_OK, BOOTSTRAPPER_APPLY_RESTART_NONE, L"Standard" },
77 { 1, HRESULT_FROM_WIN32(1), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Standard" },
78 { ERROR_SUCCESS_REBOOT_REQUIRED, S_OK, BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Standard" },
79 { (DWORD)HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), S_OK, BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Standard" },
80 { ERROR_SUCCESS_RESTART_REQUIRED, S_OK, BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Standard" },
81 { (DWORD)HRESULT_FROM_WIN32(ERROR_SUCCESS_RESTART_REQUIRED), S_OK, BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Standard" },
82 { ERROR_SUCCESS_REBOOT_INITIATED, S_OK, BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Standard" },
83 { (DWORD)HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED), S_OK, BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Standard" },
84 { ERROR_FAIL_REBOOT_REQUIRED, HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Standard" },
85 { (DWORD)HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_REQUIRED), HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Standard" },
86 { ERROR_FAIL_REBOOT_INITIATED, HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_INITIATED), BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Standard" },
87 { (DWORD)HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_INITIATED), HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_INITIATED), BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Standard" },
88 { 0, E_FAIL, BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
89 { 1, S_OK, BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
90 { 3, S_OK, BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Custom" },
91 { 4, S_OK, BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Custom" },
92 { 5, HRESULT_FROM_WIN32(5), BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Custom" },
93 { (DWORD)HRESULT_FROM_WIN32(5), HRESULT_FROM_WIN32(5), BOOTSTRAPPER_APPLY_RESTART_REQUIRED, L"Custom" },
94 { 6, HRESULT_FROM_WIN32(6), BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Custom" },
95 { (DWORD)HRESULT_FROM_WIN32(6), HRESULT_FROM_WIN32(6), BOOTSTRAPPER_APPLY_RESTART_INITIATED, L"Custom" },
96 { ERROR_SUCCESS_REBOOT_REQUIRED, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
97 { (DWORD)HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
98 { ERROR_SUCCESS_RESTART_REQUIRED, HRESULT_FROM_WIN32(ERROR_SUCCESS_RESTART_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
99 { (DWORD)HRESULT_FROM_WIN32(ERROR_SUCCESS_RESTART_REQUIRED), HRESULT_FROM_WIN32(ERROR_SUCCESS_RESTART_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
100 { ERROR_SUCCESS_REBOOT_INITIATED, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
101 { (DWORD)HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED), HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
102 { ERROR_FAIL_REBOOT_REQUIRED, HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
103 { (DWORD)HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_REQUIRED), HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_REQUIRED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
104 { ERROR_FAIL_REBOOT_INITIATED, HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_INITIATED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
105 { (DWORD)HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_INITIATED), HRESULT_FROM_WIN32(ERROR_FAIL_REBOOT_INITIATED), BOOTSTRAPPER_APPLY_RESTART_NONE, L"Custom" },
106 };
107
108 engineState.sczBundleEngineWorkingPath = L"tests\\ignore\\this\\path\\to\\burn.exe";
109
110 try
111 {
112 ShelFunctionOverride(ExitCodeTest_ShellExecuteExW);
113 CoreFunctionOverride(ExitCodeTest_CreateProcessW, ThrdWaitForCompletion);
114
115 //
116 // per-user side setup
117 //
118 LoadEngineState(&engineState);
119
120 hr = ElevationElevate(&engineState, WM_BURN_ELEVATE, NULL);
121 TestThrowOnFailure(hr, L"Failed to elevate.");
122
123 for (DWORD i = 0; i < countof(rgExitCodeItems); ++i)
124 {
125 // "run" the package both per-user and per-machine
126 ExecuteExePackage(&engineState, rgExitCodeItems + i);
127 }
128
129 //
130 // initiate termination
131 //
132 hr = BurnPipeTerminateChildProcess(pConnection, 0, FALSE);
133 TestThrowOnFailure(hr, L"Failed to terminate elevated process.");
134
135 // check results
136 for (DWORD i = 0; i < countof(rgExitCodeItems); ++i)
137 {
138 EXIT_CODE_ITEM* pExitCode = rgExitCodeItems + i;
139 String^ packageId = gcnew String(pExitCode->wzPackageId);
140 String^ exitCodeString = ((UInt32)pExitCode->dwExitCode).ToString();
141
142 NativeAssert::SpecificReturnCode(pExitCode->hrExpectedResult, pExitCode->hrResultPerMachine, L"Per-machine package: {0}, exit code: {1}", packageId, exitCodeString);
143 Assert::True(pExitCode->expectedRestart == pExitCode->restartPerMachine, String::Format("Per-machine package: {0}, exit code: {1}, expected restart type '{2}' but got '{3}'", packageId, exitCodeString, gcnew String(LoggingRestartToString(pExitCode->expectedRestart)), gcnew String(LoggingRestartToString(pExitCode->restartPerMachine))));
144
145 NativeAssert::SpecificReturnCode(pExitCode->hrExpectedResult, pExitCode->hrResultPerUser, L"Per-user package: {0}, exit code: {1}", packageId, exitCodeString);
146 Assert::True(pExitCode->expectedRestart == pExitCode->restartPerUser, String::Format("Per-user package: {0}, exit code: {1}, expected restart type '{2}' but got '{3}'", packageId, exitCodeString, gcnew String(LoggingRestartToString(pExitCode->expectedRestart)), gcnew String(LoggingRestartToString(pExitCode->restartPerUser))));
147 }
148 }
149 finally
150 {
151 VariablesUninitialize(&engineState.variables);
152 BurnPipeConnectionUninitialize(pConnection);
153 }
154 }
155
156 private:
157 void ExecuteExePackage(
158 __in BURN_ENGINE_STATE* pEngineState,
159 __in EXIT_CODE_ITEM* pExitCode
160 )
161 {
162 HRESULT hr = S_OK;
163 LPWSTR sczExitCode = NULL;
164 BURN_PACKAGE* pPackage = NULL;
165 BURN_EXECUTE_ACTION executeAction = { };
166 BURN_PIPE_CONNECTION* pConnection = &pEngineState->companionConnection;
167 BOOL fRollback = FALSE;
168
169 hr = PackageFindById(&pEngineState->packages, pExitCode->wzPackageId, &pPackage);
170 TestThrowOnFailure(hr, L"Failed to find package.");
171
172 executeAction.type = BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE;
173 executeAction.exePackage.action = BOOTSTRAPPER_ACTION_STATE_INSTALL;
174 executeAction.exePackage.pPackage = pPackage;
175
176 try
177 {
178 hr = StrAllocFormatted(&sczExitCode, L"%u", pExitCode->dwExitCode);
179 TestThrowOnFailure(hr, L"Failed to convert exit code to string.");
180
181 hr = VariableSetString(&pEngineState->variables, L"ExeExitCode", sczExitCode, FALSE, FALSE);
182 TestThrowOnFailure(hr, L"Failed to set variable.");
183
184 pExitCode->hrResultPerMachine = ElevationExecuteExePackage(pConnection->hPipe, &executeAction, &pEngineState->variables, fRollback, ExitCodeTest_GenericMessageHandler, NULL, &pExitCode->restartPerMachine);
185
186 pExitCode->hrResultPerUser = ExeEngineExecutePackage(&executeAction, &pEngineState->cache, &pEngineState->variables, fRollback, ExitCodeTest_GenericMessageHandler, NULL, &pExitCode->restartPerUser);
187 }
188 finally
189 {
190 ReleaseStr(sczExitCode);
191 }
192 }
193 };
194
195
196 static BOOL STDAPICALLTYPE ExitCodeTest_ShellExecuteExW(
197 __inout LPSHELLEXECUTEINFOW lpExecInfo
198 )
199 {
200 HRESULT hr = S_OK;
201 LPWSTR scz = NULL;
202
203 hr = StrAllocString(&scz, lpExecInfo->lpParameters, 0);
204 ExitOnFailure(hr, "Failed to copy arguments.");
205
206 // Pretend this thread is the elevated process.
207 lpExecInfo->hProcess = ::CreateThread(NULL, 0, ExitCodeTest_ElevationThreadProc, scz, 0, NULL);
208 ExitOnNullWithLastError(lpExecInfo->hProcess, hr, "Failed to create thread.");
209 scz = NULL;
210
211 LExit:
212 ReleaseStr(scz);
213
214 return SUCCEEDED(hr);
215 }
216
217 static DWORD CALLBACK ExitCodeTest_ElevationThreadProc(
218 __in LPVOID lpThreadParameter
219 )
220 {
221 HRESULT hr = S_OK;
222 LPWSTR sczArguments = (LPWSTR)lpThreadParameter;
223 BURN_ENGINE_STATE engineState = { };
224 BURN_PIPE_CONNECTION* pConnection = &engineState.companionConnection;
225 HANDLE hLock = NULL;
226 DWORD dwChildExitCode = 0;
227 BOOL fRestart = FALSE;
228 BOOL fApplying = FALSE;
229
230 LoadEngineState(&engineState);
231
232 StrAlloc(&pConnection->sczName, MAX_PATH);
233 StrAlloc(&pConnection->sczSecret, MAX_PATH);
234
235 // parse command line arguments
236 if (3 != swscanf_s(sczArguments, L"-q -burn.elevated %s %s %u", pConnection->sczName, MAX_PATH, pConnection->sczSecret, MAX_PATH, &pConnection->dwProcessId))
237 {
238 hr = E_INVALIDARG;
239 ExitOnFailure(hr, "Failed to parse argument string.");
240 }
241
242 // set up connection with per-user process
243 hr = BurnPipeChildConnect(pConnection, TRUE);
244 ExitOnFailure(hr, "Failed to connect to per-user process.");
245
246 hr = ElevationChildPumpMessages(pConnection->hPipe, pConnection->hCachePipe, &engineState.approvedExes, &engineState.cache, &engineState.containers, &engineState.packages, &engineState.payloads, &engineState.variables, &engineState.registration, &engineState.userExperience, &hLock, &dwChildExitCode, &fRestart, &fApplying);
247 ExitOnFailure(hr, "Failed while pumping messages in child 'process'.");
248
249 LExit:
250 BurnPipeConnectionUninitialize(pConnection);
251 VariablesUninitialize(&engineState.variables);
252 ReleaseStr(sczArguments);
253
254 return FAILED(hr) ? (DWORD)hr : dwChildExitCode;
255 }
256
257 static BOOL STDAPICALLTYPE ExitCodeTest_CreateProcessW(
258 __in_opt LPCWSTR /*lpApplicationName*/,
259 __inout_opt LPWSTR lpCommandLine,
260 __in_opt LPSECURITY_ATTRIBUTES /*lpProcessAttributes*/,
261 __in_opt LPSECURITY_ATTRIBUTES /*lpThreadAttributes*/,
262 __in BOOL /*bInheritHandles*/,
263 __in DWORD /*dwCreationFlags*/,
264 __in_opt LPVOID /*lpEnvironment*/,
265 __in_opt LPCWSTR /*lpCurrentDirectory*/,
266 __in LPSTARTUPINFOW /*lpStartupInfo*/,
267 __out LPPROCESS_INFORMATION lpProcessInformation
268 )
269 {
270 HRESULT hr = S_OK;
271 LPWSTR scz = NULL;
272 LPCWSTR wzArgs = lpCommandLine;
273
274 hr = StrAllocString(&scz, wzArgs, 0);
275 ExitOnFailure(hr, "Failed to copy arguments.");
276
277 // Pretend this thread is the package process.
278 lpProcessInformation->hProcess = ::CreateThread(NULL, 0, ExitCodeTest_PackageThreadProc, scz, 0, NULL);
279 ExitOnNullWithLastError(lpProcessInformation->hProcess, hr, "Failed to create thread.");
280
281 scz = NULL;
282
283 LExit:
284 ReleaseStr(scz);
285
286 return SUCCEEDED(hr);
287 }
288
289 static DWORD CALLBACK ExitCodeTest_PackageThreadProc(
290 __in LPVOID lpThreadParameter
291 )
292 {
293 HRESULT hr = S_OK;
294 LPWSTR sczArguments = (LPWSTR)lpThreadParameter;
295 int argc = 0;
296 LPWSTR* argv = NULL;
297 DWORD dwResult = 0;
298
299 hr = AppParseCommandLine(sczArguments, &argc, &argv);
300 ExitOnFailure(hr, "Failed to parse command line: %ls", sczArguments);
301
302 hr = StrStringToUInt32(argv[1], 0, reinterpret_cast<UINT*>(&dwResult));
303 ExitOnFailure(hr, "Failed to convert %ls to DWORD.", argv[1]);
304
305 LExit:
306 AppFreeCommandLineArgs(argv);
307 ReleaseStr(sczArguments);
308
309 return FAILED(hr) ? (DWORD)hr : dwResult;
310 }
311
312 static int ExitCodeTest_GenericMessageHandler(
313 __in GENERIC_EXECUTE_MESSAGE* /*pMessage*/,
314 __in LPVOID /*pvContext*/
315 )
316 {
317 return IDNOACTION;
318 }
319
320 static void LoadEngineState(
321 __in BURN_ENGINE_STATE* pEngineState
322 )
323 {
324 HRESULT hr = S_OK;
325 IXMLDOMElement* pixeBundle = NULL;
326
327 LPCWSTR wzDocument =
328 L"<BurnManifest>"
329 L" <Payload Id='test.exe' FilePath='test.exe' Packaging='external' SourcePath='test.exe' Hash='000000000000' FileSize='1' />"
330 L" <Chain>"
331 L" <ExePackage Id='Custom' Cache='remove' CacheId='test.exe' InstallSize='1' Size='1' PerMachine='no' Permanent='yes' Vital='yes' DetectCondition='' InstallArguments='[ExeExitCode]' UninstallArguments='' Uninstallable='no' RepairArguments='' Repairable='no' Protocol='none' DetectionType='condition'>"
332 L" <ExitCode Code='0' Type='2' />"
333 L" <ExitCode Code='3' Type='3' />"
334 L" <ExitCode Code='4' Type='4' />"
335 L" <ExitCode Code='5' Type='5' />"
336 L" <ExitCode Code='-2147024891' Type='5' />"
337 L" <ExitCode Code='6' Type='6' />"
338 L" <ExitCode Code='-2147024890' Type='6' />"
339 L" <ExitCode Code='3010' Type='2' />"
340 L" <ExitCode Code='-2147021886' Type='2' />"
341 L" <ExitCode Code='3011' Type='2' />"
342 L" <ExitCode Code='-2147021885' Type='2' />"
343 L" <ExitCode Code='1641' Type='2' />"
344 L" <ExitCode Code='-2147023255' Type='2' />"
345 L" <ExitCode Code='3017' Type='2' />"
346 L" <ExitCode Code='-2147021879' Type='2' />"
347 L" <ExitCode Code='3018' Type='2' />"
348 L" <ExitCode Code='-2147021878' Type='2' />"
349 L" <ExitCode Code='*' Type='1' />"
350 L" <PayloadRef Id='test.exe' />"
351 L" </ExePackage>"
352 L" <ExePackage Id='Standard' Cache='remove' CacheId='test.exe' InstallSize='1' Size='1' PerMachine='no' Permanent='yes' Vital='yes' DetectCondition='' InstallArguments='[ExeExitCode]' UninstallArguments='' Uninstallable='no' RepairArguments='' Repairable='no' Protocol='none' DetectionType='condition'>"
353 L" <PayloadRef Id='test.exe' />"
354 L" </ExePackage>"
355 L" </Chain>"
356 L"</BurnManifest>";
357
358 VariableInitialize(&pEngineState->variables);
359
360 BurnPipeConnectionInitialize(&pEngineState->companionConnection);
361
362 hr = CacheInitialize(&pEngineState->cache, &pEngineState->internalCommand);
363 TestThrowOnFailure(hr, "CacheInitialize failed.");
364
365 // load XML document
366 LoadBundleXmlHelper(wzDocument, &pixeBundle);
367
368 hr = PayloadsParseFromXml(&pEngineState->payloads, &pEngineState->containers, &pEngineState->layoutPayloads, pixeBundle);
369 TestThrowOnFailure(hr, "Failed to parse payloads from manifest.");
370
371 hr = PackagesParseFromXml(&pEngineState->packages, &pEngineState->payloads, pixeBundle);
372 TestThrowOnFailure(hr, "Failed to parse packages from manifest.");
373
374 ReleaseObject(pixeBundle);
375 }
376 }
377 }
378 }
379 }
380 }