main
cpp 294 lines 10.9 KB
Raw
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 }