From 517cd2f088a4b7c92c61cff7be27ce82eb0fab95 Mon Sep 17 00:00:00 2001 From: "Tim Mangan (MVP)" Date: Mon, 25 Jan 2021 14:27:40 -0500 Subject: [PATCH] Issues 95, 141, 152, 159, 168, and 169, and 171 (#170) * Don't throw error to user when shell launch returns non-zero, just log it. * Fix Issue #95 * Fix Issue 141 - Don't set CreationFlags if no attributeList * FIx Issue 152 - Find PowerShell.exe fix * Fix Issue 168 - Handle DllFixup with full path passed in. * Issue 169 - Add Fixup for SetCurrentDirectory in FRF * Fix Issue 169 - Add SerWorkingDirectory to FRF * Fix Issue 169 - Added SetWOrkingDirectory.cpp to project. * Fix Issue 171 - waitForDebugger in PsfLauncher debug build. * Review updates * REview Comments --- PsfLauncher/PsfPowershellScriptRunner.h | 11 +- PsfLauncher/Readme.md | 6 +- PsfLauncher/StartProcessHelper.h | 9 +- PsfLauncher/main.cpp | 702 +++++++++--------- .../DynamicLibraryFixup.cpp | 2 +- .../FunctionImplementations.h | 7 + .../FileRedirectionFixup.vcxproj | 1 + .../FunctionImplementations.h | 2 + .../FileRedirectionFixup/PathRedirection.cpp | 6 + .../SetWorkingDirectory.cpp | 46 ++ 10 files changed, 436 insertions(+), 356 deletions(-) create mode 100644 fixups/FileRedirectionFixup/SetWorkingDirectory.cpp diff --git a/PsfLauncher/PsfPowershellScriptRunner.h b/PsfLauncher/PsfPowershellScriptRunner.h index 8b5b338..8b2a493 100644 --- a/PsfLauncher/PsfPowershellScriptRunner.h +++ b/PsfLauncher/PsfPowershellScriptRunner.h @@ -443,7 +443,16 @@ class PsfPowershellScriptRunner } else if (createResult != ERROR_SUCCESS) { - THROW_HR_MSG(HRESULT_FROM_WIN32(createResult), "Error with getting the key to see if PowerShell is installed."); + // Certain systems lack the 1 key but have the 3 key (both point to same path) + createResult = RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\PowerShell\\3", 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, ®istryHandle, nullptr); + if (createResult == ERROR_FILE_NOT_FOUND) + { + return false; + } + else if (createResult != ERROR_SUCCESS) + { + THROW_HR_MSG(HRESULT_FROM_WIN32(createResult), "Error with getting the key to see if PowerShell is installed."); + } } DWORD valueFromRegistry = 0; diff --git a/PsfLauncher/Readme.md b/PsfLauncher/Readme.md index 38311ec..6302dbb 100644 --- a/PsfLauncher/Readme.md +++ b/PsfLauncher/Readme.md @@ -6,6 +6,8 @@ Based on the manifest's application ID, the `psfLauncher` looks for the app's la PSF Launcher also supports running an additional "monitor" app, intended for PsfMonitor. You use PsfMonitor to view the output in conjuction with TraceFixup injection configured to output to events. +Finally, PsfLauncher also support some limited PowerShell scripting. + ## Configuration PSF Launcher uses a config.json file to configure the behavior. @@ -244,7 +246,9 @@ This example shows an alternative for the json used in the prior example. This m In this example, the pseudo-variable %MsixPackageRoot% would be replaced by the folder path that the package was installed to. This pseudo-variable is only available for string replacement by PsfLauncher in the arguments field. The example shows a reference to a file that was placed at the root of the package and another that will exist using VFS pathing. Note that as long as the LOCALAPPDATA file is the only file required from the package LOCALAPPDATA folder, the use of FileRedirectionFixup would not be mandated. ### Example 4 -This example shows an alternative for the json used in the prior example. This might be used when an additional script is to be run upon first launch of the application. Such scripts are sometimes used for per-user configuration activities that must be made based on local conditions. +This example shows an alternative for the json used in the prior example. This might be used when an additional script is to be run upon first launch of the application. Such scripts are sometimes used for per-user configuration activities that must be made based on local conditions. + +To implement scripting PsfLauncher uses a PowerShell script as an intermediator. PshLauncher will expect to find a file StartingScriptWrapper.ps1, which is included as part of the PSF, to call the PowerShell script that is referenced in the Json. The wrapper script should be placed in the package as the same folder used by the launcher. ```json diff --git a/PsfLauncher/StartProcessHelper.h b/PsfLauncher/StartProcessHelper.h index be45071..edc8852 100644 --- a/PsfLauncher/StartProcessHelper.h +++ b/PsfLauncher/StartProcessHelper.h @@ -29,14 +29,19 @@ HRESULT StartProcess(LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR curren PROCESS_INFORMATION processInfo{}; startupInfoEx.lpAttributeList = attributeList; - + DWORD CreationFlags = 0; + if (attributeList != NULL) + { + CreationFlags = EXTENDED_STARTUPINFO_PRESENT; + } + RETURN_LAST_ERROR_IF_MSG( !::CreateProcessW( applicationName, commandLine, nullptr, nullptr, // Process/ThreadAttributes true, // InheritHandles - EXTENDED_STARTUPINFO_PRESENT, // CreationFlags + CreationFlags, nullptr, // Environment currentDirectory, (LPSTARTUPINFO)&startupInfoEx, diff --git a/PsfLauncher/main.cpp b/PsfLauncher/main.cpp index 3366cfd..55d60a6 100644 --- a/PsfLauncher/main.cpp +++ b/PsfLauncher/main.cpp @@ -1,352 +1,352 @@ -//------------------------------------------------------------------------------------------------------- -// Copyright (C) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -//------------------------------------------------------------------------------------------------------- - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include "Logger.h" -#include "StartProcessHelper.h" -#include "Telemetry.h" -#include "PsfPowershellScriptRunner.h" -#include "Globals.h" -#include -#include -#include -#include -#include -#include - - -TRACELOGGING_DECLARE_PROVIDER(g_Log_ETW_ComponentProvider); -TRACELOGGING_DEFINE_PROVIDER( - g_Log_ETW_ComponentProvider, - "Microsoft.Windows.PSFRuntime", - (0xf7f4e8c4, 0x9981, 0x5221, 0xe6, 0xfb, 0xff, 0x9d, 0xd1, 0xcd, 0xa4, 0xe1), - TraceLoggingOptionMicrosoftTelemetry()); - -using namespace std::literals; - -// Forward declarations -void LogApplicationAndProcessesCollection(); -int launcher_main(PCWSTR args, int cmdShow) noexcept; -void GetAndLaunchMonitor(const psf::json_object& monitor, std::filesystem::path packageRoot, int cmdShow, LPCWSTR dirStr); -void LaunchMonitorInBackground(std::filesystem::path packageRoot, const wchar_t executable[], const wchar_t arguments[], bool wait, bool asAdmin, int cmdShow, LPCWSTR dirStr); -bool IsCurrentOSRS2OrGreater(); -std::wstring ReplaceVariablesInString(std::wstring inputString, bool ReplaceEnvironmentVars, bool ReplacePseudoVars); - -static inline bool check_suffix_if(iwstring_view str, iwstring_view suffix) noexcept; - -int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR args, _In_ int cmdShow) -{ - return launcher_main(args, cmdShow); -} - -int launcher_main(PCWSTR args, int cmdShow) noexcept try -{ - Log("\tIn Launcher_main()"); - - auto appConfig = PSFQueryCurrentAppLaunchConfig(true); - THROW_HR_IF_MSG(ERROR_NOT_FOUND, !appConfig, "Error: could not find matching appid in config.json and appx manifest"); - -#ifdef _DEBUG - if (appConfig) - { - auto waitSignalPtr = appConfig->try_get("WaitForDebugger"); - if (waitSignalPtr) - { - bool waitSignal = waitSignalPtr->as_boolean().get(); - if (waitSignal) - { - Log("PsfLauncher waiting for debugger to attach to process...\n"); - psf::wait_for_debugger(); - } - } - } -#endif - - LogApplicationAndProcessesCollection(); - - auto dirPtr = appConfig->try_get("workingDirectory"); - auto dirStr = dirPtr ? dirPtr->as_string().wide() : L""; - - // At least for now, configured launch paths are relative to the package root - std::filesystem::path packageRoot = PSFQueryPackageRootPath(); - std::wstring dirWstr = dirStr; - dirWstr = ReplaceVariablesInString(dirWstr, true, true); - std::filesystem::path currentDirectory; - - if (dirWstr.size() < 2 || dirWstr[1] != L':') - { - currentDirectory = (packageRoot / dirWstr); - } - else - { - currentDirectory = dirWstr; - } - - PsfPowershellScriptRunner powershellScriptRunner; - - if (IsCurrentOSRS2OrGreater()) - { - powershellScriptRunner.Initialize(appConfig, currentDirectory, packageRoot); - - // Launch the starting PowerShell script if we are using one. - powershellScriptRunner.RunStartingScript(); - } - - // Launch monitor if we are using one. - auto monitor = PSFQueryAppMonitorConfig(); - if (monitor != nullptr) - { - THROW_IF_FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); - GetAndLaunchMonitor(*monitor, packageRoot, cmdShow, dirStr); - } - - // Launch underlying application. - auto exeName = appConfig->get("executable").as_string().wide(); - std::wstring exeWName = exeName; - exeWName = ReplaceVariablesInString(exeWName, true, true); - std::filesystem::path exePath; - if (exeWName[1] != L':') - { - exePath = packageRoot / exeWName; - } - else - { - exePath = exeWName; - } - - auto exeArgs = appConfig->try_get("arguments"); - std::wstring exeArgString = exeArgs ? exeArgs->as_string().wide() : (wchar_t*)L""; - exeArgString = ReplaceVariablesInString(exeArgString, true, true); - - // Keep these quotes here. StartProcess assumes there are quotes around the exe file name - if (check_suffix_if(exeName, L".exe"_isv)) - { - std::wstring fullargs; - if (!exeArgString.empty()) - { - fullargs = (exeArgString + L" " + args); - } - else - { - fullargs = args; - } - - LogString("Process Launch: ", fullargs.data()); - LogString("Working Directory: ", currentDirectory.c_str()); - - //THROW_IF_FAILED(StartProcess(exePath.c_str(), (L"\"" + exePath.filename().native() + L"\" " + exeArgString + L" " + args).data(), (packageRoot / dirStr).c_str(), cmdShow, INFINITE)); - HRESULT hr = StartProcess(exePath.c_str(), (L"\"" + exePath.filename().native() + L"\" " + exeArgString + L" " + args).data(), (packageRoot / dirStr).c_str(), cmdShow, INFINITE); - if (hr != ERROR_SUCCESS) - { - Log("Error return from launching process."); - } - } - else - { - LogString("Shell Launch", exePath.c_str()); - LogString(" Arguments", exeArgString.c_str()); - LogString("Working Directory: ", currentDirectory.c_str()); - StartWithShellExecute(packageRoot, exePath, exeArgString, currentDirectory.c_str(), cmdShow, INFINITE); - } - - if (IsCurrentOSRS2OrGreater()) - { - Log("Process Launch Ready to run any end scripts."); - // Launch the end PowerShell script if we are using one. - powershellScriptRunner.RunEndingScript(); - Log("Process Launch complete."); - } - - return 0; -} -catch (...) -{ - ::PSFReportError(widen(message_from_caught_exception()).c_str()); - return win32_from_caught_exception(); -} - -void GetAndLaunchMonitor(const psf::json_object& monitor, std::filesystem::path packageRoot, int cmdShow, LPCWSTR dirStr) -{ - bool asAdmin = false; - bool wait = false; - auto monitorExecutable = monitor.try_get("executable"); - auto monitorArguments = monitor.try_get("arguments"); - auto monitorAsAdmin = monitor.try_get("asadmin"); - auto monitorWait = monitor.try_get("wait"); - if (monitorAsAdmin) - { - asAdmin = monitorAsAdmin->as_boolean().get(); - } - - if (monitorWait) - { - wait = monitorWait->as_boolean().get(); - } - - Log("\tCreating the monitor: %ls", monitorExecutable->as_string().wide()); - LaunchMonitorInBackground(packageRoot, monitorExecutable->as_string().wide(), monitorArguments->as_string().wide(), wait, asAdmin, cmdShow, dirStr); -} - -void LaunchMonitorInBackground(std::filesystem::path packageRoot, const wchar_t executable[], const wchar_t arguments[], bool wait, bool asAdmin, int cmdShow, LPCWSTR dirStr) -{ - std::wstring cmd = L"\"" + (packageRoot / executable).native() + L"\""; - - if (asAdmin) - { - // This happens when the program is requested for elevation. - SHELLEXECUTEINFOW shExInfo = - { - sizeof(shExInfo) // bSize - , wait ? (ULONG)SEE_MASK_NOCLOSEPROCESS : (ULONG)(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_WAITFORINPUTIDLE) // fmask - , 0 // hwnd - , L"runas" // lpVerb - , cmd.c_str() // lpFile - , arguments // lpParameters - , nullptr // lpDirectory - , 1 // nShow - , 0 // hInstApp - }; - - THROW_LAST_ERROR_IF_MSG(!ShellExecuteEx(&shExInfo), "Error starting monitor using ShellExecuteEx"); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), shExInfo.hProcess == INVALID_HANDLE_VALUE); - - if (wait) - { - WaitForSingleObject(shExInfo.hProcess, INFINITE); - CloseHandle(shExInfo.hProcess); - } - else - { - WaitForInputIdle(shExInfo.hProcess, 1000); - // Due to elevation, the process starts, relaunches, and the main process ends in under 1ms. - // So we'll just toss in an ugly sleep here for now. - Sleep(5000); - } - - // Should not kill the intended app because the monitor elevated. - //DWORD exitCode{}; - //THROW_LAST_ERROR_IF_MSG(!GetExitCodeProcess(shExInfo.hProcess, &exitCode), "Could not get error for process"); - //THROW_IF_WIN32_ERROR(exitCode); - } - else - { - THROW_IF_FAILED(StartProcess(executable, (cmd + L" " + arguments).data(), (packageRoot / dirStr).c_str(), cmdShow, INFINITE)); - } -} - - -// Replace all occurrences of requested environment and/or pseudo-environment variables in a string. -std::wstring ReplaceVariablesInString(std::wstring inputString, bool ReplaceEnvironmentVars, bool ReplacePseudoVars) -{ - std::wstring outputString = inputString; - if (ReplacePseudoVars) - { - std::wstring::size_type pos = 0u; - std::wstring var2rep = L"%MsixPackageRoot%"; - std::wstring repargs = PSFQueryPackageRootPath(); - while ((pos = outputString.find(var2rep, pos)) != std::string::npos) { - outputString.replace(pos, var2rep.length(), repargs); - pos += repargs.length(); - } - - pos = 0u; - var2rep = L"%MsixWritablePackageRoot%"; - std::filesystem::path writablePackageRootPath = psf::known_folder(FOLDERID_LocalAppData) / std::filesystem::path(L"Packages") / psf::current_package_family_name() / LR"(LocalCache\Local\Microsoft\WritablePackageRoot)"; - repargs = writablePackageRootPath.c_str(); - while ((pos = outputString.find(var2rep, pos)) != std::string::npos) { - outputString.replace(pos, var2rep.length(), repargs); - pos += repargs.length(); - } - } - if (ReplaceEnvironmentVars) - { - // Potentially an environment variable that needs replacing. For Example: "%HomeDir%\\Documents" - DWORD nSizeBuff = 256; - LPWSTR buff = new wchar_t[nSizeBuff]; - DWORD nSizeRet = ExpandEnvironmentStrings(outputString.c_str(), buff, nSizeBuff); - if (nSizeRet > 0) - { - outputString = std::wstring(buff); - } - - } - return outputString; -} - - -static inline bool check_suffix_if(iwstring_view str, iwstring_view suffix) noexcept -{ - return ((str.length() >= suffix.length()) && (str.substr(str.length() - suffix.length()) == suffix)); -} - -void LogApplicationAndProcessesCollection() -{ - auto configRoot = PSFQueryConfigRoot(); - - if (auto applications = configRoot->as_object().try_get("applications")) - { - for (auto& applicationsConfig : applications->as_array()) - { - auto exeStr = applicationsConfig.as_object().try_get("executable")->as_string().wide(); - auto idStr = applicationsConfig.as_object().try_get("id")->as_string().wide(); - TraceLoggingWrite( - g_Log_ETW_ComponentProvider, - "ApplicationsConfigdata", - TraceLoggingWideString(exeStr, "applications_executable"), - TraceLoggingWideString(idStr, "applications_id"), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - } - } - - if (auto processes = configRoot->as_object().try_get("processes")) - { - for (auto& processConfig : processes->as_array()) - { - auto exeStr = processConfig.as_object().get("executable").as_string().wide(); - TraceLoggingWrite( - g_Log_ETW_ComponentProvider, - "ProcessesExecutableConfigdata", - TraceLoggingWideString(exeStr, "processes_executable"), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (auto fixups = processConfig.as_object().try_get("fixups")) - { - for (auto& fixupConfig : fixups->as_array()) - { - auto dllStr = fixupConfig.as_object().try_get("dll")->as_string().wide(); - TraceLoggingWrite( - g_Log_ETW_ComponentProvider, - "ProcessesFixUpConfigdata", - TraceLoggingWideString(dllStr, "processes_fixups"), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - } - } - } - } -} - -bool IsCurrentOSRS2OrGreater() -{ - OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; - DWORDLONG const dwlConditionMask = VerSetConditionMask(0, VER_BUILDNUMBER, VER_GREATER_EQUAL); - osvi.dwBuildNumber = 15063; - - return VerifyVersionInfoW(&osvi, VER_BUILDNUMBER, dwlConditionMask); +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "Logger.h" +#include "StartProcessHelper.h" +#include "Telemetry.h" +#include "PsfPowershellScriptRunner.h" +#include "Globals.h" +#include +#include +#include +#include +#include +#include + + +TRACELOGGING_DECLARE_PROVIDER(g_Log_ETW_ComponentProvider); +TRACELOGGING_DEFINE_PROVIDER( + g_Log_ETW_ComponentProvider, + "Microsoft.Windows.PSFRuntime", + (0xf7f4e8c4, 0x9981, 0x5221, 0xe6, 0xfb, 0xff, 0x9d, 0xd1, 0xcd, 0xa4, 0xe1), + TraceLoggingOptionMicrosoftTelemetry()); + +using namespace std::literals; + +// Forward declarations +void LogApplicationAndProcessesCollection(); +int launcher_main(PCWSTR args, int cmdShow) noexcept; +void GetAndLaunchMonitor(const psf::json_object& monitor, std::filesystem::path packageRoot, int cmdShow, LPCWSTR dirStr); +void LaunchMonitorInBackground(std::filesystem::path packageRoot, const wchar_t executable[], const wchar_t arguments[], bool wait, bool asAdmin, int cmdShow, LPCWSTR dirStr); +bool IsCurrentOSRS2OrGreater(); +std::wstring ReplaceVariablesInString(std::wstring inputString, bool ReplaceEnvironmentVars, bool ReplacePseudoVars); + +static inline bool check_suffix_if(iwstring_view str, iwstring_view suffix) noexcept; + +int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR args, _In_ int cmdShow) +{ + return launcher_main(args, cmdShow); +} + +int launcher_main(PCWSTR args, int cmdShow) noexcept try +{ + Log("\tIn Launcher_main()"); + + auto appConfig = PSFQueryCurrentAppLaunchConfig(true); + THROW_HR_IF_MSG(ERROR_NOT_FOUND, !appConfig, "Error: could not find matching appid in config.json and appx manifest"); + +#ifdef _DEBUG + if (appConfig) + { + auto waitSignalPtr = appConfig->try_get("waitForDebugger"); + if (waitSignalPtr) + { + bool waitSignal = waitSignalPtr->as_boolean().get(); + if (waitSignal) + { + Log("PsfLauncher waiting for debugger to attach to process...\n"); + psf::wait_for_debugger(); + } + } + } +#endif + + LogApplicationAndProcessesCollection(); + + auto dirPtr = appConfig->try_get("workingDirectory"); + auto dirStr = dirPtr ? dirPtr->as_string().wide() : L""; + + // At least for now, configured launch paths are relative to the package root + std::filesystem::path packageRoot = PSFQueryPackageRootPath(); + std::wstring dirWstr = dirStr; + dirWstr = ReplaceVariablesInString(dirWstr, true, true); + std::filesystem::path currentDirectory; + + if (dirWstr.size() < 2 || dirWstr[1] != L':') + { + currentDirectory = (packageRoot / dirWstr); + } + else + { + currentDirectory = dirWstr; + } + + PsfPowershellScriptRunner powershellScriptRunner; + + if (IsCurrentOSRS2OrGreater()) + { + powershellScriptRunner.Initialize(appConfig, currentDirectory, packageRoot); + + // Launch the starting PowerShell script if we are using one. + powershellScriptRunner.RunStartingScript(); + } + + // Launch monitor if we are using one. + auto monitor = PSFQueryAppMonitorConfig(); + if (monitor != nullptr) + { + THROW_IF_FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); + GetAndLaunchMonitor(*monitor, packageRoot, cmdShow, dirStr); + } + + // Launch underlying application. + auto exeName = appConfig->get("executable").as_string().wide(); + std::wstring exeWName = exeName; + exeWName = ReplaceVariablesInString(exeWName, true, true); + std::filesystem::path exePath; + if (exeWName[1] != L':') + { + exePath = packageRoot / exeWName; + } + else + { + exePath = exeWName; + } + + auto exeArgs = appConfig->try_get("arguments"); + std::wstring exeArgString = exeArgs ? exeArgs->as_string().wide() : (wchar_t*)L""; + exeArgString = ReplaceVariablesInString(exeArgString, true, true); + + // Keep these quotes here. StartProcess assumes there are quotes around the exe file name + if (check_suffix_if(exeName, L".exe"_isv)) + { + std::wstring fullargs; + if (!exeArgString.empty()) + { + fullargs = (exeArgString + L" " + args); + } + else + { + fullargs = args; + } + + LogString("Process Launch: ", fullargs.data()); + LogString("Working Directory: ", currentDirectory.c_str()); + + //THROW_IF_FAILED(StartProcess(exePath.c_str(), (L"\"" + exePath.filename().native() + L"\" " + exeArgString + L" " + args).data(), (packageRoot / dirStr).c_str(), cmdShow, INFINITE)); + HRESULT hr = StartProcess(exePath.c_str(), (L"\"" + exePath.filename().native() + L"\" " + exeArgString + L" " + args).data(), (packageRoot / dirStr).c_str(), cmdShow, INFINITE); + if (hr != ERROR_SUCCESS) + { + Log("Error return from launching process."); + } + } + else + { + LogString("Shell Launch", exePath.c_str()); + LogString(" Arguments", exeArgString.c_str()); + LogString("Working Directory: ", currentDirectory.c_str()); + StartWithShellExecute(packageRoot, exePath, exeArgString, currentDirectory.c_str(), cmdShow, INFINITE); + } + + if (IsCurrentOSRS2OrGreater()) + { + Log("Process Launch Ready to run any end scripts."); + // Launch the end PowerShell script if we are using one. + powershellScriptRunner.RunEndingScript(); + Log("Process Launch complete."); + } + + return 0; +} +catch (...) +{ + ::PSFReportError(widen(message_from_caught_exception()).c_str()); + return win32_from_caught_exception(); +} + +void GetAndLaunchMonitor(const psf::json_object& monitor, std::filesystem::path packageRoot, int cmdShow, LPCWSTR dirStr) +{ + bool asAdmin = false; + bool wait = false; + auto monitorExecutable = monitor.try_get("executable"); + auto monitorArguments = monitor.try_get("arguments"); + auto monitorAsAdmin = monitor.try_get("asadmin"); + auto monitorWait = monitor.try_get("wait"); + if (monitorAsAdmin) + { + asAdmin = monitorAsAdmin->as_boolean().get(); + } + + if (monitorWait) + { + wait = monitorWait->as_boolean().get(); + } + + Log("\tCreating the monitor: %ls", monitorExecutable->as_string().wide()); + LaunchMonitorInBackground(packageRoot, monitorExecutable->as_string().wide(), monitorArguments->as_string().wide(), wait, asAdmin, cmdShow, dirStr); +} + +void LaunchMonitorInBackground(std::filesystem::path packageRoot, const wchar_t executable[], const wchar_t arguments[], bool wait, bool asAdmin, int cmdShow, LPCWSTR dirStr) +{ + std::wstring cmd = L"\"" + (packageRoot / executable).native() + L"\""; + + if (asAdmin) + { + // This happens when the program is requested for elevation. + SHELLEXECUTEINFOW shExInfo = + { + sizeof(shExInfo) // bSize + , wait ? (ULONG)SEE_MASK_NOCLOSEPROCESS : (ULONG)(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_WAITFORINPUTIDLE) // fmask + , 0 // hwnd + , L"runas" // lpVerb + , cmd.c_str() // lpFile + , arguments // lpParameters + , nullptr // lpDirectory + , 1 // nShow + , 0 // hInstApp + }; + + THROW_LAST_ERROR_IF_MSG(!ShellExecuteEx(&shExInfo), "Error starting monitor using ShellExecuteEx"); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), shExInfo.hProcess == INVALID_HANDLE_VALUE); + + if (wait) + { + WaitForSingleObject(shExInfo.hProcess, INFINITE); + CloseHandle(shExInfo.hProcess); + } + else + { + WaitForInputIdle(shExInfo.hProcess, 1000); + // Due to elevation, the process starts, relaunches, and the main process ends in under 1ms. + // So we'll just toss in an ugly sleep here for now. + Sleep(5000); + } + + // Should not kill the intended app because the monitor elevated. + //DWORD exitCode{}; + //THROW_LAST_ERROR_IF_MSG(!GetExitCodeProcess(shExInfo.hProcess, &exitCode), "Could not get error for process"); + //THROW_IF_WIN32_ERROR(exitCode); + } + else + { + THROW_IF_FAILED(StartProcess(executable, (cmd + L" " + arguments).data(), (packageRoot / dirStr).c_str(), cmdShow, INFINITE)); + } +} + + +// Replace all occurrences of requested environment and/or pseudo-environment variables in a string. +std::wstring ReplaceVariablesInString(std::wstring inputString, bool ReplaceEnvironmentVars, bool ReplacePseudoVars) +{ + std::wstring outputString = inputString; + if (ReplacePseudoVars) + { + std::wstring::size_type pos = 0u; + std::wstring var2rep = L"%MsixPackageRoot%"; + std::wstring repargs = PSFQueryPackageRootPath(); + while ((pos = outputString.find(var2rep, pos)) != std::string::npos) { + outputString.replace(pos, var2rep.length(), repargs); + pos += repargs.length(); + } + + pos = 0u; + var2rep = L"%MsixWritablePackageRoot%"; + std::filesystem::path writablePackageRootPath = psf::known_folder(FOLDERID_LocalAppData) / std::filesystem::path(L"Packages") / psf::current_package_family_name() / LR"(LocalCache\Local\Microsoft\WritablePackageRoot)"; + repargs = writablePackageRootPath.c_str(); + while ((pos = outputString.find(var2rep, pos)) != std::string::npos) { + outputString.replace(pos, var2rep.length(), repargs); + pos += repargs.length(); + } + } + if (ReplaceEnvironmentVars) + { + // Potentially an environment variable that needs replacing. For Example: "%HomeDir%\\Documents" + DWORD nSizeBuff = 256; + LPWSTR buff = new wchar_t[nSizeBuff]; + DWORD nSizeRet = ExpandEnvironmentStrings(outputString.c_str(), buff, nSizeBuff); + if (nSizeRet > 0) + { + outputString = std::wstring(buff); + } + + } + return outputString; +} + + +static inline bool check_suffix_if(iwstring_view str, iwstring_view suffix) noexcept +{ + return ((str.length() >= suffix.length()) && (str.substr(str.length() - suffix.length()) == suffix)); +} + +void LogApplicationAndProcessesCollection() +{ + auto configRoot = PSFQueryConfigRoot(); + + if (auto applications = configRoot->as_object().try_get("applications")) + { + for (auto& applicationsConfig : applications->as_array()) + { + auto exeStr = applicationsConfig.as_object().try_get("executable")->as_string().wide(); + auto idStr = applicationsConfig.as_object().try_get("id")->as_string().wide(); + TraceLoggingWrite( + g_Log_ETW_ComponentProvider, + "ApplicationsConfigdata", + TraceLoggingWideString(exeStr, "applications_executable"), + TraceLoggingWideString(idStr, "applications_id"), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + } + + if (auto processes = configRoot->as_object().try_get("processes")) + { + for (auto& processConfig : processes->as_array()) + { + auto exeStr = processConfig.as_object().get("executable").as_string().wide(); + TraceLoggingWrite( + g_Log_ETW_ComponentProvider, + "ProcessesExecutableConfigdata", + TraceLoggingWideString(exeStr, "processes_executable"), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (auto fixups = processConfig.as_object().try_get("fixups")) + { + for (auto& fixupConfig : fixups->as_array()) + { + auto dllStr = fixupConfig.as_object().try_get("dll")->as_string().wide(); + TraceLoggingWrite( + g_Log_ETW_ComponentProvider, + "ProcessesFixUpConfigdata", + TraceLoggingWideString(dllStr, "processes_fixups"), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + } + } + } +} + +bool IsCurrentOSRS2OrGreater() +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask(0, VER_BUILDNUMBER, VER_GREATER_EQUAL); + osvi.dwBuildNumber = 15063; + + return VerifyVersionInfoW(&osvi, VER_BUILDNUMBER, dwlConditionMask); } \ No newline at end of file diff --git a/fixups/DynamicLibraryFixup/DynamicLibraryFixup.cpp b/fixups/DynamicLibraryFixup/DynamicLibraryFixup.cpp index 6100cca..d680f1b 100644 --- a/fixups/DynamicLibraryFixup/DynamicLibraryFixup.cpp +++ b/fixups/DynamicLibraryFixup/DynamicLibraryFixup.cpp @@ -28,7 +28,7 @@ HMODULE __stdcall LoadLibraryFixup(_In_ const CharT* libFileName) Log("LoadLibraryFixup unguarded."); #endif // Check against known dlls in package. - std::wstring libFileNameW = InterpretStringW(libFileName); + std::wstring libFileNameW = GetFilenameOnly(InterpretStringW(libFileName)); if (g_dynf_forcepackagedlluse) { diff --git a/fixups/DynamicLibraryFixup/FunctionImplementations.h b/fixups/DynamicLibraryFixup/FunctionImplementations.h index 0af87c3..e805e57 100644 --- a/fixups/DynamicLibraryFixup/FunctionImplementations.h +++ b/fixups/DynamicLibraryFixup/FunctionImplementations.h @@ -31,6 +31,13 @@ inline std::wstring InterpretStringW(const wchar_t* value) return value; } +inline std::wstring GetFilenameOnly(std::wstring path) +{ + size_t index = path.find_last_of(L"\\", std::wstring::npos); + if (index == std::wstring::npos) + return path; + return path.substr(index + 1); +} void Log(const char* fmt, ...); void LogString(const char* name, const char* value); diff --git a/fixups/FileRedirectionFixup/FileRedirectionFixup.vcxproj b/fixups/FileRedirectionFixup/FileRedirectionFixup.vcxproj index d0ed4e1..a71512b 100644 --- a/fixups/FileRedirectionFixup/FileRedirectionFixup.vcxproj +++ b/fixups/FileRedirectionFixup/FileRedirectionFixup.vcxproj @@ -49,6 +49,7 @@ + diff --git a/fixups/FileRedirectionFixup/FunctionImplementations.h b/fixups/FileRedirectionFixup/FunctionImplementations.h index f5570f8..846ceae 100644 --- a/fixups/FileRedirectionFixup/FunctionImplementations.h +++ b/fixups/FileRedirectionFixup/FunctionImplementations.h @@ -56,6 +56,8 @@ namespace impl inline auto SetFileAttributes = psf::detoured_string_function(&::SetFileAttributesA, &::SetFileAttributesW); + inline auto SetCurrentDirectory = psf::detoured_string_function(&::SetCurrentDirectoryA, &::SetCurrentDirectoryW); + inline auto WritePrivateProfileSection = psf::detoured_string_function(&::WritePrivateProfileSectionA, &::WritePrivateProfileSectionW); inline auto WritePrivateProfileString = psf::detoured_string_function(&::WritePrivateProfileStringA, &::WritePrivateProfileStringW); inline auto WritePrivateProfileStruct = psf::detoured_string_function(&::WritePrivateProfileStructA, &::WritePrivateProfileStructW); diff --git a/fixups/FileRedirectionFixup/PathRedirection.cpp b/fixups/FileRedirectionFixup/PathRedirection.cpp index 7f39ca4..a60d8a7 100644 --- a/fixups/FileRedirectionFixup/PathRedirection.cpp +++ b/fixups/FileRedirectionFixup/PathRedirection.cpp @@ -1201,6 +1201,12 @@ static path_redirect_info ShouldRedirectImpl(const CharT* path, redirect_flags f } LogString(inst, L"\tFRF Should: for path", widen(path).c_str()); + size_t found = (widen(path)).find(L"WritablePackageRoot", 0); + if (found != 0) + { + LogString(inst, L"Prevent redundant redirection.", widen(path).c_str()); + return result; + } bool c_presense = flag_set(flags, redirect_flags::check_file_presence); bool c_copy = flag_set(flags, redirect_flags::copy_file); diff --git a/fixups/FileRedirectionFixup/SetWorkingDirectory.cpp b/fixups/FileRedirectionFixup/SetWorkingDirectory.cpp new file mode 100644 index 0000000..34159b8 --- /dev/null +++ b/fixups/FileRedirectionFixup/SetWorkingDirectory.cpp @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) TMurgent Technologies, LLP. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +#include "FunctionImplementations.h" +#include "PathRedirection.h" + + +template +BOOL __stdcall SetCurrentDirectoryFixup(_In_ const CharT* filePath) noexcept +{ + auto guard = g_reentrancyGuard.enter(); + try + { + if (guard) + { + DWORD SetWorkingDirectoryInstance = ++g_FileIntceptInstance; + LogString(SetWorkingDirectoryInstance, L"SetCurrentDirectoryFixup ", filePath); + if (!path_relative_to(filePath, psf::current_package_path())) + { + normalized_path normalized = NormalizePath(filePath); + normalized_path virtualized = VirtualizePath(normalized, SetWorkingDirectoryInstance); + if (impl::PathExists(virtualized.full_path.c_str())) + { + return impl::SetCurrentDirectory(virtualized.full_path.c_str()); + } + else + { + // Fall through to original call + } + } + else + { + // Fall through to original call + } + } + } + catch (...) + { + // Fall back to assuming no redirection is necessary + } + + return impl::SetCurrentDirectory(filePath); +} +DECLARE_STRING_FIXUP(impl::SetCurrentDirectory, SetCurrentDirectoryFixup);