// 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 "EntryPoints.h" #include "SfxUtil.h" #define MANAGED_CAs_OUT_OF_PROC 1 HMODULE g_hModule; bool g_fRunningOutOfProc = false; RemoteMsiSession* g_pRemote = NULL; // Prototypes for local functions. // See the function definitions for comments. bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); /// /// Entry-point for the CA DLL when re-launched as a separate process; /// connects the comm channel for remote MSI APIs, then invokes the /// managed custom action entry-point. /// /// /// Do not change the parameters or calling-convention: RUNDLL32 /// requires this exact signature. /// extern "C" void __stdcall InvokeManagedCustomActionOutOfProc( __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hwnd); UNREFERENCED_PARAMETER(hinst); UNREFERENCED_PARAMETER(nCmdShow); g_fRunningOutOfProc = true; const wchar_t* szSessionName = szCmdLine; MSIHANDLE hSession; const wchar_t* szEntryPoint; int i; for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; hSession = _wtoi(szCmdLine + i); for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; szEntryPoint = szCmdLine + i; g_pRemote = new RemoteMsiSession(szSessionName, false); g_pRemote->Connect(); int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); RemoteMsiSession::RequestData requestData; SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); requestData.fields[0].vt = VT_I4; requestData.fields[0].iValue = ret; g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); delete g_pRemote; } /// /// Re-launch this CA DLL as a separate process, and setup a comm channel /// for remote MSI API calls back to this process. /// int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) { wchar_t szSessionName[100] = {0}; swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); RemoteMsiSession remote(szSessionName, true); DWORD ret = remote.Connect(); if (ret != 0) { Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); return ERROR_INSTALL_FAILURE; } ret = remote.ProcessRequests(); if (ret != 0) { Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); return ERROR_INSTALL_FAILURE; } wchar_t szModule[MAX_PATH] = {0}; GetModuleFileName(g_hModule, szModule, MAX_PATH); const wchar_t* rundll32 = L"rundll32.exe"; wchar_t szRunDll32Path[MAX_PATH] = {0}; GetSystemDirectory(szRunDll32Path, MAX_PATH); wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); wcscat_s(szRunDll32Path, MAX_PATH, rundll32); const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; wchar_t szCommandLine[1024] = {0}; swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); STARTUPINFO si; SecureZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); PROCESS_INFORMATION pi; SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { DWORD err = GetLastError(); Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); return ERROR_INSTALL_FAILURE; } DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); if (dwWait != WAIT_OBJECT_0) { DWORD err = GetLastError(); Log(hSession, L"Failed to wait for CA process. Error code: %d", err); return ERROR_INSTALL_FAILURE; } DWORD dwExitCode; BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); if (!bRet) { DWORD err = GetLastError(); Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); return ERROR_INSTALL_FAILURE; } else if (dwExitCode != 0) { Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); return ERROR_INSTALL_FAILURE; } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); remote.WaitExitCode(); return remote.ExitCode; } /// /// Entrypoint for the managed CA proxy (RemotableNativeMethods) to /// call MSI APIs remotely. /// void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) { if (g_fRunningOutOfProc) { g_pRemote->SendRequest(id, pRequest, ppResponse); } else { *ppResponse = NULL; } } /// /// Invokes a managed custom action from native code by /// extracting the package to a temporary working directory /// then hosting the CLR and locating and calling the entrypoint. /// /// Handle to the installation session. /// Passed to custom action entrypoints by the installer engine. /// Directory containing the CA binaries /// and the CustomAction.config file defining the entrypoints. /// This may be NULL, in which case the current module must have /// a concatenated cabinet containing those files, which will be /// extracted to a temporary directory. /// Name of the CA entrypoint to be invoked. /// This must be either an explicit "AssemblyName!Namespace.Class.Method" /// string, or a simple name that maps to a full entrypoint definition /// in CustomAction.config. /// The value returned by the managed custom action method, /// or ERROR_INSTALL_FAILURE if the CA could not be invoked. int InvokeCustomAction(MSIHANDLE hSession, const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) { #ifdef MANAGED_CAs_OUT_OF_PROC if (!g_fRunningOutOfProc && szWorkingDir == NULL) { return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); } #endif wchar_t szTempDir[MAX_PATH]; bool fDeleteTemp = false; if (szWorkingDir == NULL) { if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) { return ERROR_INSTALL_FAILURE; } szWorkingDir = szTempDir; fDeleteTemp = true; } wchar_t szConfigFilePath[MAX_PATH + 20]; StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); const wchar_t* szConfigFile = szConfigFilePath; if (!::PathFileExists(szConfigFilePath)) { szConfigFile = NULL; } wchar_t szWIAssembly[MAX_PATH + 50]; StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); int iResult = ERROR_INSTALL_FAILURE; ICorRuntimeHost* pHost; if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) { _AppDomain* pAppDomain; if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, szConfigFile, &pAppDomain)) { if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) { iResult = ERROR_INSTALL_FAILURE; } HRESULT hr = pHost->UnloadDomain(pAppDomain); if (FAILED(hr)) { Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); } pAppDomain->Release(); } pHost->Stop(); pHost->Release(); } if (fDeleteTemp) { DeleteDirectory(szTempDir); } return iResult; } /// /// Called by the system when the DLL is loaded. /// Saves the module handle for later use. /// BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) { UNREFERENCED_PARAMETER(pReserved); switch (dwReason) { case DLL_PROCESS_ATTACH: g_hModule = hModule; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } /// /// Loads and invokes the managed portion of the proxy. /// /// Handle to the installer session, /// used for logging errors and to be passed on to the custom action. /// AppDomain which has its application /// base set to the CA working directory. /// Name of the CA entrypoint to be invoked. /// This must be either an explicit "AssemblyName!Namespace.Class.Method" /// string, or a simple name that maps to a full entrypoint definition /// in CustomAction.config. /// Return value of the invoked custom /// action method. /// True if the managed proxy was invoked successfully, /// false if there was some error. Note the custom action itself may /// return an error via piResult while this method still returns true /// since the invocation was successful. bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult) { VARIANT vResult; ::VariantInit(&vResult); const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); _MethodInfo* pCAInvokeMethod; if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) { return false; } HRESULT hr; VARIANT vNull; vNull.vt = VT_EMPTY; SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); VARIANT vSessionHandle; vSessionHandle.vt = VT_I4; vSessionHandle.intVal = hSession; LONG index = 0; hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); if (FAILED(hr)) goto LExit; VARIANT vEntryPoint; vEntryPoint.vt = VT_BSTR; vEntryPoint.bstrVal = SysAllocString(szEntryPoint); if (vEntryPoint.bstrVal == NULL) { hr = E_OUTOFMEMORY; goto LExit; } index = 1; hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); if (FAILED(hr)) goto LExit; VARIANT vRemotingFunctionPtr; #pragma warning(push) #pragma warning(disable:4127) // conditional expression is constant if (f64bit) #pragma warning(pop) { vRemotingFunctionPtr.vt = VT_I8; vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); } else { vRemotingFunctionPtr.vt = VT_I4; #pragma warning(push) #pragma warning(disable:4302) // truncation #pragma warning(disable:4311) // pointer truncation vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); #pragma warning(pop) } index = 2; hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); if (FAILED(hr)) goto LExit; hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); LExit: SafeArrayDestroy(saArgs); pCAInvokeMethod->Release(); if (FAILED(hr)) { Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); return false; } *piResult = vResult.intVal; return true; }