// 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" #define GUID_STRING_LENGTH 39 /// /// Writes a formatted message to the MSI log. /// Does out-of-proc MSI calls if necessary. /// void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) { const int LOG_BUFSIZE = 4096; wchar_t szBuf[LOG_BUFSIZE]; va_list args; va_start(args, szMessage); StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); if (!g_fRunningOutOfProc || NULL == g_pRemote) { MSIHANDLE hRec = MsiCreateRecord(1); MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); MsiRecordSetString(hRec, 1, szBuf); MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); MsiCloseHandle(hRec); } else { // Logging is the only remote-MSI operation done from unmanaged code. // It's not very convenient here because part of the infrastructure // for remote MSI APIs is on the managed side. RemoteMsiSession::RequestData req; RemoteMsiSession::RequestData* pResp = NULL; SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); req.fields[0].vt = VT_UI4; req.fields[0].uiValue = 1; g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; req.fields[0].vt = VT_I4; req.fields[0].iValue = (int) hRec; req.fields[1].vt = VT_UI4; req.fields[1].uiValue = 0; req.fields[2].vt = VT_LPWSTR; req.fields[2].szValue = L"SFXCA: [1]"; g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); req.fields[0].vt = VT_I4; req.fields[0].iValue = (int) hRec; req.fields[1].vt = VT_UI4; req.fields[1].uiValue = 1; req.fields[2].vt = VT_LPWSTR; req.fields[2].szValue = szBuf; g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); req.fields[0].vt = VT_I4; req.fields[0].iValue = (int) hSession; req.fields[1].vt = VT_I4; req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; req.fields[2].vt = VT_I4; req.fields[2].iValue = (int) hRec; g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); req.fields[0].vt = VT_I4; req.fields[0].iValue = (int) hRec; req.fields[1].vt = VT_EMPTY; req.fields[2].vt = VT_EMPTY; g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); } } /// /// Deletes a directory, including all files and subdirectories. /// /// Path to the directory to delete, /// not including a trailing backslash. /// True if the directory was successfully deleted, or false /// if the deletion failed (most likely because some files were locked). /// bool DeleteDirectory(const wchar_t* szDir) { size_t cchDir = wcslen(szDir); size_t cchPathBuf = cchDir + 3 + MAX_PATH; wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); if (szPath == NULL) return false; StringCchCopy(szPath, cchPathBuf, szDir); StringCchCat(szPath, cchPathBuf, L"\\*"); WIN32_FIND_DATA fd; HANDLE hSearch = FindFirstFile(szPath, &fd); while (hSearch != INVALID_HANDLE_VALUE) { StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0 && ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) { DeleteDirectory(szPath); } } else { DeleteFile(szPath); } if (!FindNextFile(hSearch, &fd)) { FindClose(hSearch); hSearch = INVALID_HANDLE_VALUE; } } for (int i = 0; i < 3; i++) { if (::RemoveDirectory(szDir)) { return true; } ::Sleep(100); } return false; } static HRESULT CreateGuid( _Out_z_cap_c_(GUID_STRING_LENGTH) wchar_t* wzGuid) { HRESULT hr = S_OK; RPC_STATUS rs = RPC_S_OK; UUID guid = {}; rs = ::UuidCreate(&guid); if (rs != RPC_S_OK) { hr = (HRESULT)(rs | FACILITY_RPC); } else if (!::StringFromGUID2(guid, wzGuid, GUID_STRING_LENGTH)) { hr = E_OUTOFMEMORY; } else // make the temp directory more recognizable for easy deletion. { // Copy the first four hex chars of the GUID over the dashes in the GUID and trim the, so // '{1234ABCD-ABCD-ABCD-ABCD-ABCDABCDABCD}' turns into '{1234ABCD1ABCD2ABCD3ABCD4ABCDABCDABCD}' wzGuid[9] = wzGuid[1]; wzGuid[14] = wzGuid[2]; wzGuid[19] = wzGuid[3]; wzGuid[24] = wzGuid[4]; // Now '{1234ABCD1ABCD2ABCD3ABCD4ABCDABCDABCD}' turns into 'SFXCAABCD1ABCD2ABCD3ABCD4ABCDABCDABCD' wzGuid[0] = L'S'; wzGuid[1] = L'F'; wzGuid[2] = L'X'; wzGuid[3] = L'C'; wzGuid[4] = L'A'; wzGuid[GUID_STRING_LENGTH - 2] = L'\0'; } return hr; } static HRESULT ProcessElevated() { HRESULT hr = S_OK; HANDLE hToken = NULL; TOKEN_ELEVATION tokenElevated = {}; DWORD cbToken = 0; if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken) && ::GetTokenInformation(hToken, TokenElevation, &tokenElevated, sizeof(TOKEN_ELEVATION), &cbToken)) { hr = (0 != tokenElevated.TokenIsElevated) ? S_OK : S_FALSE; } else { hr = HRESULT_FROM_WIN32(::GetLastError()); } return hr; } /// /// Extracts a cabinet that is concatenated to a module /// to a new temporary directory. /// /// Handle to the installer session, /// used just for logging. /// Module that has the concatenated cabinet. /// Buffer for returning the path of the /// created temp directory. /// Size in characters of the buffer. /// True if the files were extracted, or false if the /// buffer was too small or the directory could not be created /// or the extraction failed for some other reason. __success(return != false) bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) { HRESULT hr = S_OK; wchar_t szModule[MAX_PATH] = {}; wchar_t szGuid[GUID_STRING_LENGTH] = {}; DWORD cchCopied = ::GetModuleFileName(hModule, szModule, MAX_PATH - 1); if (cchCopied == 0 || cchCopied == MAX_PATH - 1) { hr = HRESULT_FROM_WIN32(::GetLastError()); if (SUCCEEDED(hr)) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } Log(hSession, L"Failed to get module path. Error code 0x%x.", hr); goto LExit; } hr = CreateGuid(szGuid); if (FAILED(hr)) { Log(hSession, L"Failed to create a GUID. Error code 0x%x", hr); goto LExit; } // Unelevated we use the user's temp directory. hr = ProcessElevated(); if (S_FALSE == hr) { // Temp path is documented to be returned with a trailing backslash. cchCopied = ::GetTempPath(cchTempDirBuf, szTempDir); if (cchCopied == 0 || cchCopied >= cchTempDirBuf) { hr = HRESULT_FROM_WIN32(::GetLastError()); if (SUCCEEDED(hr)) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } Log(hSession, L"Failed to get user temp directory. Error code 0x%x", hr); goto LExit; } } else // elevated or we couldn't check (in the latter case, assume we're elevated since it's safer to use) { // Windows directory will not contain a trailing backslash, so we add it next. cchCopied = ::GetWindowsDirectoryW(szTempDir, cchTempDirBuf); if (cchCopied == 0 || cchCopied >= cchTempDirBuf) { hr = HRESULT_FROM_WIN32(::GetLastError()); if (SUCCEEDED(hr)) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } Log(hSession, L"Failed to get Windows directory. Error code 0x%x", hr); goto LExit; } hr = ::StringCchCat(szTempDir, cchTempDirBuf, L"\\Installer\\"); if (FAILED(hr)) { Log(hSession, L"Failed append 'Installer' to Windows directory. Error code 0x%x", hr); goto LExit; } } hr = ::StringCchCat(szTempDir, cchTempDirBuf, szGuid); if (FAILED(hr)) { Log(hSession, L"Failed append GUID to temp path. Error code 0x%x", hr); goto LExit; } if (!::CreateDirectory(szTempDir, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); Log(hSession, L"Failed to create temp directory. Error code 0x%x", hr); goto LExit; } Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); int err = ExtractCabinet(szModule, szTempDir); if (err != 0) { hr = E_FAIL; Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); DeleteDirectory(szTempDir); } LExit: return SUCCEEDED(hr); }