diff options
author | Rob Mensching <rob@firegiant.com> | 2022-03-31 11:56:14 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2022-03-31 18:01:06 -0700 |
commit | 47582b162368e8edf7a3b11c13b8e9dabc5f0a26 (patch) | |
tree | 2c4063eff325684bed39de0edacd7866a257ae02 /src/dtf/SfxCA/SfxCA.cpp | |
parent | 167296c42497c4e95f0d5d71168542d747655981 (diff) | |
download | wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.tar.gz wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.tar.bz2 wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.zip |
Provide managed CA and Embedded UI DTF libraries via NuGet
Lots of refactoring to bring the SFX tooling back into the 'dtf'
layer since they are (in the end) tightly coupled to some DTF
assemblies. Also refactored the DTF tests into their own folder
and added a couple integration tests to build using the new CA/UI
NuGet package.
Closes wixtoolset/issues#6080
Diffstat (limited to 'src/dtf/SfxCA/SfxCA.cpp')
-rw-r--r-- | src/dtf/SfxCA/SfxCA.cpp | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/src/dtf/SfxCA/SfxCA.cpp b/src/dtf/SfxCA/SfxCA.cpp new file mode 100644 index 00000000..06319f1e --- /dev/null +++ b/src/dtf/SfxCA/SfxCA.cpp | |||
@@ -0,0 +1,363 @@ | |||
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 | #include "EntryPoints.h" | ||
5 | #include "SfxUtil.h" | ||
6 | |||
7 | #define MANAGED_CAs_OUT_OF_PROC 1 | ||
8 | |||
9 | HMODULE g_hModule; | ||
10 | bool g_fRunningOutOfProc = false; | ||
11 | |||
12 | RemoteMsiSession* g_pRemote = NULL; | ||
13 | |||
14 | // Prototypes for local functions. | ||
15 | // See the function definitions for comments. | ||
16 | |||
17 | bool InvokeManagedCustomAction(MSIHANDLE hSession, | ||
18 | _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); | ||
19 | |||
20 | /// <summary> | ||
21 | /// Entry-point for the CA DLL when re-launched as a separate process; | ||
22 | /// connects the comm channel for remote MSI APIs, then invokes the | ||
23 | /// managed custom action entry-point. | ||
24 | /// </summary> | ||
25 | /// <remarks> | ||
26 | /// Do not change the parameters or calling-convention: RUNDLL32 | ||
27 | /// requires this exact signature. | ||
28 | /// </remarks> | ||
29 | extern "C" | ||
30 | void __stdcall InvokeManagedCustomActionOutOfProc( | ||
31 | __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) | ||
32 | { | ||
33 | UNREFERENCED_PARAMETER(hwnd); | ||
34 | UNREFERENCED_PARAMETER(hinst); | ||
35 | UNREFERENCED_PARAMETER(nCmdShow); | ||
36 | |||
37 | g_fRunningOutOfProc = true; | ||
38 | |||
39 | const wchar_t* szSessionName = szCmdLine; | ||
40 | MSIHANDLE hSession; | ||
41 | const wchar_t* szEntryPoint; | ||
42 | |||
43 | int i; | ||
44 | for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); | ||
45 | if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; | ||
46 | hSession = _wtoi(szCmdLine + i); | ||
47 | |||
48 | for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); | ||
49 | if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; | ||
50 | szEntryPoint = szCmdLine + i; | ||
51 | |||
52 | g_pRemote = new RemoteMsiSession(szSessionName, false); | ||
53 | g_pRemote->Connect(); | ||
54 | |||
55 | int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); | ||
56 | |||
57 | RemoteMsiSession::RequestData requestData; | ||
58 | SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); | ||
59 | requestData.fields[0].vt = VT_I4; | ||
60 | requestData.fields[0].iValue = ret; | ||
61 | g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); | ||
62 | delete g_pRemote; | ||
63 | } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Re-launch this CA DLL as a separate process, and setup a comm channel | ||
67 | /// for remote MSI API calls back to this process. | ||
68 | /// </summary> | ||
69 | int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) | ||
70 | { | ||
71 | wchar_t szSessionName[100] = {0}; | ||
72 | swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); | ||
73 | |||
74 | RemoteMsiSession remote(szSessionName, true); | ||
75 | |||
76 | DWORD ret = remote.Connect(); | ||
77 | if (ret != 0) | ||
78 | { | ||
79 | Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); | ||
80 | return ERROR_INSTALL_FAILURE; | ||
81 | } | ||
82 | |||
83 | ret = remote.ProcessRequests(); | ||
84 | if (ret != 0) | ||
85 | { | ||
86 | Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); | ||
87 | return ERROR_INSTALL_FAILURE; | ||
88 | } | ||
89 | |||
90 | wchar_t szModule[MAX_PATH] = {0}; | ||
91 | GetModuleFileName(g_hModule, szModule, MAX_PATH); | ||
92 | |||
93 | const wchar_t* rundll32 = L"rundll32.exe"; | ||
94 | wchar_t szRunDll32Path[MAX_PATH] = {0}; | ||
95 | GetSystemDirectory(szRunDll32Path, MAX_PATH); | ||
96 | wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); | ||
97 | wcscat_s(szRunDll32Path, MAX_PATH, rundll32); | ||
98 | |||
99 | const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; | ||
100 | wchar_t szCommandLine[1024] = {0}; | ||
101 | swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", | ||
102 | rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); | ||
103 | |||
104 | STARTUPINFO si; | ||
105 | SecureZeroMemory(&si, sizeof(STARTUPINFO)); | ||
106 | si.cb = sizeof(STARTUPINFO); | ||
107 | |||
108 | PROCESS_INFORMATION pi; | ||
109 | SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); | ||
110 | |||
111 | if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, | ||
112 | 0, NULL, NULL, &si, &pi)) | ||
113 | { | ||
114 | DWORD err = GetLastError(); | ||
115 | Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); | ||
116 | return ERROR_INSTALL_FAILURE; | ||
117 | } | ||
118 | |||
119 | DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); | ||
120 | if (dwWait != WAIT_OBJECT_0) | ||
121 | { | ||
122 | DWORD err = GetLastError(); | ||
123 | Log(hSession, L"Failed to wait for CA process. Error code: %d", err); | ||
124 | return ERROR_INSTALL_FAILURE; | ||
125 | } | ||
126 | |||
127 | DWORD dwExitCode; | ||
128 | BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); | ||
129 | if (!bRet) | ||
130 | { | ||
131 | DWORD err = GetLastError(); | ||
132 | Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); | ||
133 | return ERROR_INSTALL_FAILURE; | ||
134 | } | ||
135 | else if (dwExitCode != 0) | ||
136 | { | ||
137 | Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); | ||
138 | return ERROR_INSTALL_FAILURE; | ||
139 | } | ||
140 | |||
141 | CloseHandle(pi.hThread); | ||
142 | CloseHandle(pi.hProcess); | ||
143 | |||
144 | remote.WaitExitCode(); | ||
145 | return remote.ExitCode; | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Entrypoint for the managed CA proxy (RemotableNativeMethods) to | ||
150 | /// call MSI APIs remotely. | ||
151 | /// </summary> | ||
152 | void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) | ||
153 | { | ||
154 | if (g_fRunningOutOfProc) | ||
155 | { | ||
156 | g_pRemote->SendRequest(id, pRequest, ppResponse); | ||
157 | } | ||
158 | else | ||
159 | { | ||
160 | *ppResponse = NULL; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | /// <summary> | ||
165 | /// Invokes a managed custom action from native code by | ||
166 | /// extracting the package to a temporary working directory | ||
167 | /// then hosting the CLR and locating and calling the entrypoint. | ||
168 | /// </summary> | ||
169 | /// <param name="hSession">Handle to the installation session. | ||
170 | /// Passed to custom action entrypoints by the installer engine.</param> | ||
171 | /// <param name="szWorkingDir">Directory containing the CA binaries | ||
172 | /// and the CustomAction.config file defining the entrypoints. | ||
173 | /// This may be NULL, in which case the current module must have | ||
174 | /// a concatenated cabinet containing those files, which will be | ||
175 | /// extracted to a temporary directory.</param> | ||
176 | /// <param name="szEntryPoint">Name of the CA entrypoint to be invoked. | ||
177 | /// This must be either an explicit "AssemblyName!Namespace.Class.Method" | ||
178 | /// string, or a simple name that maps to a full entrypoint definition | ||
179 | /// in CustomAction.config.</param> | ||
180 | /// <returns>The value returned by the managed custom action method, | ||
181 | /// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns> | ||
182 | int InvokeCustomAction(MSIHANDLE hSession, | ||
183 | const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) | ||
184 | { | ||
185 | #ifdef MANAGED_CAs_OUT_OF_PROC | ||
186 | if (!g_fRunningOutOfProc && szWorkingDir == NULL) | ||
187 | { | ||
188 | return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); | ||
189 | } | ||
190 | #endif | ||
191 | |||
192 | wchar_t szTempDir[MAX_PATH]; | ||
193 | bool fDeleteTemp = false; | ||
194 | if (szWorkingDir == NULL) | ||
195 | { | ||
196 | if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) | ||
197 | { | ||
198 | return ERROR_INSTALL_FAILURE; | ||
199 | } | ||
200 | szWorkingDir = szTempDir; | ||
201 | fDeleteTemp = true; | ||
202 | } | ||
203 | |||
204 | wchar_t szConfigFilePath[MAX_PATH + 20]; | ||
205 | StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); | ||
206 | StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); | ||
207 | |||
208 | const wchar_t* szConfigFile = szConfigFilePath; | ||
209 | if (!::PathFileExists(szConfigFilePath)) | ||
210 | { | ||
211 | szConfigFile = NULL; | ||
212 | } | ||
213 | |||
214 | wchar_t szWIAssembly[MAX_PATH + 50]; | ||
215 | StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); | ||
216 | StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); | ||
217 | |||
218 | int iResult = ERROR_INSTALL_FAILURE; | ||
219 | ICorRuntimeHost* pHost; | ||
220 | if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) | ||
221 | { | ||
222 | _AppDomain* pAppDomain; | ||
223 | if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, | ||
224 | szConfigFile, &pAppDomain)) | ||
225 | { | ||
226 | if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) | ||
227 | { | ||
228 | iResult = ERROR_INSTALL_FAILURE; | ||
229 | } | ||
230 | HRESULT hr = pHost->UnloadDomain(pAppDomain); | ||
231 | if (FAILED(hr)) | ||
232 | { | ||
233 | Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); | ||
234 | } | ||
235 | pAppDomain->Release(); | ||
236 | } | ||
237 | |||
238 | pHost->Stop(); | ||
239 | pHost->Release(); | ||
240 | } | ||
241 | |||
242 | if (fDeleteTemp) | ||
243 | { | ||
244 | DeleteDirectory(szTempDir); | ||
245 | } | ||
246 | return iResult; | ||
247 | } | ||
248 | |||
249 | /// <summary> | ||
250 | /// Called by the system when the DLL is loaded. | ||
251 | /// Saves the module handle for later use. | ||
252 | /// </summary> | ||
253 | BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) | ||
254 | { | ||
255 | UNREFERENCED_PARAMETER(pReserved); | ||
256 | |||
257 | switch (dwReason) | ||
258 | { | ||
259 | case DLL_PROCESS_ATTACH: | ||
260 | g_hModule = hModule; | ||
261 | break; | ||
262 | case DLL_THREAD_ATTACH: | ||
263 | case DLL_THREAD_DETACH: | ||
264 | case DLL_PROCESS_DETACH: | ||
265 | break; | ||
266 | } | ||
267 | return TRUE; | ||
268 | } | ||
269 | |||
270 | /// <summary> | ||
271 | /// Loads and invokes the managed portion of the proxy. | ||
272 | /// </summary> | ||
273 | /// <param name="hSession">Handle to the installer session, | ||
274 | /// used for logging errors and to be passed on to the custom action.</param> | ||
275 | /// <param name="pAppDomain">AppDomain which has its application | ||
276 | /// base set to the CA working directory.</param> | ||
277 | /// <param name="szEntryPoint">Name of the CA entrypoint to be invoked. | ||
278 | /// This must be either an explicit "AssemblyName!Namespace.Class.Method" | ||
279 | /// string, or a simple name that maps to a full entrypoint definition | ||
280 | /// in CustomAction.config.</param> | ||
281 | /// <param name="piResult">Return value of the invoked custom | ||
282 | /// action method.</param> | ||
283 | /// <returns>True if the managed proxy was invoked successfully, | ||
284 | /// false if there was some error. Note the custom action itself may | ||
285 | /// return an error via piResult while this method still returns true | ||
286 | /// since the invocation was successful.</returns> | ||
287 | bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
288 | const wchar_t* szEntryPoint, int* piResult) | ||
289 | { | ||
290 | VARIANT vResult; | ||
291 | ::VariantInit(&vResult); | ||
292 | |||
293 | const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); | ||
294 | const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; | ||
295 | const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; | ||
296 | const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); | ||
297 | |||
298 | _MethodInfo* pCAInvokeMethod; | ||
299 | if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, | ||
300 | szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) | ||
301 | { | ||
302 | return false; | ||
303 | } | ||
304 | |||
305 | HRESULT hr; | ||
306 | VARIANT vNull; | ||
307 | vNull.vt = VT_EMPTY; | ||
308 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); | ||
309 | VARIANT vSessionHandle; | ||
310 | vSessionHandle.vt = VT_I4; | ||
311 | vSessionHandle.intVal = hSession; | ||
312 | LONG index = 0; | ||
313 | hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); | ||
314 | if (FAILED(hr)) goto LExit; | ||
315 | VARIANT vEntryPoint; | ||
316 | vEntryPoint.vt = VT_BSTR; | ||
317 | vEntryPoint.bstrVal = SysAllocString(szEntryPoint); | ||
318 | if (vEntryPoint.bstrVal == NULL) | ||
319 | { | ||
320 | hr = E_OUTOFMEMORY; | ||
321 | goto LExit; | ||
322 | } | ||
323 | index = 1; | ||
324 | hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); | ||
325 | if (FAILED(hr)) goto LExit; | ||
326 | VARIANT vRemotingFunctionPtr; | ||
327 | #pragma warning(push) | ||
328 | #pragma warning(disable:4127) // conditional expression is constant | ||
329 | if (f64bit) | ||
330 | #pragma warning(pop) | ||
331 | { | ||
332 | vRemotingFunctionPtr.vt = VT_I8; | ||
333 | vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); | ||
334 | } | ||
335 | else | ||
336 | { | ||
337 | vRemotingFunctionPtr.vt = VT_I4; | ||
338 | #pragma warning(push) | ||
339 | #pragma warning(disable:4302) // truncation | ||
340 | #pragma warning(disable:4311) // pointer truncation | ||
341 | vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); | ||
342 | #pragma warning(pop) | ||
343 | } | ||
344 | index = 2; | ||
345 | hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); | ||
346 | if (FAILED(hr)) goto LExit; | ||
347 | |||
348 | hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); | ||
349 | |||
350 | LExit: | ||
351 | SafeArrayDestroy(saArgs); | ||
352 | pCAInvokeMethod->Release(); | ||
353 | |||
354 | if (FAILED(hr)) | ||
355 | { | ||
356 | Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); | ||
357 | return false; | ||
358 | } | ||
359 | |||
360 | *piResult = vResult.intVal; | ||
361 | return true; | ||
362 | } | ||
363 | |||