From 681cf4a9eb6be7e4092c6e5b690773fbd8469e63 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 4 Apr 2024 15:24:34 -0700 Subject: Ensure elevated SFXCA uses Windows Installer cache and unelevated uses Temp folder Fixes 8078 --- src/dtf/SfxCA/SfxCA.vcxproj | 2 +- src/dtf/SfxCA/SfxUtil.cpp | 169 +++++++++++++++++---- src/dtf/dtf.cmd | 23 +++ .../ManagedCustomActions/Package.wxs | 6 +- .../CustomActionTests/TestCA/CustomAction.cs | 38 +++++ 5 files changed, 203 insertions(+), 35 deletions(-) diff --git a/src/dtf/SfxCA/SfxCA.vcxproj b/src/dtf/SfxCA/SfxCA.vcxproj index 5c4f674f..ee591619 100644 --- a/src/dtf/SfxCA/SfxCA.vcxproj +++ b/src/dtf/SfxCA/SfxCA.vcxproj @@ -41,7 +41,7 @@ - msi.lib;cabinet.lib;shlwapi.lib + msi.lib;cabinet.lib;rpcrt4.lib;shlwapi.lib diff --git a/src/dtf/SfxCA/SfxUtil.cpp b/src/dtf/SfxCA/SfxUtil.cpp index 32dc6e04..079f1617 100644 --- a/src/dtf/SfxCA/SfxUtil.cpp +++ b/src/dtf/SfxCA/SfxUtil.cpp @@ -3,6 +3,8 @@ #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. @@ -110,20 +112,75 @@ bool DeleteDirectory(const wchar_t* szDir) hSearch = INVALID_HANDLE_VALUE; } } - return RemoveDirectory(szDir) != 0; + + for (int i = 0; i < 3; i++) + { + if (::RemoveDirectory(szDir)) + { + return true; + } + + ::Sleep(100); + } + + return false; } -bool DirectoryExists(const wchar_t* szDir) +static HRESULT CreateGuid( + _Out_z_cap_c_(GUID_STRING_LENGTH) wchar_t* wzGuid) { - if (szDir != NULL) + HRESULT hr = S_OK; + RPC_STATUS rs = RPC_S_OK; + UUID guid = {}; + + rs = ::UuidCreate(&guid); + if (rs != RPC_S_OK) { - DWORD dwAttrs = GetFileAttributes(szDir); - if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0) - { - return true; - } + hr = (HRESULT)(rs | FACILITY_RPC); } - return false; + 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; } /// @@ -143,49 +200,95 @@ __success(return != false) bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) { - wchar_t szModule[MAX_PATH]; - DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1); - if (cchCopied == 0) + 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) { - Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); - return false; + 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; } - else if (cchCopied == MAX_PATH - 1) + + hr = CreateGuid(szGuid); + if (FAILED(hr)) { - Log(hSession, L"Failed to get module path -- path is too long."); - return false; + Log(hSession, L"Failed to create a GUID. Error code 0x%x", hr); + goto LExit; } - if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1) + // 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) { - Log(hSession, L"Temp directory buffer is NULL or too small."); - return false; + // 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; + } } - StringCchCopy(szTempDir, cchTempDirBuf, szModule); - StringCchCat(szTempDir, cchTempDirBuf, L"-"); - BOOL fCreatedDirectory = FALSE; - DWORD cchTempDir = (DWORD) wcslen(szTempDir); - for (int i = 0; i < 10000 && !fCreatedDirectory; i++) + hr = ::StringCchCat(szTempDir, cchTempDirBuf, szGuid); + if (FAILED(hr)) { - swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); - fCreatedDirectory = ::CreateDirectory(szTempDir, NULL); + Log(hSession, L"Failed append GUID to temp path. Error code 0x%x", hr); + goto LExit; } - if (!fCreatedDirectory) + if (!::CreateDirectory(szTempDir, NULL)) { - Log(hSession, L"Failed to create temp directory. Error code %d", ::GetLastError()); - return false; + 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); - return false; } - return true; -} +LExit: + return SUCCEEDED(hr); +} diff --git a/src/dtf/dtf.cmd b/src/dtf/dtf.cmd index d72c803b..530ee432 100644 --- a/src/dtf/dtf.cmd +++ b/src/dtf/dtf.cmd @@ -4,13 +4,36 @@ @set _C=Debug :parse_args @if /i "%1"=="release" set _C=Release +@if /i "%1"=="inc" set _INC=1 +@if /i "%1"=="clean" set _CLEAN=1 @if not "%1"=="" shift & goto parse_args +:: Clean + +@if "%_INC%"=="" call :clean +@if NOT "%_CLEAN%"=="" goto :end + @echo Building dtf %_C% msbuild -Restore SfxCA\sfxca_t.proj -p:Configuration=%_C% -tl -nologo -m -warnaserror -bl:..\..\build\logs\dtf_sfxca.binlog || exit /b msbuild -Restore -t:Pack dtf.sln -p:Configuration=%_C% -tl -nologo -m -warnaserror -bl:..\..\build\logs\dtf_build.binlog || exit /b +@goto :end + +:clean +@rd /s/q "..\..\build\dtf" 2> nul +@del "..\..\build\artifacts\WixToolset.Dtf.*.nupkg" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.compression" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.compression.cab" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.compression.zip" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.customaction" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.resources" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.windowsinstaller" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.windowsinstaller.linq" 2> nul +@rd /s/q "%USERPROFILE%\.nuget\packages\wixtoolset.dtf.windowsinstaller.package" 2> nul +@exit /b + +:end @popd @endlocal diff --git a/src/test/msi/TestData/CustomActionTests/ManagedCustomActions/Package.wxs b/src/test/msi/TestData/CustomActionTests/ManagedCustomActions/Package.wxs index 9b1d224a..4159cd3e 100644 --- a/src/test/msi/TestData/CustomActionTests/ManagedCustomActions/Package.wxs +++ b/src/test/msi/TestData/CustomActionTests/ManagedCustomActions/Package.wxs @@ -13,13 +13,17 @@ - + + + + + diff --git a/src/test/msi/TestData/CustomActionTests/TestCA/CustomAction.cs b/src/test/msi/TestData/CustomActionTests/TestCA/CustomAction.cs index 6891f8da..54afc2cf 100644 --- a/src/test/msi/TestData/CustomActionTests/TestCA/CustomAction.cs +++ b/src/test/msi/TestData/CustomActionTests/TestCA/CustomAction.cs @@ -4,7 +4,9 @@ namespace WixToolsetTest.TestCA { using System; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Text; using WixToolset.Dtf.WindowsInstaller; public class CustomActions @@ -14,6 +16,31 @@ namespace WixToolsetTest.TestCA { session.Log("Begin ImmediateCA"); + var path = Path.Combine(Path.GetTempPath(), "ImmediateCA.txt"); + WriteNow(path); + + return ActionResult.Success; + } + + [CustomAction] + public static ActionResult ExecuteImmediateCA(Session session) + { + session.Log("Begin ExecuteImmediateCA"); + + var path = Path.Combine(Path.GetTempPath(), "ExecuteImmediateCA.txt"); + WriteNow(path); + + return ActionResult.Success; + } + + [CustomAction] + public static ActionResult ImpersonatedDeferredCA(Session session) + { + session.Log("Begin ImpersonatedDeferredCA"); + + var path = Path.Combine(Path.GetTempPath(), "ImpersonatedDeferredCA.txt"); + WriteNow(path); + return ActionResult.Success; } @@ -22,7 +49,18 @@ namespace WixToolsetTest.TestCA { session.Log("Begin DeferredCA"); + WriteNow(@"C:\Windows\Installer\DeferredCA.txt"); + return ActionResult.Success; } + + private static void WriteNow(string path) + { + using (var file = File.Create(path)) + { + var now = Encoding.UTF8.GetBytes(DateTime.Now.ToString("o")); + file.Write(now, 0, now.Length); + } + } } } -- cgit v1.2.3-55-g6feb