// 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);
///
/// 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.
///
/// Handle to the installer session,
/// used for logging errors and to be passed on to the managed initialize method.
/// Path the directory where resources from the MsiEmbeddedUI table
/// have been extracted, and where additional payload from this package will be extracted.
/// MSI install UI level passed to and returned from
/// the managed initialize method.
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;
}
///
/// 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.
///
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;
}
}
///
/// 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.
///
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;
}
///
/// Loads and invokes the managed portion of the proxy.
///
/// Managed initialize method to be invoked.
/// Handle to the installer session,
/// used for logging errors and to be passed on to the managed initialize method.
/// Name of the UI class to be loaded.
/// This must be of the form: AssemblyName!Namespace.Class
/// MSI install UI level passed to and returned from
/// the managed initialize method.
/// Return value of the invoked initialize method.
/// 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.
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;
}
}