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
}