summaryrefslogtreecommitdiff
path: root/src/dtf/SfxCA/EmbeddedUI.cpp
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-03-31 11:56:14 -0700
committerRob Mensching <rob@firegiant.com>2022-03-31 18:01:06 -0700
commit47582b162368e8edf7a3b11c13b8e9dabc5f0a26 (patch)
tree2c4063eff325684bed39de0edacd7866a257ae02 /src/dtf/SfxCA/EmbeddedUI.cpp
parent167296c42497c4e95f0d5d71168542d747655981 (diff)
downloadwix-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.cpp281
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.
7static const wchar_t* g_szWorkingDir;
8static ICorRuntimeHost* g_pClrHost;
9static _AppDomain* g_pAppDomain;
10static _MethodInfo* g_pProcessMessageMethod;
11static _MethodInfo* g_pShutdownMethod;
12
13// Reserve extra space for strings to be replaced at build time.
14#define NULLSPACE \
15L"\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" \
16L"\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" \
17L"\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" \
18L"\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
23bool 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>
37extern "C"
38UINT __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>
145extern "C"
146INT __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
176LExit:
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>
192extern "C"
193DWORD __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>
227bool 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
262LExit:
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}