| 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 | #define GUID_STRING_LENGTH 39 |
| 7 | |
| 8 | /// <summary> |
| 9 | /// Writes a formatted message to the MSI log. |
| 10 | /// Does out-of-proc MSI calls if necessary. |
| 11 | /// </summary> |
| 12 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) |
| 13 | { |
| 14 | const int LOG_BUFSIZE = 4096; |
| 15 | wchar_t szBuf[LOG_BUFSIZE]; |
| 16 | va_list args; |
| 17 | va_start(args, szMessage); |
| 18 | StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); |
| 19 | |
| 20 | if (!g_fRunningOutOfProc || NULL == g_pRemote) |
| 21 | { |
| 22 | MSIHANDLE hRec = MsiCreateRecord(1); |
| 23 | MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); |
| 24 | MsiRecordSetString(hRec, 1, szBuf); |
| 25 | MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); |
| 26 | MsiCloseHandle(hRec); |
| 27 | } |
| 28 | else |
| 29 | { |
| 30 | // Logging is the only remote-MSI operation done from unmanaged code. |
| 31 | // It's not very convenient here because part of the infrastructure |
| 32 | // for remote MSI APIs is on the managed side. |
| 33 | |
| 34 | RemoteMsiSession::RequestData req; |
| 35 | RemoteMsiSession::RequestData* pResp = NULL; |
| 36 | SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); |
| 37 | |
| 38 | req.fields[0].vt = VT_UI4; |
| 39 | req.fields[0].uiValue = 1; |
| 40 | g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); |
| 41 | MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; |
| 42 | |
| 43 | req.fields[0].vt = VT_I4; |
| 44 | req.fields[0].iValue = (int) hRec; |
| 45 | req.fields[1].vt = VT_UI4; |
| 46 | req.fields[1].uiValue = 0; |
| 47 | req.fields[2].vt = VT_LPWSTR; |
| 48 | req.fields[2].szValue = L"SFXCA: [1]"; |
| 49 | g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); |
| 50 | |
| 51 | req.fields[0].vt = VT_I4; |
| 52 | req.fields[0].iValue = (int) hRec; |
| 53 | req.fields[1].vt = VT_UI4; |
| 54 | req.fields[1].uiValue = 1; |
| 55 | req.fields[2].vt = VT_LPWSTR; |
| 56 | req.fields[2].szValue = szBuf; |
| 57 | g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); |
| 58 | |
| 59 | req.fields[0].vt = VT_I4; |
| 60 | req.fields[0].iValue = (int) hSession; |
| 61 | req.fields[1].vt = VT_I4; |
| 62 | req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; |
| 63 | req.fields[2].vt = VT_I4; |
| 64 | req.fields[2].iValue = (int) hRec; |
| 65 | g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); |
| 66 | |
| 67 | req.fields[0].vt = VT_I4; |
| 68 | req.fields[0].iValue = (int) hRec; |
| 69 | req.fields[1].vt = VT_EMPTY; |
| 70 | req.fields[2].vt = VT_EMPTY; |
| 71 | g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /// <summary> |
| 76 | /// Deletes a directory, including all files and subdirectories. |
| 77 | /// </summary> |
| 78 | /// <param name="szDir">Path to the directory to delete, |
| 79 | /// not including a trailing backslash.</param> |
| 80 | /// <returns>True if the directory was successfully deleted, or false |
| 81 | /// if the deletion failed (most likely because some files were locked). |
| 82 | /// </returns> |
| 83 | bool DeleteDirectory(const wchar_t* szDir) |
| 84 | { |
| 85 | size_t cchDir = wcslen(szDir); |
| 86 | size_t cchPathBuf = cchDir + 3 + MAX_PATH; |
| 87 | wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); |
| 88 | if (szPath == NULL) return false; |
| 89 | StringCchCopy(szPath, cchPathBuf, szDir); |
| 90 | StringCchCat(szPath, cchPathBuf, L"\\*"); |
| 91 | WIN32_FIND_DATA fd; |
| 92 | HANDLE hSearch = FindFirstFile(szPath, &fd); |
| 93 | while (hSearch != INVALID_HANDLE_VALUE) |
| 94 | { |
| 95 | StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); |
| 96 | if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) |
| 97 | { |
| 98 | if (wcscmp(fd.cFileName, L".") != 0 |
| 99 | && wcscmp(fd.cFileName, L"..") != 0 |
| 100 | && ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) |
| 101 | { |
| 102 | DeleteDirectory(szPath); |
| 103 | } |
| 104 | } |
| 105 | else |
| 106 | { |
| 107 | DeleteFile(szPath); |
| 108 | } |
| 109 | if (!FindNextFile(hSearch, &fd)) |
| 110 | { |
| 111 | FindClose(hSearch); |
| 112 | hSearch = INVALID_HANDLE_VALUE; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | for (int i = 0; i < 3; i++) |
| 117 | { |
| 118 | if (::RemoveDirectory(szDir)) |
| 119 | { |
| 120 | return true; |
| 121 | } |
| 122 | |
| 123 | ::Sleep(100); |
| 124 | } |
| 125 | |
| 126 | return false; |
| 127 | } |
| 128 | |
| 129 | static HRESULT CreateGuid( |
| 130 | _Out_z_cap_c_(GUID_STRING_LENGTH) wchar_t* wzGuid) |
| 131 | { |
| 132 | HRESULT hr = S_OK; |
| 133 | RPC_STATUS rs = RPC_S_OK; |
| 134 | UUID guid = {}; |
| 135 | |
| 136 | rs = ::UuidCreate(&guid); |
| 137 | if (rs != RPC_S_OK) |
| 138 | { |
| 139 | hr = (HRESULT)(rs | FACILITY_RPC); |
| 140 | } |
| 141 | else if (!::StringFromGUID2(guid, wzGuid, GUID_STRING_LENGTH)) |
| 142 | { |
| 143 | hr = E_OUTOFMEMORY; |
| 144 | } |
| 145 | else // make the temp directory more recognizable for easy deletion. |
| 146 | { |
| 147 | // Copy the first four hex chars of the GUID over the dashes in the GUID and trim the, so |
| 148 | // '{1234ABCD-ABCD-ABCD-ABCD-ABCDABCDABCD}' turns into '{1234ABCD1ABCD2ABCD3ABCD4ABCDABCDABCD}' |
| 149 | wzGuid[9] = wzGuid[1]; |
| 150 | wzGuid[14] = wzGuid[2]; |
| 151 | wzGuid[19] = wzGuid[3]; |
| 152 | wzGuid[24] = wzGuid[4]; |
| 153 | |
| 154 | // Now '{1234ABCD1ABCD2ABCD3ABCD4ABCDABCDABCD}' turns into 'SFXCAABCD1ABCD2ABCD3ABCD4ABCDABCDABCD' |
| 155 | wzGuid[0] = L'S'; |
| 156 | wzGuid[1] = L'F'; |
| 157 | wzGuid[2] = L'X'; |
| 158 | wzGuid[3] = L'C'; |
| 159 | wzGuid[4] = L'A'; |
| 160 | wzGuid[GUID_STRING_LENGTH - 2] = L'\0'; |
| 161 | } |
| 162 | |
| 163 | return hr; |
| 164 | } |
| 165 | |
| 166 | static HRESULT ProcessElevated() |
| 167 | { |
| 168 | HRESULT hr = S_OK; |
| 169 | HANDLE hToken = NULL; |
| 170 | TOKEN_ELEVATION tokenElevated = {}; |
| 171 | DWORD cbToken = 0; |
| 172 | |
| 173 | if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken) && |
| 174 | ::GetTokenInformation(hToken, TokenElevation, &tokenElevated, sizeof(TOKEN_ELEVATION), &cbToken)) |
| 175 | { |
| 176 | hr = (0 != tokenElevated.TokenIsElevated) ? S_OK : S_FALSE; |
| 177 | } |
| 178 | else |
| 179 | { |
| 180 | hr = HRESULT_FROM_WIN32(::GetLastError()); |
| 181 | } |
| 182 | |
| 183 | return hr; |
| 184 | } |
| 185 | |
| 186 | /// <summary> |
| 187 | /// Extracts a cabinet that is concatenated to a module |
| 188 | /// to a new temporary directory. |
| 189 | /// </summary> |
| 190 | /// <param name="hSession">Handle to the installer session, |
| 191 | /// used just for logging.</param> |
| 192 | /// <param name="hModule">Module that has the concatenated cabinet.</param> |
| 193 | /// <param name="szTempDir">Buffer for returning the path of the |
| 194 | /// created temp directory.</param> |
| 195 | /// <param name="cchTempDirBuf">Size in characters of the buffer. |
| 196 | /// <returns>True if the files were extracted, or false if the |
| 197 | /// buffer was too small or the directory could not be created |
| 198 | /// or the extraction failed for some other reason.</returns> |
| 199 | __success(return != false) |
| 200 | bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, |
| 201 | __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) |
| 202 | { |
| 203 | HRESULT hr = S_OK; |
| 204 | wchar_t szModule[MAX_PATH] = {}; |
| 205 | wchar_t szGuid[GUID_STRING_LENGTH] = {}; |
| 206 | |
| 207 | DWORD cchCopied = ::GetModuleFileName(hModule, szModule, MAX_PATH - 1); |
| 208 | if (cchCopied == 0 || cchCopied == MAX_PATH - 1) |
| 209 | { |
| 210 | hr = HRESULT_FROM_WIN32(::GetLastError()); |
| 211 | if (SUCCEEDED(hr)) |
| 212 | { |
| 213 | hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); |
| 214 | } |
| 215 | |
| 216 | Log(hSession, L"Failed to get module path. Error code 0x%x.", hr); |
| 217 | goto LExit; |
| 218 | } |
| 219 | |
| 220 | hr = CreateGuid(szGuid); |
| 221 | if (FAILED(hr)) |
| 222 | { |
| 223 | Log(hSession, L"Failed to create a GUID. Error code 0x%x", hr); |
| 224 | goto LExit; |
| 225 | } |
| 226 | |
| 227 | // Unelevated we use the user's temp directory. |
| 228 | hr = ProcessElevated(); |
| 229 | if (S_FALSE == hr) |
| 230 | { |
| 231 | // Temp path is documented to be returned with a trailing backslash. |
| 232 | cchCopied = ::GetTempPath(cchTempDirBuf, szTempDir); |
| 233 | if (cchCopied == 0 || cchCopied >= cchTempDirBuf) |
| 234 | { |
| 235 | hr = HRESULT_FROM_WIN32(::GetLastError()); |
| 236 | if (SUCCEEDED(hr)) |
| 237 | { |
| 238 | hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); |
| 239 | } |
| 240 | |
| 241 | Log(hSession, L"Failed to get user temp directory. Error code 0x%x", hr); |
| 242 | goto LExit; |
| 243 | } |
| 244 | } |
| 245 | else // elevated or we couldn't check (in the latter case, assume we're elevated since it's safer to use) |
| 246 | { |
| 247 | // Windows directory will not contain a trailing backslash, so we add it next. |
| 248 | cchCopied = ::GetWindowsDirectoryW(szTempDir, cchTempDirBuf); |
| 249 | if (cchCopied == 0 || cchCopied >= cchTempDirBuf) |
| 250 | { |
| 251 | hr = HRESULT_FROM_WIN32(::GetLastError()); |
| 252 | if (SUCCEEDED(hr)) |
| 253 | { |
| 254 | hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); |
| 255 | } |
| 256 | |
| 257 | Log(hSession, L"Failed to get Windows directory. Error code 0x%x", hr); |
| 258 | goto LExit; |
| 259 | } |
| 260 | |
| 261 | hr = ::StringCchCat(szTempDir, cchTempDirBuf, L"\\Installer\\"); |
| 262 | if (FAILED(hr)) |
| 263 | { |
| 264 | Log(hSession, L"Failed append 'Installer' to Windows directory. Error code 0x%x", hr); |
| 265 | goto LExit; |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | hr = ::StringCchCat(szTempDir, cchTempDirBuf, szGuid); |
| 270 | if (FAILED(hr)) |
| 271 | { |
| 272 | Log(hSession, L"Failed append GUID to temp path. Error code 0x%x", hr); |
| 273 | goto LExit; |
| 274 | } |
| 275 | |
| 276 | if (!::CreateDirectory(szTempDir, NULL)) |
| 277 | { |
| 278 | hr = HRESULT_FROM_WIN32(::GetLastError()); |
| 279 | Log(hSession, L"Failed to create temp directory. Error code 0x%x", hr); |
| 280 | goto LExit; |
| 281 | } |
| 282 | |
| 283 | Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); |
| 284 | int err = ExtractCabinet(szModule, szTempDir); |
| 285 | if (err != 0) |
| 286 | { |
| 287 | hr = E_FAIL; |
| 288 | Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); |
| 289 | DeleteDirectory(szTempDir); |
| 290 | } |
| 291 | |
| 292 | LExit: |
| 293 | return SUCCEEDED(hr); |
| 294 | } |