@joebigelow / wix-1 / commits / 681cf4a9

Ensure elevated SFXCA uses Windows Installer cache and unelevated uses Temp folder

Fixes 8078

Rob Mensching committed Apr 4, 2024 at 15:24 UTC 681cf4a9eb6be7e4092c6e5b690773fbd8469e63
5 files changed +203 -35
src/dtf/SfxCA/SfxCA.vcxproj
+1 -1
@@ -41,7 +41,7 @@
41 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
42
43 <PropertyGroup>
44 - <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
44 + <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;rpcrt4.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
45 </PropertyGroup>
46
47 <ItemGroup>
src/dtf/SfxCA/SfxUtil.cpp
+136 -33
@@ -3,6 +3,8 @@
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.
@@ -110,20 +112,75 @@ bool DeleteDirectory(const wchar_t* szDir)
112 hSearch = INVALID_HANDLE_VALUE;
113 }
114 }
113 - return RemoveDirectory(szDir) != 0;
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
116 -bool DirectoryExists(const wchar_t* szDir)
129 +static HRESULT CreateGuid(
130 + _Out_z_cap_c_(GUID_STRING_LENGTH) wchar_t* wzGuid)
131 {
118 - if (szDir != NULL)
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 {
120 - DWORD dwAttrs = GetFileAttributes(szDir);
121 - if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
122 - {
123 - return true;
124 - }
139 + hr = (HRESULT)(rs | FACILITY_RPC);
140 }
126 - return false;
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>
@@ -143,49 +200,95 @@ __success(return != false)
200 bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
201 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf)
202 {
146 - wchar_t szModule[MAX_PATH];
147 - DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1);
148 - if (cchCopied == 0)
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 {
150 - Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
151 - return false;
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 }
153 - else if (cchCopied == MAX_PATH - 1)
219 +
220 + hr = CreateGuid(szGuid);
221 + if (FAILED(hr))
222 {
155 - Log(hSession, L"Failed to get module path -- path is too long.");
156 - return false;
223 + Log(hSession, L"Failed to create a GUID. Error code 0x%x", hr);
224 + goto LExit;
225 }
226
159 - if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1)
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 {
161 - Log(hSession, L"Temp directory buffer is NULL or too small.");
162 - return false;
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 }
164 - StringCchCopy(szTempDir, cchTempDirBuf, szModule);
165 - StringCchCat(szTempDir, cchTempDirBuf, L"-");
268
167 - BOOL fCreatedDirectory = FALSE;
168 - DWORD cchTempDir = (DWORD) wcslen(szTempDir);
169 - for (int i = 0; i < 10000 && !fCreatedDirectory; i++)
269 + hr = ::StringCchCat(szTempDir, cchTempDirBuf, szGuid);
270 + if (FAILED(hr))
271 {
171 - swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
172 - fCreatedDirectory = ::CreateDirectory(szTempDir, NULL);
272 + Log(hSession, L"Failed append GUID to temp path. Error code 0x%x", hr);
273 + goto LExit;
274 }
275
175 - if (!fCreatedDirectory)
276 + if (!::CreateDirectory(szTempDir, NULL))
277 {
177 - Log(hSession, L"Failed to create temp directory. Error code %d", ::GetLastError());
178 - return false;
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);
187 - return false;
290 }
189 - return true;
190 -}
291
292 +LExit:
293 + return SUCCEEDED(hr);
294 +}
src/dtf/dtf.cmd
+23
@@ -4,13 +4,36 @@
4 @set _C=Debug
5 :parse_args
6 @if /i "%1"=="release" set _C=Release
7 +@if /i "%1"=="inc" set _INC=1
8 +@if /i "%1"=="clean" set _CLEAN=1
9 @if not "%1"=="" shift & goto parse_args
10
11 +:: Clean
12 +
13 +@if "%_INC%"=="" call :clean
14 +@if NOT "%_CLEAN%"=="" goto :end
15 +
16 @echo Building dtf %_C%
17
18 msbuild -Restore SfxCA\sfxca_t.proj -p:Configuration=%_C% -tl -nologo -m -warnaserror -bl:..\..\build\logs\dtf_sfxca.binlog || exit /b
19
20 msbuild -Restore -t:Pack dtf.sln -p:Configuration=%_C% -tl -nologo -m -warnaserror -bl:..\..\build\logs\dtf_build.binlog || exit /b
21
22 +@goto :end
23 +
24 +:clean
25 +@rd /s/q "..\..\build\dtf" 2> nul
26 +@del "..\..\build\artifacts\WixToolset.Dtf.*.nupkg" 2> nul
27 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.compression" 2> nul
28 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.compression.cab" 2> nul
29 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.compression.zip" 2> nul
30 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.customaction" 2> nul
31 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.resources" 2> nul
32 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.windowsinstaller" 2> nul
33 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.windowsinstaller.linq" 2> nul
34 +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.windowsinstaller.package" 2> nul
35 +@exit /b
36 +
37 +:end
38 @popd
39 @endlocal
src/test/msi/TestData/CustomActionTests/ManagedCustomActions/Package.wxs
+5 -1
@@ -13,13 +13,17 @@
13 <Binary Id="ManagedCA" SourceFile="TestCA.CA.dll" />
14
15 <CustomAction Id="ImmediateCA" BinaryRef="ManagedCA" DllEntry="ImmediateCA" Execute="immediate" Return="check" />
16 - <CustomAction Id="DeferredCA" BinaryRef="ManagedCA" DllEntry="DeferredCA" Execute="deferred" Return="check" />
16 + <CustomAction Id="ExecuteImmediateCA" BinaryRef="ManagedCA" DllEntry="ExecuteImmediateCA" Execute="immediate" Return="check" />
17 + <CustomAction Id="ImpersonatedDeferredCA" BinaryRef="ManagedCA" DllEntry="ImpersonatedDeferredCA" Execute="deferred" Impersonate="yes" Return="check" />
18 + <CustomAction Id="DeferredCA" BinaryRef="ManagedCA" DllEntry="DeferredCA" Execute="deferred" Impersonate="no" Return="check" />
19
20 <InstallUISequence>
21 <Custom Action="ImmediateCA" Before="CostFinalize" />
22 </InstallUISequence>
23
24 <InstallExecuteSequence>
25 + <Custom Action="ExecuteImmediateCA" Before="CostFinalize" />
26 + <Custom Action="ImpersonatedDeferredCA" Before="InstallFiles" />
27 <Custom Action="DeferredCA" After="InstallFiles" />
28 </InstallExecuteSequence>
29 </Package>
src/test/msi/TestData/CustomActionTests/TestCA/CustomAction.cs
+38
@@ -4,7 +4,9 @@ namespace WixToolsetTest.TestCA
4 {
5 using System;
6 using System.Collections.Generic;
7 + using System.IO;
8 using System.Linq;
9 + using System.Text;
10 using WixToolset.Dtf.WindowsInstaller;
11
12 public class CustomActions
@@ -14,6 +16,31 @@ namespace WixToolsetTest.TestCA
16 {
17 session.Log("Begin ImmediateCA");
18
19 + var path = Path.Combine(Path.GetTempPath(), "ImmediateCA.txt");
20 + WriteNow(path);
21 +
22 + return ActionResult.Success;
23 + }
24 +
25 + [CustomAction]
26 + public static ActionResult ExecuteImmediateCA(Session session)
27 + {
28 + session.Log("Begin ExecuteImmediateCA");
29 +
30 + var path = Path.Combine(Path.GetTempPath(), "ExecuteImmediateCA.txt");
31 + WriteNow(path);
32 +
33 + return ActionResult.Success;
34 + }
35 +
36 + [CustomAction]
37 + public static ActionResult ImpersonatedDeferredCA(Session session)
38 + {
39 + session.Log("Begin ImpersonatedDeferredCA");
40 +
41 + var path = Path.Combine(Path.GetTempPath(), "ImpersonatedDeferredCA.txt");
42 + WriteNow(path);
43 +
44 return ActionResult.Success;
45 }
46
@@ -22,7 +49,18 @@ namespace WixToolsetTest.TestCA
49 {
50 session.Log("Begin DeferredCA");
51
52 + WriteNow(@"C:\Windows\Installer\DeferredCA.txt");
53 +
54 return ActionResult.Success;
55 }
56 +
57 + private static void WriteNow(string path)
58 + {
59 + using (var file = File.Create(path))
60 + {
61 + var now = Encoding.UTF8.GetBytes(DateTime.Now.ToString("o"));
62 + file.Write(now, 0, now.Length);
63 + }
64 + }
65 }
66 }