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/EmbeddedUI.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/EmbeddedUI.cpp')
-rw-r--r-- | src/dtf/SfxCA/EmbeddedUI.cpp | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/src/dtf/SfxCA/EmbeddedUI.cpp b/src/dtf/SfxCA/EmbeddedUI.cpp new file mode 100644 index 00000000..a49cdeec --- /dev/null +++ b/src/dtf/SfxCA/EmbeddedUI.cpp | |||
@@ -0,0 +1,281 @@ | |||
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 "SfxUtil.h" | ||
5 | |||
6 | // Globals for keeping track of things across UI messages. | ||
7 | static const wchar_t* g_szWorkingDir; | ||
8 | static ICorRuntimeHost* g_pClrHost; | ||
9 | static _AppDomain* g_pAppDomain; | ||
10 | static _MethodInfo* g_pProcessMessageMethod; | ||
11 | static _MethodInfo* g_pShutdownMethod; | ||
12 | |||
13 | // Reserve extra space for strings to be replaced at build time. | ||
14 | #define NULLSPACE \ | ||
15 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
16 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
17 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
18 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
19 | |||
20 | // Prototypes for local functions. | ||
21 | // See the function definitions for comments. | ||
22 | |||
23 | bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, | ||
24 | const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult); | ||
25 | |||
26 | /// <summary> | ||
27 | /// First entry-point for the UI DLL when loaded and called by MSI. | ||
28 | /// Extracts the payload, hosts the CLR, and invokes the managed | ||
29 | /// initialize method. | ||
30 | /// </summary> | ||
31 | /// <param name="hSession">Handle to the installer session, | ||
32 | /// used for logging errors and to be passed on to the managed initialize method.</param> | ||
33 | /// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table | ||
34 | /// have been extracted, and where additional payload from this package will be extracted.</param> | ||
35 | /// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from | ||
36 | /// the managed initialize method.</param> | ||
37 | extern "C" | ||
38 | UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel) | ||
39 | { | ||
40 | // If the managed initialize method cannot be called, continue the installation in BASIC UI mode. | ||
41 | UINT uiResult = INSTALLUILEVEL_BASIC; | ||
42 | |||
43 | const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE; | ||
44 | |||
45 | g_szWorkingDir = szResourcePath; | ||
46 | |||
47 | wchar_t szModule[MAX_PATH]; | ||
48 | DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1); | ||
49 | if (cchCopied == 0) | ||
50 | { | ||
51 | Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); | ||
52 | return uiResult; | ||
53 | } | ||
54 | else if (cchCopied == MAX_PATH - 1) | ||
55 | { | ||
56 | Log(hSession, L"Failed to get module path -- path is too long."); | ||
57 | return uiResult; | ||
58 | } | ||
59 | |||
60 | Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir); | ||
61 | int err = ExtractCabinet(szModule, g_szWorkingDir); | ||
62 | if (err != 0) | ||
63 | { | ||
64 | Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); | ||
65 | Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as " | ||
66 | L"any file contained in the embedded UI package."); | ||
67 | return uiResult; | ||
68 | } | ||
69 | |||
70 | wchar_t szConfigFilePath[MAX_PATH + 20]; | ||
71 | StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir); | ||
72 | StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config"); | ||
73 | |||
74 | const wchar_t* szConfigFile = szConfigFilePath; | ||
75 | if (!PathFileExists(szConfigFilePath)) | ||
76 | { | ||
77 | szConfigFile = NULL; | ||
78 | } | ||
79 | |||
80 | wchar_t szWIAssembly[MAX_PATH + 50]; | ||
81 | StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir); | ||
82 | StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); | ||
83 | |||
84 | if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost)) | ||
85 | { | ||
86 | if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir, | ||
87 | szConfigFile, &g_pAppDomain)) | ||
88 | { | ||
89 | const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; | ||
90 | const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy"; | ||
91 | const wchar_t* szInitMethod = L"Initialize"; | ||
92 | const wchar_t* szProcessMessageMethod = L"ProcessMessage"; | ||
93 | const wchar_t* szShutdownMethod = L"Shutdown"; | ||
94 | |||
95 | if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
96 | szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) && | ||
97 | GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
98 | szProxyClass, szShutdownMethod, &g_pShutdownMethod)) | ||
99 | { | ||
100 | _MethodInfo* pInitMethod; | ||
101 | if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
102 | szProxyClass, szInitMethod, &pInitMethod)) | ||
103 | { | ||
104 | bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult); | ||
105 | pInitMethod->Release(); | ||
106 | if (invokeSuccess) | ||
107 | { | ||
108 | if (uiResult == 0) | ||
109 | { | ||
110 | return ERROR_SUCCESS; | ||
111 | } | ||
112 | else if (uiResult == ERROR_INSTALL_USEREXIT) | ||
113 | { | ||
114 | // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT. | ||
115 | // So return success here and then IDCANCEL on the next progress message. | ||
116 | uiResult = 0; | ||
117 | *pdwInternalUILevel = INSTALLUILEVEL_NONE; | ||
118 | Log(hSession, L"Initialization canceled by user."); | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | |||
124 | g_pProcessMessageMethod->Release(); | ||
125 | g_pProcessMessageMethod = NULL; | ||
126 | g_pShutdownMethod->Release(); | ||
127 | g_pShutdownMethod = NULL; | ||
128 | |||
129 | g_pClrHost->UnloadDomain(g_pAppDomain); | ||
130 | g_pAppDomain->Release(); | ||
131 | g_pAppDomain = NULL; | ||
132 | } | ||
133 | g_pClrHost->Stop(); | ||
134 | g_pClrHost->Release(); | ||
135 | g_pClrHost = NULL; | ||
136 | } | ||
137 | |||
138 | return uiResult; | ||
139 | } | ||
140 | |||
141 | /// <summary> | ||
142 | /// Entry-point for UI progress messages received from the MSI engine during an active installation. | ||
143 | /// Forwards the progress messages to the managed handler method and returns its result. | ||
144 | /// </summary> | ||
145 | extern "C" | ||
146 | INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord) | ||
147 | { | ||
148 | if (g_pProcessMessageMethod == NULL) | ||
149 | { | ||
150 | // Initialization was canceled. | ||
151 | return IDCANCEL; | ||
152 | } | ||
153 | |||
154 | VARIANT vResult; | ||
155 | VariantInit(&vResult); | ||
156 | |||
157 | VARIANT vNull; | ||
158 | vNull.vt = VT_EMPTY; | ||
159 | |||
160 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2); | ||
161 | VARIANT vMessageType; | ||
162 | vMessageType.vt = VT_I4; | ||
163 | vMessageType.lVal = (LONG) uiMessageType; | ||
164 | LONG index = 0; | ||
165 | HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType); | ||
166 | if (FAILED(hr)) goto LExit; | ||
167 | VARIANT vRecord; | ||
168 | vRecord.vt = VT_I4; | ||
169 | vRecord.lVal = (LONG) hRecord; | ||
170 | index = 1; | ||
171 | hr = SafeArrayPutElement(saArgs, &index, &vRecord); | ||
172 | if (FAILED(hr)) goto LExit; | ||
173 | |||
174 | hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult); | ||
175 | |||
176 | LExit: | ||
177 | SafeArrayDestroy(saArgs); | ||
178 | if (SUCCEEDED(hr)) | ||
179 | { | ||
180 | return vResult.intVal; | ||
181 | } | ||
182 | else | ||
183 | { | ||
184 | return -1; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Entry-point for the UI shutdown message received from the MSI engine after installation has completed. | ||
190 | /// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR. | ||
191 | /// </summary> | ||
192 | extern "C" | ||
193 | DWORD __stdcall ShutdownEmbeddedUI() | ||
194 | { | ||
195 | if (g_pShutdownMethod != NULL) | ||
196 | { | ||
197 | VARIANT vNull; | ||
198 | vNull.vt = VT_EMPTY; | ||
199 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0); | ||
200 | g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL); | ||
201 | SafeArrayDestroy(saArgs); | ||
202 | |||
203 | g_pClrHost->UnloadDomain(g_pAppDomain); | ||
204 | g_pAppDomain->Release(); | ||
205 | g_pClrHost->Stop(); | ||
206 | g_pClrHost->Release(); | ||
207 | } | ||
208 | |||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | /// <summary> | ||
213 | /// Loads and invokes the managed portion of the proxy. | ||
214 | /// </summary> | ||
215 | /// <param name="pInitMethod">Managed initialize method to be invoked.</param> | ||
216 | /// <param name="hSession">Handle to the installer session, | ||
217 | /// used for logging errors and to be passed on to the managed initialize method.</param> | ||
218 | /// <param name="szClassName">Name of the UI class to be loaded. | ||
219 | /// This must be of the form: AssemblyName!Namespace.Class</param> | ||
220 | /// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from | ||
221 | /// the managed initialize method.</param> | ||
222 | /// <param name="puiResult">Return value of the invoked initialize method.</param> | ||
223 | /// <returns>True if the managed proxy was invoked successfully, or an | ||
224 | /// error code if there was some error. Note the initialize method itself may | ||
225 | /// return an error via puiResult while this method still returns true | ||
226 | /// since the invocation was successful.</returns> | ||
227 | bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult) | ||
228 | { | ||
229 | VARIANT vResult; | ||
230 | VariantInit(&vResult); | ||
231 | |||
232 | VARIANT vNull; | ||
233 | vNull.vt = VT_EMPTY; | ||
234 | |||
235 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); | ||
236 | VARIANT vSessionHandle; | ||
237 | vSessionHandle.vt = VT_I4; | ||
238 | vSessionHandle.lVal = (LONG) hSession; | ||
239 | LONG index = 0; | ||
240 | HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); | ||
241 | if (FAILED(hr)) goto LExit; | ||
242 | VARIANT vEntryPoint; | ||
243 | vEntryPoint.vt = VT_BSTR; | ||
244 | vEntryPoint.bstrVal = SysAllocString(szClassName); | ||
245 | if (vEntryPoint.bstrVal == NULL) | ||
246 | { | ||
247 | hr = E_OUTOFMEMORY; | ||
248 | goto LExit; | ||
249 | } | ||
250 | index = 1; | ||
251 | hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); | ||
252 | if (FAILED(hr)) goto LExit; | ||
253 | VARIANT vUILevel; | ||
254 | vUILevel.vt = VT_I4; | ||
255 | vUILevel.ulVal = *pdwInternalUILevel; | ||
256 | index = 2; | ||
257 | hr = SafeArrayPutElement(saArgs, &index, &vUILevel); | ||
258 | if (FAILED(hr)) goto LExit; | ||
259 | |||
260 | hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult); | ||
261 | |||
262 | LExit: | ||
263 | SafeArrayDestroy(saArgs); | ||
264 | if (SUCCEEDED(hr)) | ||
265 | { | ||
266 | *puiResult = (UINT) vResult.lVal; | ||
267 | if ((*puiResult & 0xFFFF) == 0) | ||
268 | { | ||
269 | // Due to interop limitations, the successful resulting UILevel is returned | ||
270 | // as the high-word of the return value instead of via a ref parameter. | ||
271 | *pdwInternalUILevel = *puiResult >> 16; | ||
272 | *puiResult = 0; | ||
273 | } | ||
274 | return true; | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr); | ||
279 | return false; | ||
280 | } | ||
281 | } | ||