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