@joebigelow / wix-1 / commits / ec413164

Add embedded test.

Sean Hall committed Jun 29, 2022 at 10:29 UTC ec413164bd0285d1e9b9d36538974641a109b579
11 files changed +248 -33
src/burn/engine/core.cpp
+35
@@ -12,6 +12,7 @@ struct BURN_CACHE_THREAD_CONTEXT
12 };
13
14
15 +static PFN_CREATEPROCESSW vpfnCreateProcessW = ::CreateProcessW;
16 static PFN_PROCWAITFORCOMPLETION vpfnProcWaitForCompletion = ProcWaitForCompletion;
17
18
@@ -1947,12 +1948,46 @@ LExit:
1948 }
1949
1950 extern "C" void CoreFunctionOverride(
1951 + __in_opt PFN_CREATEPROCESSW pfnCreateProcessW,
1952 __in_opt PFN_PROCWAITFORCOMPLETION pfnProcWaitForCompletion
1953 )
1954 {
1955 + vpfnCreateProcessW = pfnCreateProcessW;
1956 vpfnProcWaitForCompletion = pfnProcWaitForCompletion;
1957 }
1958
1959 +extern "C" HRESULT CoreCreateProcess(
1960 + __in_opt LPCWSTR wzApplicationName,
1961 + __inout_opt LPWSTR sczCommandLine,
1962 + __in BOOL fInheritHandles,
1963 + __in DWORD dwCreationFlags,
1964 + __in_opt LPCWSTR wzCurrentDirectory,
1965 + __in WORD wShowWindow,
1966 + __out LPPROCESS_INFORMATION pProcessInformation
1967 + )
1968 +{
1969 + HRESULT hr = S_OK;
1970 + STARTUPINFOW si = { };
1971 + size_t cchCurrentDirectory = 0;
1972 +
1973 + // CreateProcessW has undocumented MAX_PATH restriction for lpCurrentDirectory even when long path support is enabled.
1974 + if (wzCurrentDirectory && FAILED(::StringCchLengthW(wzCurrentDirectory, MAX_PATH - 1, &cchCurrentDirectory)))
1975 + {
1976 + wzCurrentDirectory = NULL;
1977 + }
1978 +
1979 + si.cb = sizeof(si);
1980 + si.wShowWindow = wShowWindow;
1981 +
1982 + if (!vpfnCreateProcessW(wzApplicationName, sczCommandLine, NULL, NULL, fInheritHandles, dwCreationFlags, NULL, wzCurrentDirectory, &si, pProcessInformation))
1983 + {
1984 + ExitWithLastError(hr, "CreateProcessW failed with return code: %d", Dutil_er);
1985 + }
1986 +
1987 +LExit:
1988 + return hr;
1989 +}
1990 +
1991 extern "C" HRESULT DAPI CoreWaitForProcCompletion(
1992 __in HANDLE hProcess,
1993 __in DWORD dwTimeout,
src/burn/engine/core.h
+23
@@ -174,6 +174,19 @@ typedef struct _BURN_APPLY_CONTEXT
174 DWORD dwCacheCheckpoint;
175 } BURN_APPLY_CONTEXT;
176
177 +typedef BOOL (STDAPICALLTYPE *PFN_CREATEPROCESSW)(
178 + __in_opt LPCWSTR lpApplicationName,
179 + __inout_opt LPWSTR lpCommandLine,
180 + __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
181 + __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
182 + __in BOOL bInheritHandles,
183 + __in DWORD dwCreationFlags,
184 + __in_opt LPVOID lpEnvironment,
185 + __in_opt LPCWSTR lpCurrentDirectory,
186 + __in LPSTARTUPINFOW lpStartupInfo,
187 + __out LPPROCESS_INFORMATION lpProcessInformation
188 + );
189 +
190 typedef HRESULT (DAPI *PFN_PROCWAITFORCOMPLETION)(
191 __in HANDLE hProcess,
192 __in DWORD dwTimeout,
@@ -287,8 +300,18 @@ HRESULT CoreParseCommandLine(
300 __inout HANDLE* phSourceEngineFile
301 );
302 void CoreFunctionOverride(
303 + __in_opt PFN_CREATEPROCESSW pfnCreateProcessW,
304 __in_opt PFN_PROCWAITFORCOMPLETION pfnProcWaitForCompletion
305 );
306 +HRESULT CoreCreateProcess(
307 + __in_opt LPCWSTR wzApplicationName,
308 + __inout_opt LPWSTR sczCommandLine,
309 + __in BOOL fInheritHandles,
310 + __in DWORD dwCreationFlags,
311 + __in_opt LPCWSTR wzCurrentDirectory,
312 + __in WORD wShowWindow,
313 + __out LPPROCESS_INFORMATION pProcessInformation
314 + );
315 HRESULT DAPI CoreWaitForProcCompletion(
316 __in HANDLE hProcess,
317 __in DWORD dwTimeout,
src/burn/engine/embedded.cpp
+3 -6
@@ -52,7 +52,6 @@ extern "C" HRESULT EmbeddedRunBundle(
52 DWORD dwCurrentProcessId = ::GetCurrentProcessId();
53 HANDLE hCreatedPipesEvent = NULL;
54 LPWSTR sczCommand = NULL;
55 - STARTUPINFOW si = { };
55 PROCESS_INFORMATION pi = { };
56 BURN_PIPE_RESULT result = { };
57
@@ -79,10 +78,8 @@ extern "C" HRESULT EmbeddedRunBundle(
78 ExitOnFailure(hr, "Failed to append user args.");
79 }
80
82 - if (!::CreateProcessW(wzExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
83 - {
84 - ExitWithLastError(hr, "Failed to create embedded process at path: %ls", wzExecutablePath);
85 - }
81 + hr = CoreCreateProcess(wzExecutablePath, sczCommand, TRUE, CREATE_NO_WINDOW, NULL, 0, &pi);
82 + ExitOnFailure(hr, "Failed to create embedded process at path: %ls", wzExecutablePath);
83
84 connection.dwProcessId = ::GetProcessId(pi.hProcess);
85 connection.hProcess = pi.hProcess;
@@ -95,7 +92,7 @@ extern "C" HRESULT EmbeddedRunBundle(
92 ExitOnFailure(hr, "Failed to process messages from embedded message.");
93
94 // Get the return code from the embedded process.
98 - hr = ProcWaitForCompletion(connection.hProcess, INFINITE, pdwExitCode);
95 + hr = CoreWaitForProcCompletion(connection.hProcess, INFINITE, pdwExitCode);
96 ExitOnFailure(hr, "Failed to wait for embedded executable: %ls", wzExecutablePath);
97
98 LExit:
src/burn/engine/engine.cpp
+2 -7
@@ -445,7 +445,6 @@ static HRESULT RunUntrusted(
445 LPWSTR sczCachedCleanRoomBundlePath = NULL;
446 LPWSTR sczParameters = NULL;
447 LPWSTR sczFullCommandLine = NULL;
448 - STARTUPINFOW si = { };
448 PROCESS_INFORMATION pi = { };
449 HANDLE hFileAttached = NULL;
450 HANDLE hFileSelf = NULL;
@@ -484,12 +483,8 @@ static HRESULT RunUntrusted(
483 hr = StrAllocFormattedSecure(&sczFullCommandLine, L"\"%ls\" %ls", wzCleanRoomBundlePath, sczParameters);
484 ExitOnFailure(hr, "Failed to allocate full command-line.");
485
487 - si.cb = sizeof(si);
488 - si.wShowWindow = static_cast<WORD>(pEngineState->command.nCmdShow);
489 - if (!::CreateProcessW(wzCleanRoomBundlePath, sczFullCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
490 - {
491 - ExitWithLastError(hr, "Failed to launch clean room process: %ls", sczFullCommandLine);
492 - }
486 + hr = CoreCreateProcess(wzCleanRoomBundlePath, sczFullCommandLine, TRUE, 0, NULL, static_cast<WORD>(pEngineState->command.nCmdShow), &pi);
487 + ExitOnFailure(hr, "Failed to launch clean room process: %ls", sczFullCommandLine);
488
489 hProcess = pi.hProcess;
490 pi.hProcess = NULL;
src/burn/engine/exeengine.cpp
+2 -13
@@ -687,7 +687,6 @@ extern "C" HRESULT ExeEngineRunProcess(
687 {
688 HRESULT hr = S_OK;
689 LPWSTR sczCommand = NULL;
690 - STARTUPINFOW si = { };
690 PROCESS_INFORMATION pi = { };
691 GENERIC_EXECUTE_MESSAGE message = { };
692 int nResult = IDNOACTION;
@@ -695,7 +694,6 @@ extern "C" HRESULT ExeEngineRunProcess(
694 BOOL fDelayedCancel = FALSE;
695 BOOL fFireAndForget = BURN_PACKAGE_TYPE_EXE == pPackage->type && pPackage->Exe.fFireAndForget;
696 BOOL fInheritHandles = BURN_PACKAGE_TYPE_BUNDLE == pPackage->type;
698 - size_t cchCachedDirectory = 0;
697
698 // Always add user supplied arguments last.
699 if (wzUserArgs)
@@ -704,19 +702,10 @@ extern "C" HRESULT ExeEngineRunProcess(
702 ExitOnFailure(hr, "Failed to append user args.");
703 }
704
707 - // CreateProcessW has undocumented MAX_PATH restriction for lpCurrentDirectory even when long path support is enabled.
708 - if (wzCachedDirectory && FAILED(::StringCchLengthW(wzCachedDirectory, MAX_PATH - 1, &cchCachedDirectory)))
709 - {
710 - wzCachedDirectory = NULL;
711 - }
712 -
705 // Make the cache location of the executable the current directory to help those executables
706 // that expect stuff to be relative to them.
715 - si.cb = sizeof(si);
716 - if (!::CreateProcessW(wzExecutablePath, sczCommand ? sczCommand : sczBaseCommand, NULL, NULL, fInheritHandles, CREATE_NO_WINDOW, NULL, wzCachedDirectory, &si, &pi))
717 - {
718 - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath);
719 - }
707 + hr = CoreCreateProcess(wzExecutablePath, sczCommand ? sczCommand : sczBaseCommand, fInheritHandles, CREATE_NO_WINDOW, wzCachedDirectory, 0, &pi);
708 + ExitOnFailure(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath);
709
710 message.type = GENERIC_EXECUTE_MESSAGE_PROCESS_STARTED;
711 message.dwUIHint = MB_OK;
src/burn/engine/netfxchainer.cpp
+2 -6
@@ -345,7 +345,6 @@ extern "C" HRESULT NetFxRunChainer(
345 LPWSTR sczSectionName = NULL;
346 LPWSTR sczCommand = NULL;
347 NetFxChainer* pNetfxChainer = NULL;
348 - STARTUPINFOW si = { };
348 PROCESS_INFORMATION pi = { };
349 HRESULT hrInternalError = 0;
350
@@ -372,11 +371,8 @@ extern "C" HRESULT NetFxRunChainer(
371 ExitOnFailure(hr, "Failed to append user args.");
372 }
373
375 - si.cb = sizeof(si);
376 - if (!::CreateProcessW(wzExecutablePath, sczCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
377 - {
378 - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath);
379 - }
374 + hr = CoreCreateProcess(wzExecutablePath, sczCommand, FALSE, CREATE_NO_WINDOW, NULL, 0, &pi);
375 + ExitOnFailure(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath);
376
377 HANDLE handles[2] = { pi.hProcess, pNetfxChainer->hEventChaineeSend };
378
src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj
+1
@@ -47,6 +47,7 @@
47 <ClCompile Include="AssemblyInfo.cpp" />
48 <ClCompile Include="CacheTest.cpp" />
49 <ClCompile Include="ElevationTest.cpp" />
50 + <ClCompile Include="EmbeddedTest.cpp" />
51 <ClCompile Include="ManifestHelpers.cpp" />
52 <ClCompile Include="ManifestTest.cpp" />
53 <ClCompile Include="PlanTest.cpp" />
src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters
+3
@@ -24,6 +24,9 @@
24 <ClCompile Include="ElevationTest.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 + <ClCompile Include="EmbeddedTest.cpp">
28 + <Filter>Source Files</Filter>
29 + </ClCompile>
30 <ClCompile Include="ManifestHelpers.cpp">
31 <Filter>Source Files</Filter>
32 </ClCompile>
src/burn/test/BurnUnitTest/ElevationTest.cpp
+1 -1
@@ -61,7 +61,7 @@ namespace Bootstrapper
61 try
62 {
63 ShelFunctionOverride(ElevateTest_ShellExecuteExW);
64 - CoreFunctionOverride(ThrdWaitForCompletion);
64 + CoreFunctionOverride(NULL, ThrdWaitForCompletion);
65
66 PipeConnectionInitialize(pConnection);
67
src/burn/test/BurnUnitTest/EmbeddedTest.cpp new
+175
@@ -0,0 +1,175 @@
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 +const HRESULT S_TEST_SUCCEEDED = 0x3133;
7 +const DWORD TEST_EXIT_CODE = 666;
8 +
9 +struct BUNDLE_RUNNER_CONTEXT
10 +{
11 + DWORD dwResult;
12 +};
13 +
14 +
15 +static BOOL STDAPICALLTYPE EmbeddedTest_CreateProcessW(
16 + __in_opt LPCWSTR lpApplicationName,
17 + __inout_opt LPWSTR lpCommandLine,
18 + __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
19 + __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
20 + __in BOOL bInheritHandles,
21 + __in DWORD dwCreationFlags,
22 + __in_opt LPVOID lpEnvironment,
23 + __in_opt LPCWSTR lpCurrentDirectory,
24 + __in LPSTARTUPINFOW lpStartupInfo,
25 + __out LPPROCESS_INFORMATION lpProcessInformation
26 + );
27 +static DWORD CALLBACK EmbeddedTest_ThreadProc(
28 + __in LPVOID lpThreadParameter
29 + );
30 +static int EmbeddedTest_GenericMessageHandler(
31 + __in GENERIC_EXECUTE_MESSAGE* pMessage,
32 + __in LPVOID pvContext
33 + );
34 +
35 +namespace Microsoft
36 +{
37 +namespace Tools
38 +{
39 +namespace WindowsInstallerXml
40 +{
41 +namespace Test
42 +{
43 +namespace Bootstrapper
44 +{
45 + using namespace System;
46 + using namespace System::IO;
47 + using namespace System::Threading;
48 + using namespace Xunit;
49 +
50 + public ref class EmbeddedTest : BurnUnitTest
51 + {
52 + public:
53 + EmbeddedTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture)
54 + {
55 + }
56 +
57 + [Fact]
58 + void EmbeddedProtocolTest()
59 + {
60 + HRESULT hr = S_OK;
61 + BUNDLE_RUNNER_CONTEXT bundleRunnerContext = { };
62 + DWORD dwExitCode = 0;
63 +
64 + try
65 + {
66 + CoreFunctionOverride(EmbeddedTest_CreateProcessW, ThrdWaitForCompletion);
67 +
68 + //
69 + // bundle runner setup
70 + //
71 + hr = EmbeddedRunBundle(L"C:\\ignored\\target.exe", L"\"C:\\ignored\\target.exe\"", NULL, EmbeddedTest_GenericMessageHandler, &bundleRunnerContext, &dwExitCode);
72 + TestThrowOnFailure(hr, L"Failed to run embedded bundle.");
73 +
74 + // check results
75 + Assert::Equal<HRESULT>(S_TEST_SUCCEEDED, (HRESULT)bundleRunnerContext.dwResult);
76 + Assert::Equal<DWORD>(TEST_EXIT_CODE, dwExitCode);
77 + }
78 + finally
79 + {
80 + }
81 + }
82 + };
83 +}
84 +}
85 +}
86 +}
87 +}
88 +
89 +
90 +static BOOL STDAPICALLTYPE EmbeddedTest_CreateProcessW(
91 + __in_opt LPCWSTR /*lpApplicationName*/,
92 + __inout_opt LPWSTR lpCommandLine,
93 + __in_opt LPSECURITY_ATTRIBUTES /*lpProcessAttributes*/,
94 + __in_opt LPSECURITY_ATTRIBUTES /*lpThreadAttributes*/,
95 + __in BOOL /*bInheritHandles*/,
96 + __in DWORD /*dwCreationFlags*/,
97 + __in_opt LPVOID /*lpEnvironment*/,
98 + __in_opt LPCWSTR /*lpCurrentDirectory*/,
99 + __in LPSTARTUPINFOW /*lpStartupInfo*/,
100 + __out LPPROCESS_INFORMATION lpProcessInformation
101 + )
102 +{
103 + HRESULT hr = S_OK;
104 + LPWSTR scz = NULL;
105 + LPCWSTR wzArgs = lpCommandLine + 24; //skip '"C:\ignored\target.exe" '
106 +
107 + hr = StrAllocString(&scz, wzArgs, 0);
108 + ExitOnFailure(hr, "Failed to copy arguments.");
109 +
110 + // Pretend this thread is the embedded process.
111 + lpProcessInformation->hProcess = ::CreateThread(NULL, 0, EmbeddedTest_ThreadProc, scz, 0, NULL);
112 + ExitOnNullWithLastError(lpProcessInformation->hProcess, hr, "Failed to create thread.");
113 +
114 + scz = NULL;
115 +
116 +LExit:
117 + ReleaseStr(scz);
118 +
119 + return SUCCEEDED(hr);
120 +}
121 +
122 +static DWORD CALLBACK EmbeddedTest_ThreadProc(
123 + __in LPVOID lpThreadParameter
124 + )
125 +{
126 + HRESULT hr = S_OK;
127 + LPWSTR sczArguments = (LPWSTR)lpThreadParameter;
128 + BURN_ENGINE_STATE engineState = { };
129 + BURN_PIPE_CONNECTION* pConnection = &engineState.embeddedConnection;
130 + DWORD dwResult = 0;
131 +
132 + engineState.internalCommand.mode = BURN_MODE_EMBEDDED;
133 +
134 + PipeConnectionInitialize(pConnection);
135 +
136 + StrAlloc(&pConnection->sczName, MAX_PATH);
137 + StrAlloc(&pConnection->sczSecret, MAX_PATH);
138 +
139 + // parse command line arguments
140 + if (3 != swscanf_s(sczArguments, L"-burn.embedded %s %s %u", pConnection->sczName, MAX_PATH, pConnection->sczSecret, MAX_PATH, &pConnection->dwProcessId))
141 + {
142 + ExitWithRootFailure(hr, E_INVALIDARG, "Failed to parse argument string.");
143 + }
144 +
145 + // set up connection with parent bundle runner
146 + hr = PipeChildConnect(pConnection, FALSE);
147 + ExitOnFailure(hr, "Failed to connect to parent bundle runner.");
148 +
149 + // post known message
150 + hr = ExternalEngineSendEmbeddedError(&engineState, S_TEST_SUCCEEDED, NULL, 0, reinterpret_cast<int*>(&dwResult));
151 + ExitOnFailure(hr, "Failed to post known message to parent bundle runner.");
152 +
153 +LExit:
154 + PipeConnectionUninitialize(pConnection);
155 + ReleaseStr(sczArguments);
156 +
157 + return FAILED(hr) ? (DWORD)hr : dwResult;
158 +}
159 +
160 +static int EmbeddedTest_GenericMessageHandler(
161 + __in GENERIC_EXECUTE_MESSAGE* pMessage,
162 + __in LPVOID pvContext
163 + )
164 +{
165 + BUNDLE_RUNNER_CONTEXT* pContext = reinterpret_cast<BUNDLE_RUNNER_CONTEXT*>(pvContext);
166 + DWORD dwResult = 0;
167 +
168 + if (GENERIC_EXECUTE_MESSAGE_ERROR == pMessage->type)
169 + {
170 + pContext->dwResult = pMessage->error.dwErrorCode;
171 + dwResult = TEST_EXIT_CODE;
172 + }
173 +
174 + return dwResult;
175 +}
src/burn/test/BurnUnitTest/precomp.h
+1
@@ -73,6 +73,7 @@
73 #include "manifest.h"
74 #include "splashscreen.h"
75 #include "detect.h"
76 +#include "externalengine.h"
77
78 #include "engine.version.h"
79