diff --git a/CentennialFixups.sln b/CentennialFixups.sln index fcbd8de..b0ce760 100644 --- a/CentennialFixups.sln +++ b/CentennialFixups.sln @@ -48,6 +48,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MFRFixup", "fixups\MFRFixup {87CCE0AC-A7FB-4A31-89D3-C0ACDB315EE0} = {87CCE0AC-A7FB-4A31-89D3-C0ACDB315EE0} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PsfFtaCom", "PsfFtaCom\PsfFtaCom.vcxproj", "{01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -100,14 +102,10 @@ Global {2896A610-9654-43BE-8493-B74D1BC44FD9}.Release|x86.Build.0 = Release|Win32 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Debug|Any CPU.ActiveCfg = Debug|Win32 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Debug|x64.ActiveCfg = Debug|x64 - {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Debug|x64.Build.0 = Debug|x64 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Debug|x86.ActiveCfg = Debug|Win32 - {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Debug|x86.Build.0 = Debug|Win32 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Release|Any CPU.ActiveCfg = Release|Win32 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Release|x64.ActiveCfg = Release|x64 - {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Release|x64.Build.0 = Release|x64 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Release|x86.ActiveCfg = Release|Win32 - {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5}.Release|x86.Build.0 = Release|Win32 {42E2CC9E-D708-4C4B-A91B-00B23F893C4C}.Debug|Any CPU.ActiveCfg = Debug|Win32 {42E2CC9E-D708-4C4B-A91B-00B23F893C4C}.Debug|x64.ActiveCfg = Debug|x64 {42E2CC9E-D708-4C4B-A91B-00B23F893C4C}.Debug|x64.Build.0 = Debug|x64 @@ -130,14 +128,10 @@ Global {76053BA2-AB6B-4F27-90E1-EE5BFE2EFA70}.Release|x86.Build.0 = Release|Win32 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Debug|Any CPU.ActiveCfg = Debug|Win32 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Debug|x64.ActiveCfg = Debug|x64 - {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Debug|x64.Build.0 = Debug|x64 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Debug|x86.ActiveCfg = Debug|Win32 - {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Debug|x86.Build.0 = Debug|Win32 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Release|Any CPU.ActiveCfg = Release|Win32 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Release|x64.ActiveCfg = Release|x64 - {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Release|x64.Build.0 = Release|x64 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Release|x86.ActiveCfg = Release|Win32 - {0C1F7A43-65DE-4460-A9EB-F44F40AF0968}.Release|x86.Build.0 = Release|Win32 {A3653AD0-2406-48A4-95CD-7D4264257F9F}.Debug|Any CPU.ActiveCfg = Debug|Win32 {A3653AD0-2406-48A4-95CD-7D4264257F9F}.Debug|x64.ActiveCfg = Debug|x64 {A3653AD0-2406-48A4-95CD-7D4264257F9F}.Debug|x64.Build.0 = Debug|x64 @@ -200,6 +194,18 @@ Global {E65C064C-5A3C-422E-A57C-116853EEACD6}.Release|x64.Build.0 = Release|x64 {E65C064C-5A3C-422E-A57C-116853EEACD6}.Release|x86.ActiveCfg = Release|Win32 {E65C064C-5A3C-422E-A57C-116853EEACD6}.Release|x86.Build.0 = Release|Win32 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Debug|Any CPU.ActiveCfg = Debug|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Debug|Any CPU.Build.0 = Debug|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Debug|x64.ActiveCfg = Debug|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Debug|x64.Build.0 = Debug|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Debug|x86.ActiveCfg = Debug|Win32 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Debug|x86.Build.0 = Debug|Win32 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Release|Any CPU.ActiveCfg = Release|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Release|Any CPU.Build.0 = Release|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Release|x64.ActiveCfg = Release|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Release|x64.Build.0 = Release|x64 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Release|x86.ActiveCfg = Release|Win32 + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -219,6 +225,7 @@ Global {1D26CBD7-B670-4F8E-BBD8-4771B76C9215} = {1B9D61ED-0B97-469C-A12D-079526888BF8} {AA616ED2-6783-40AE-9197-B257E8B17690} = {1B9D61ED-0B97-469C-A12D-079526888BF8} {E65C064C-5A3C-422E-A57C-116853EEACD6} = {1B9D61ED-0B97-469C-A12D-079526888BF8} + {01C18483-3F8E-40E1-B69C-6A1AA65DDFB7} = {5785A7B6-A9A7-4623-B5A2-62F660695A71} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46CC2CF3-2979-46F8-B3C9-D85349586600} diff --git a/CommonSrc/ArgumentVirtualization.cpp b/CommonSrc/ArgumentVirtualization.cpp index b33886c..84167d0 100644 --- a/CommonSrc/ArgumentVirtualization.cpp +++ b/CommonSrc/ArgumentVirtualization.cpp @@ -289,6 +289,9 @@ std::wstring CanReplaceWithVFS(const std::wstring input) std::wstring ArgumentVirtualization(const std::wstring input) { std::wstring output = L""; + + + if (findStringIC(input, L"C:\\")) { size_t offset = 0; diff --git a/Detours/Detours.vcxproj b/Detours/Detours.vcxproj index 26cd20c..188a5cd 100644 --- a/Detours/Detours.vcxproj +++ b/Detours/Detours.vcxproj @@ -40,7 +40,7 @@ 16.0 {79DB420C-0C71-4948-A93C-821761A8105B} - 10.0 + 10.0.22621.0 StaticLibrary diff --git a/Notes.txt b/Notes.txt index 7504bdd..81ec80a 100644 --- a/Notes.txt +++ b/Notes.txt @@ -14,8 +14,11 @@ ideas: as an extra xxx.yyy.deleted file in the redirection area. But then all methods looking to see files would have to look for this, and anything creating a file might have to remove it. Needs more thought on finding a way to distinguish. -v.2024.next: + +v.2024.08.05: * Prevent crash due to downrev Windows.storage.dll Detouring. +* Added new launcher component, PsfFtaCom to handle FTA, Shell Extension, and Application COM components. +* Fix for PsfLauncher with argument virtualization. v.2024.03.04: diff --git a/PsfFtaCom/Globals.h b/PsfFtaCom/Globals.h new file mode 100644 index 0000000..338e096 --- /dev/null +++ b/PsfFtaCom/Globals.h @@ -0,0 +1,4 @@ +#pragma once + +// Define _PROC_THREAD_ATTRIBUTE_LIST as an empty struct because it's internal-only and variable-sized +//struct _PROC_THREAD_ATTRIBUTE_LIST {}; diff --git a/PsfFtaCom/PsfFtaCom.cpp b/PsfFtaCom/PsfFtaCom.cpp new file mode 100644 index 0000000..725f02a --- /dev/null +++ b/PsfFtaCom/PsfFtaCom.cpp @@ -0,0 +1,311 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) TMurgent Technologies, LLP. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// PsfFtaCom.cpp : This file contains the 'main' function. Program execution begins and ends there. +// + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "StartProcessHelper.h" +#include "PsfPowershellScriptRunner.h" +#include "Globals.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; + +// Forward declarations +extern void LogApplicationAndProcessesCollection(); +extern bool IsCurrentOSRS2OrGreater(); +extern std::wstring ReplaceMisleadingSlashVFS(std::wstring inputString); +extern std::wstring ReplaceVariablesInString(std::wstring inputString, bool ReplaceEnvironmentVars, bool ReplacePseudoVars); + +int launcher_main(PCWSTR wargs, int cmdShow) noexcept try +{ + Log(L"PsfFtaCom started."); + + + //Log(L"DEBUG TEMP PsfFtaCom waiting for debugger to attach to process...\n"); + //psf::wait_for_debugger(); + + 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(L"PsfFtaCom waiting for debugger to attach to process...\n"); + psf::wait_for_debugger(); + } + } + } +#endif + + LogApplicationAndProcessesCollection(); + + // Determine process and command line arguments from what was passed in. + std::wstring targetFilePath; + std::wstring targetArgs; + if (wargs != NULL) + { + std::wstring temp; + std::vector parts; + std::wstringstream wss(wargs); + LogString(L"Input arguments", wargs); + + while (std::getline(wss, temp, L'\"')) + parts.push_back(temp); + if (parts.size() >= 3) // 0="\"" 1=command\" 2 and above are rest of the arguments + { + targetFilePath = parts[0] + parts[1]; + for (int inx=2; inx <(int)parts.size(); inx++) + { + targetArgs += parts[inx]; + } + targetFilePath = ReplaceVariablesInString(targetFilePath, true, true); + targetArgs = ReplaceVariablesInString(targetArgs, true, true); + LogString(L"TargetFilePath", targetFilePath.c_str()); + LogString(L"TargetArgs", targetArgs.c_str()); + } + else if (parts.size() == 2) + { + targetFilePath = parts[0] + parts[1]; + targetArgs = L""; + LogString(L"TargetFilePath", targetFilePath.c_str()); + LogString(L"TargetArgs", L"***none***"); + } + else + { + Log(L"Error: Invalid command line arguments passed to PsfFtaCom."); + Log(L" Number of parts=%d", parts.size()); + return -1; + } + } + else + { + Log(L"Error: No command line arguments passed to PsfFtaCom."); + return -1; + } + + // Determine currentdirectory for the new process + const wchar_t* dirStr = L""; + auto dirPtr = appConfig->try_get("workingDirectory"); + if (dirPtr != NULL) + dirStr = dirPtr->as_string().wide(); + else + dirStr = L""; + + // At least for now, configured launch paths are relative to the package root + std::filesystem::path packageRoot = PSFQueryPackageRootPath(); + std::wstring dirWstr = dirStr; + dirWstr = ReplaceMisleadingSlashVFS(dirWstr); + dirWstr = ReplaceVariablesInString(dirWstr, true, true); + std::filesystem::path currentDirectory; + + if (dirWstr.size() < 2 || dirWstr[1] != L':') + { + if (dirWstr.size() == 0) + { + + currentDirectory = packageRoot / targetFilePath.substr(0,targetFilePath.find_last_of('\\')); + } + else + { + currentDirectory = (packageRoot / dirWstr); + } + } + else + { + currentDirectory = dirWstr; + } + + if (targetFilePath._Starts_with(L"VFS\\")) + { + targetFilePath = packageRoot / targetFilePath; + } + + LogString(L"TargetFilePath", targetFilePath.c_str()); + LogString(L"TargetArgs", targetArgs.c_str()); + std::wstring quotedFullLine = L"\"" + targetFilePath + L"\" " + targetArgs.c_str(); + HRESULT hr = StartProcess(targetFilePath.c_str(), quotedFullLine.data(), currentDirectory.c_str(), cmdShow, INFINITE, true, 0, NULL); + if (hr != ERROR_SUCCESS) + { + Log(L"Error return from launching process second try, try again 0x%x.", GetLastError()); + } + + + return 0; +} +catch (...) +{ + ::PSFReportError(widen(message_from_caught_exception()).c_str()); + return win32_from_caught_exception(); +} // launcher_main() + + +int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR args, _In_ int cmdShow) +{ + int ret = launcher_main(args, cmdShow); + + return ret; +} // wWinMain() + + + +/// /////////////////////////////////////////////////////// +/// ///// REGION: UTIITIES +/// /////////////////////////////////////////////////////// + + +void LogApplicationAndProcessesCollection() +{ + auto configRoot = PSFQueryConfigRoot(); + const wchar_t* exeStr = NULL; + const wchar_t* idStr = NULL; + const wchar_t* hasShellVerbsStr = NULL; + if (auto applications = configRoot->as_object().try_get("applications")) + { + for (auto& applicationsConfig : applications->as_array()) + { + try { + auto exeObj = applicationsConfig.as_object().try_get("executable"); + if (exeObj != NULL) + exeStr = exeObj->as_string().wide(); + } + catch (...) {; } // These are now optional, and you can't widen a NULL. + try + { + auto idObj = applicationsConfig.as_object().try_get("id"); + if (idObj != NULL) + idStr = idObj->as_string().wide(); + } + catch (...) {} + try + { + auto hasShellVerbsObj = applicationsConfig.as_object().try_get("shellVerbs"); + if (hasShellVerbsObj != NULL) + hasShellVerbsStr = hasShellVerbsObj->as_string().wide(); + } + catch (...) {} + + + if (exeStr != NULL) + LogString(L"executable", exeStr); + if (idStr != NULL) + LogString(L"id", idStr); + if (hasShellVerbsStr != NULL) + LogString(L"shellVerbs", hasShellVerbsStr); + } + } + +#if _DEBUG + if (auto processes = configRoot->as_object().try_get("processes")) + { + for (auto& processConfig : processes->as_array()) + { + exeStr = processConfig.as_object().get("executable").as_string().wide(); + + if (auto fixups = processConfig.as_object().try_get("fixups")) + { + for (auto& fixupConfig : fixups->as_array()) + { + [[maybe_unused]] auto dllStr = fixupConfig.as_object().try_get("dll")->as_string().wide(); + } + } + } + } +#endif + +} // LogApplicationAndProcessesCollection() + +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); +} // IsCurrentOSRS2OrGreater() + + +// Drop the mistaken first slash in \VFS. +// Sometimes people assume they need to reference the relative path starting with a forward slash, +// If they do it with a VFS folder, the mistake is obvious and we can adjust it for them. +std::wstring ReplaceMisleadingSlashVFS(std::wstring inputString) +{ + if (inputString._Starts_with(L"\\VFS")) + { + return inputString.substr(1); + } + return inputString; +} + +// 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)); +} + diff --git a/PsfFtaCom/PsfFtaCom.vcxproj b/PsfFtaCom/PsfFtaCom.vcxproj new file mode 100644 index 0000000..2d49ee2 --- /dev/null +++ b/PsfFtaCom/PsfFtaCom.vcxproj @@ -0,0 +1,226 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + 17.0 + Win32Proj + {01c18483-3f8e-40e1-b69c-6a1aa65ddfb7} + PsfFtaCom + 10.0.22621.0 + + + + Application + true + v142 + Unicode + + + + + Application + false + v142 + true + Unicode + + + + + Application + true + v142 + Unicode + + + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(ProjectName)32 + false + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(ProjectName)32 + false + false + + + $(ProjectName)64 + false + false + + + $(ProjectName)64 + false + false + + + + Level4 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\CommonSrc;$(SolutionDir)\include;$(SolutionDir)\include\rapidjson;$(SolutionDir)\include\wil;%(AdditionalIncludeDirectories) + stdcpp17 + true + true + MultiThreadedDebug + false + Sync + Cdecl + + + + Windows + true + + + + + Level4 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\CommonSrc;$(SolutionDir)\include;$(SolutionDir)\include\rapidjson;$(SolutionDir)\include\wil;%(AdditionalIncludeDirectories) + stdcpp17 + true + true + MultiThreaded + false + Sync + Cdecl + + + + Windows + true + true + true + + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\CommonSrc;$(SolutionDir)\include;$(SolutionDir)\include\rapidjson;$(SolutionDir)\include\wil;%(AdditionalIncludeDirectories) + stdcpp17 + true + true + MultiThreadedDebug + false + Sync + Cdecl + + + + Windows + true + + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\CommonSrc;$(SolutionDir)\include;$(SolutionDir)\include\rapidjson;$(SolutionDir)\include\wil;%(AdditionalIncludeDirectories) + stdcpp17 + true + true + MultiThreaded + false + Sync + Cdecl + + + + Windows + true + true + true + + + + + NONAMELESSUNION + + + + + {87cce0ac-a7fb-4a31-89d3-c0acdb315ee0} + + + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/PsfFtaCom/PsfFtaCom.vcxproj.filters b/PsfFtaCom/PsfFtaCom.vcxproj.filters new file mode 100644 index 0000000..5a5049f --- /dev/null +++ b/PsfFtaCom/PsfFtaCom.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Header Files + + + \ No newline at end of file diff --git a/PsfFtaCom/PsfPowershellScriptRunner.h b/PsfFtaCom/PsfPowershellScriptRunner.h new file mode 100644 index 0000000..cb8a46c --- /dev/null +++ b/PsfFtaCom/PsfPowershellScriptRunner.h @@ -0,0 +1,673 @@ +#pragma once +#include "psf_runtime.h" +#include "StartProcessHelper.h" +#include "Globals.h" +#include +#include +#include + +#ifndef SW_SHOW +#define SW_SHOW 5 +#endif + +#ifndef SW_HIDE +#define SW_HIDE 0 +#endif + +class PsfPowershellScriptRunner +{ +public: + PsfPowershellScriptRunner() = default; + + void Initialize(const psf::json_object* appConfig, const std::filesystem::path& currentDirectory, const std::filesystem::path& packageRootDirectory) + { + auto startScriptInformationObject = PSFQueryStartScriptInfo(); + auto endScriptInformationObject = PSFQueryEndScriptInfo(); + + //If we want to run at least one script make sure powershell is installed on the computer. + if (startScriptInformationObject || endScriptInformationObject) + { + // throwing out this check as useless + //THROW_HR_IF_MSG(ERROR_NOT_SUPPORTED, !CheckIfPowershellIsInstalled(), "PowerShell is not installed. Please install PowerShell to run scripts in PSF"); + } + + bool stopOnScriptError = false; + auto stopOnScriptErrorObject = appConfig->try_get("stopOnScriptError"); + if (stopOnScriptErrorObject) + { + stopOnScriptError = stopOnScriptErrorObject->as_boolean().get(); + } + + std::wstring scriptExecutionMode = L""; + auto scriptExecutionModeObject = appConfig->try_get("scriptExecutionMode"); // supports options like "-ExecutionPolicy ByPass" + if (scriptExecutionModeObject) + { + scriptExecutionMode = scriptExecutionModeObject->as_string().wstring(); + } + + // Note: the following path must be kept in sync with the FileRedirectionFixup PathRedirection.cpp + std::filesystem::path writablePackageRootPath = psf::known_folder(FOLDERID_LocalAppData) / std::filesystem::path(L"Packages") / psf::current_package_family_name() / LR"(LocalCache\Local\Microsoft\WritablePackageRoot)"; + + if (startScriptInformationObject) + { + this->m_startingScriptInformation = MakeScriptInformation(startScriptInformationObject, stopOnScriptError, scriptExecutionMode, currentDirectory, packageRootDirectory, writablePackageRootPath); + this->m_startingScriptInformation.doesScriptExistInConfig = true; + } + + if (endScriptInformationObject) + { + //Ending script ignores stopOnScriptError. Keep it the default value + this->m_endingScriptInformation = MakeScriptInformation(endScriptInformationObject, false, scriptExecutionMode, currentDirectory, packageRootDirectory, writablePackageRootPath); + this->m_endingScriptInformation.doesScriptExistInConfig = true; + + //Ending script ignores this value. Keep true to make sure + //script runs on the current thread. + this->m_endingScriptInformation.waitForScriptToFinish = true; + this->m_endingScriptInformation.stopOnScriptError = false; + } + } + + bool HasStartingScript() + { + if (this->m_startingScriptInformation.doesScriptExistInConfig) + return true; + return false; + } + bool HasEndingScript() + { + if (this->m_endingScriptInformation.doesScriptExistInConfig) + return true; + return false; + } + + //RunStartingScript should return an error only if stopOnScriptError is true + void RunStartingScript() + { + if (HasStartingScript()) + { + LogString(L"StartingScript commandString", this->m_startingScriptInformation.commandString.c_str()); + LogString(L"StartingScript currentDirectory", this->m_startingScriptInformation.currentDirectory.c_str()); + if (this->m_startingScriptInformation.waitForScriptToFinish) + { + Log(L"StartingScript waitForScriptToFinish=true"); + } + else + { + Log(L"StartingScript waitForScriptToFinish=false"); + } + RunScript(this->m_startingScriptInformation, true); + } + } + + void RunEndingScript() + { + if (HasEndingScript()) + { + LogString(L"EndingScript commandString", this->m_endingScriptInformation.commandString.c_str()); + LogString(L"EndingScript currentDirectory", this->m_endingScriptInformation.currentDirectory.c_str()); + RunScript(this->m_endingScriptInformation, true); + } + } + + void RunOtherScript(const wchar_t* ScriptWrapper, const wchar_t* CurrentDirectory, const wchar_t* InjectCommand, const wchar_t* InjectCommandArgs, bool inside) + { + ScriptInformation scriptStruct; + scriptStruct.PsPath = PathToPowershell(); + scriptStruct.doesScriptExistInConfig = true; // fake it out + scriptStruct.shouldRunOnce = false; + scriptStruct.timeout = INFINITE; + scriptStruct.showWindowAction = SW_HIDE; // hide the powershell script, but cmd may show. + scriptStruct.waitForScriptToFinish = true; + scriptStruct.stopOnScriptError = false; + + scriptStruct.scriptPath = ScriptWrapper; + scriptStruct.commandString = scriptStruct.PsPath; + scriptStruct.commandString.append(L" -ExecutionPolicy Bypass "); + scriptStruct.commandString.append(L" -file \""); + scriptStruct.commandString.append(ScriptWrapper); + scriptStruct.commandString.append(L"\" "); + scriptStruct.commandString.append(PSFQueryPackageFamilyName()); + scriptStruct.commandString.append(L" "); + + scriptStruct.commandString.append(L" "); + scriptStruct.commandString.append(PSFQueryApplicationId()); + scriptStruct.commandString.append(L" "); + + scriptStruct.commandString.append(L"\""); + scriptStruct.commandString.append(InjectCommand); + scriptStruct.commandString.append(L"\" "); + + scriptStruct.commandString.append(InjectCommandArgs); + scriptStruct.currentDirectory = CurrentDirectory; + scriptStruct.packageRoot = PSFQueryPackageRootPath(); + LogString(L"Script Launch to inject commandString", scriptStruct.commandString.c_str()); + LogString(L"Script Launch using currentDirectory", scriptStruct.currentDirectory.c_str()); + RunScript(scriptStruct, inside); + //Log(L"RunOtherScript returned."); + } + +private: +#if NOTMOVEDTOSTARTINFO_HELPER + struct MyProcThreadAttributeList + { + private: + // Implementation notes: + // Currently (Windows 10 21H1 and some previous releases plus Windows 11 21H2), the default + // behavior for most child processes is that they run inside the container. The two known + // exceptions to this are the conhost and cmd.exe processes. We generally don't care about + // the conhost processes. + // + // The documentation on these can be found here: + //https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute + + // The PROC_THREAD_ATTRIBUTE_DESKTOP_APP_POLICY attribute has some settings that can impact this. + // 0x04 is equivalent to PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_OVERRIDE. + // When used in a process creation call it affects only the new process being created, and + // forces the new process to start inside the container, if possible. + // 0x01 is equivalent to PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_ENABLE_PROCESS_TREE + // When used in a process creation call, it ONLY affects child processes of the process being + // created, meaning that grandshildren can/will break away from running inside the container used by + // that new process. + // 0x02 is equivalent to PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_DISABLE_PROCESS_TREE + // When used in a process creation call, it ONLY affects child processes of the process being + // created, meaning that grandshildren can/will NOT break away from running inside the container used by + // that new process. + // + // This PSF code uses the attribute to cause: + // 1. Create a Powershell process in the same container as PSF. + // 2. Any process that Powershell stats will also be in the same container as PSF. + // This means that powershell, and any windows it makes, will have the same restrictions as PSF. + DWORD createInContainerAttribute = 0x02; + DWORD createOutsideContainerAttribute = 0x04; + + // Processes running inside the container run at a different level (Low) that uncontained processes, + // and the default behavior is to have the child process run at the same level. This can be overridden + // by using PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL. + // + // The code here currently does not set this level as we prefer to keep the same level anyway. + //DWORD protectionLevel = PROTECTION_LEVEL_SAME; + + // Should it become neccessary to change more than one attribute, the number of attributes will need + // to be modified in both initialization calls in the CTOR. + + std::unique_ptr<_PROC_THREAD_ATTRIBUTE_LIST> attributeList; + + public: + + MyProcThreadAttributeList(bool inside) + { + // Ffor example of this code with two attributes see: https://github.com/microsoft/terminal/blob/main/src/server/Entrypoints.cpp + SIZE_T AttributeListSize; //{}; + InitializeProcThreadAttributeList(nullptr, 1, 0, &AttributeListSize); + attributeList = std::unique_ptr<_PROC_THREAD_ATTRIBUTE_LIST>(reinterpret_cast<_PROC_THREAD_ATTRIBUTE_LIST*>(new char[AttributeListSize])); + //attributeList = std::unique_ptr<_PROC_THREAD_ATTRIBUTE_LIST>(reinterpret_cast<_PROC_THREAD_ATTRIBUTE_LIST*>(HeapAlloc(GetProcessHeap(), 0, AttributeListSize))); + THROW_LAST_ERROR_IF_MSG( + !InitializeProcThreadAttributeList( + attributeList.get(), + 1, + 0, + &AttributeListSize), + "Could not initialize the proc thread attribute list."); + + // 18 stands for + // PROC_THREAD_ATTRIBUTE_DESKTOP_APP_POLICY + // this is the attribute value we want to add + if (inside) + { + THROW_LAST_ERROR_IF_MSG( + !UpdateProcThreadAttribute( + attributeList.get(), + 0, + ProcThreadAttributeValue(18, FALSE, TRUE, FALSE), + &createInContainerAttribute, + sizeof(createInContainerAttribute), + nullptr, + nullptr), + "Could not update Proc thread attribute for DESKTOP_APP_POLICY (inside)."); + } + else + { + THROW_LAST_ERROR_IF_MSG( + !UpdateProcThreadAttribute( + attributeList.get(), + 0, + ProcThreadAttributeValue(18, FALSE, TRUE, FALSE), + &createOutsideContainerAttribute, + sizeof(createOutsideContainerAttribute), + nullptr, + nullptr), + "Could not update Proc thread attribute for DESKTOP_APP_POLICY (outside)."); + } + + // 11 stands for + // PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL + // this is the attribute value we want to add + // + //THROW_LAST_ERROR_IF_MSG( + // !UpdateProcThreadAttribute( + // attributeList.get(), + // 0, + // ProcThreadAttributeValue(11, FALSE, TRUE, FALSE), + // &protectionLevel, + // sizeof(protectionLevel), + // nullptr, + // nullptr), + // "Could not update Proc thread attribute for PROTECTION_LEVEL."); + } + + ~MyProcThreadAttributeList() + { + DeleteProcThreadAttributeList(attributeList.get()); + } + + LPPROC_THREAD_ATTRIBUTE_LIST get() + { + return attributeList.get(); + } + + }; +#endif + + struct ScriptInformation + { + std::wstring PsPath; + std::wstring scriptPath; + std::wstring commandString; + DWORD timeout = INFINITE; + bool shouldRunOnce = true; + int showWindowAction = SW_HIDE; + bool waitForScriptToFinish = true; + bool stopOnScriptError = false; + std::filesystem::path currentDirectory; + std::filesystem::path packageRoot; + bool doesScriptExistInConfig = false; + }; + + ScriptInformation m_startingScriptInformation; + ScriptInformation m_endingScriptInformation; + MyProcThreadAttributeList m_AttributeListInside = MyProcThreadAttributeList(true, true, false); + MyProcThreadAttributeList m_AttributeListOutside = MyProcThreadAttributeList(true, false, false); + + void RunScript(ScriptInformation& script, bool inside) + { + if (!script.doesScriptExistInConfig) + { + return; + } + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !DoesScriptExist(script.scriptPath, script.currentDirectory)); + + bool canScriptRun = false; + THROW_IF_FAILED(CheckIfShouldRun(script.shouldRunOnce, canScriptRun)); + + if (!canScriptRun) + { + Log(L"Script has already been run and is marked to run once."); + return; + } + + if (script.waitForScriptToFinish) + { + HRESULT startScriptResult; + if (inside) + { + //LogString(L"DEBUG: Starting the script (inside) and waiting to finish", script.commandString.data()); + startScriptResult = StartProcess(script.PsPath.c_str(), script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, true, 0, m_AttributeListInside.get()); + } + else + { + //LogString(L"DEBUG: Starting the script (outside) and waiting to finish", script.commandString.data()); + startScriptResult = StartProcess(script.PsPath.c_str(), script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, true, 0, m_AttributeListOutside.get()); + } + //HRESULT startScriptResult = StartProcess(nullptr, script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, true, 0, nullptr); + if (startScriptResult == 0xC000013A) + { + Log(L"Debug: Script process was closed by user action."); + } + else if (startScriptResult != ERROR_SUCCESS) + { + Log(L"Debug: Error return from script process 0x%x LastError=0x%x", startScriptResult, GetLastError()); + } + else + { + //Log(L"Debug: Script returns without error"); + } + if (script.stopOnScriptError) + { + THROW_IF_FAILED(startScriptResult); + } + } + else + { + //We don't want to stop on an error and we want to run async + if (inside) + { + //LogString(L"DEBUG: Starting the script (inside) without waiting to finish", script.commandString.data()); + std::thread pwrShellThread = std::thread(StartProcess, script.PsPath.c_str(), script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, true, 0, m_AttributeListInside.get()); + pwrShellThread.detach(); + } + else + { + //LogString(L"DEBUG: Starting the script (outside) without waiting to finish", script.commandString.data()); + std::thread pwrShellThread = std::thread(StartProcess, script.PsPath.c_str(), script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, true, 0, m_AttributeListOutside.get()); + pwrShellThread.detach(); + } + } + } + + ScriptInformation MakeScriptInformation(const psf::json_object* scriptInformation, bool stopOnScriptError, std::wstring scriptExecutionMode, std::filesystem::path currentDirectory, std::filesystem::path packageRoot, std::filesystem::path packageWritableRoot) + { + ScriptInformation scriptStruct; + scriptStruct.PsPath = PathToPowershell(); + scriptStruct.scriptPath = ReplacePsuedoRootVariables(GetScriptPath(*scriptInformation), packageRoot, packageWritableRoot); + scriptStruct.commandString = ReplacePsuedoRootVariables(MakeCommandString(*scriptInformation, scriptStruct.PsPath, scriptExecutionMode, scriptStruct.scriptPath, packageRoot), packageRoot, packageWritableRoot); + scriptStruct.timeout = GetTimeout(*scriptInformation); + scriptStruct.shouldRunOnce = GetRunOnce(*scriptInformation); + scriptStruct.showWindowAction = GetShowWindowAction(*scriptInformation); + scriptStruct.waitForScriptToFinish = GetWaitForScriptToFinish(*scriptInformation); + scriptStruct.stopOnScriptError = stopOnScriptError; + scriptStruct.currentDirectory = currentDirectory; + scriptStruct.packageRoot = packageRoot; + + //Async script run with a termination on failure is not a supported scenario. + //Supporting this scenario would mean force terminating an executing user process + //if the script fails. + if (stopOnScriptError && !scriptStruct.waitForScriptToFinish) + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_BAD_CONFIGURATION), "PSF does not allow stopping on a script error and running asynchronously. Please either remove stopOnScriptError or add a wait"); + } + + return scriptStruct; + } + + + std::wstring ReplacePsuedoRootVariables(std::wstring inString, std::filesystem::path packageRoot, std::filesystem::path packageWritableRoot) + { + //Allow for a substitution in the strings for a new pseudo variable %MsixPackageRoot% so that arguments can point to files + //inside the package using a syntax relative to the package root rather than rely on VFS pathing which can't kick in yet. + std::wstring outString = inString; + std::wstring var2rep1 = L"%MsixPackageRoot%"; + std::wstring var2rep2 = L"%MsixWritablePackageRoot%"; + + std::wstring::size_type pos1 = 0u; + std::wstring repargs1 = packageRoot.c_str(); + while ((pos1 = outString.find(var2rep1, pos1)) != std::string::npos) { + outString.replace(pos1, var2rep1.length(), repargs1); + pos1 += repargs1.length(); + } + std::wstring::size_type pos2 = 0u; + std::wstring repargs2 = packageWritableRoot.c_str(); + while ((pos2 = outString.find(var2rep2, pos2)) != std::string::npos) { + outString.replace(pos2, var2rep2.length(), repargs2); + pos2 += repargs2.length(); + } + return outString; + } + + std::wstring Dequote(std::wstring inString) + { + // Remove quotation-like marks around edges of a string reference to a file, if present. + if (inString.length() > 2) + { + if (inString[0] == L'\"' && inString[inString.length() - 1] == L'\"') + { + return inString.substr(1, inString.length() - 2); + } + if (inString[0] == L'\'' && inString[inString.length() - 1] == L'\'') + { + return inString.substr(1, inString.length() - 2); + } + if (inString.length() > 4) + { + // Check for Powershell style references too + if (inString[0] == L'`' && inString[1] == L'\'' && inString[inString.length() - 2] == L'`' && inString[inString.length() - 1] == L'\'') + { + return inString.substr(2, inString.length() - 4); + } + if (inString[0] == L'`' && inString[1] == L'\"' && inString[inString.length() - 2] == L'`' && inString[inString.length() - 1] == L'\"') + { + return inString.substr(2, inString.length() - 4); + } + } + } + return inString; + } + + std::wstring EscapeFilenameForPowerShell(std::filesystem::path inputPath) + { + std::wstring outString = inputPath.c_str(); + std::wstring::size_type pos = 0u; + std::wstring var2rep = L" "; + std::wstring repargs = L"` "; + while ((pos = outString.find(var2rep, pos)) != std::string::npos) { + outString.replace(pos, var2rep.length(), repargs); + pos += repargs.length(); + } + return outString; + + } + + std::wstring MakeCommandString(const psf::json_object& scriptInformation, const std::wstring& psPath, const std::wstring& scriptExecutionMode, const std::wstring& scriptPath, const std::filesystem::path packageRoot) + { + //std::filesystem::path SSWrapperFileName = L"StartingScriptWrapper.ps1"; + std::filesystem::path SSWrapper = packageRoot / L"StartingScriptWrapper.ps1"; + if (!std::filesystem::exists(SSWrapper)) + { + // The wrapper isn't in this folder, so we should search for it elewhere in the package. + for (const auto& file : std::filesystem::recursive_directory_iterator(packageRoot)) + { + if (file.path().filename().compare(SSWrapper.filename()) == 0) + { + SSWrapper = file.path(); + break; + } + } + } + std::wstring commandString = psPath; + commandString.append(L" "); + commandString.append(scriptExecutionMode); + commandString.append(L" -file \"" + SSWrapper.native() + L"\""); /// StartingScriptWrapper.ps1 "); + commandString.append(L" "); + + // ScriptWrapper uses invoke-expression so we need the expression to launch another powershell to run a file with arguments. + commandString.append(L"\""); // wrapper for the invoked command and arguments + commandString.append(psPath.c_str()); + commandString.append(L" "); + commandString.append(scriptExecutionMode); + commandString.append(L" -file "); + + std::wstring wScriptPath = scriptPath; + LogString(L"MakeCommandString: Input Script path", scriptPath.c_str()); + if (!std::filesystem::exists(scriptPath)) + { + // The wrapper isn't in this folder, so we should search for it elewhere in the package. + for (const auto& file : std::filesystem::recursive_directory_iterator(packageRoot)) + { + if (file.path().filename().compare(scriptPath.c_str()) == 0) + { + wScriptPath = file.path(); + break; + } + } + } + LogString(L"MakeCommandString: post exists search Script path", wScriptPath.c_str()); + const std::filesystem::path dequotedScriptPath = Dequote(wScriptPath); + LogString(L"MakeCommandString: post DeQuote Script path", wScriptPath.c_str()); + std::wstring fixed4PowerShell = dequotedScriptPath; // EscapeFilenameForPowerShell(dequotedScriptPath); + LogString(L"MakeCommandString: Updated Script path", fixed4PowerShell.c_str()); + ///if (dequotedScriptPath.is_absolute()) + ///{ + commandString.append(L"\\"); + commandString.append(L"\""); + commandString.append(fixed4PowerShell); + commandString.append(L"\\"); + commandString.append(L"\""); + ///} + ///else + ///{ + /// commandString.append(L"\'"); + /// commandString.append(L".\\"); + /// commandString.append(fixed4PowerShell); + /// commandString.append(L"\'"); + ///} + + //Script arguments are optional. + auto scriptArgumentsJObject = scriptInformation.try_get("scriptArguments"); + if (scriptArgumentsJObject && scriptArgumentsJObject->as_string().wstring().length() > 0) + { + commandString.append(L" "); + commandString.append(scriptArgumentsJObject->as_string().wide()); + } + + //Add ending quote for the script inside a string literal. + commandString.append(L"\""); + + LogString(L"MakeCommandString: final string", commandString.c_str()); + + return commandString; + } + + const std::wstring GetScriptPath(const psf::json_object& scriptInformation) const + { + //.get throws if the key does not exist. + return scriptInformation.get("scriptPath").as_string().wide(); + } + + DWORD GetTimeout(const psf::json_object& scriptInformation) + { + auto timeoutObject = scriptInformation.try_get("timeout"); + if (timeoutObject) + { + //Timout needs to be in milliseconds. + //Multiple seconds from config by milliseconds. + return (DWORD)(1000 * timeoutObject->as_number().get_unsigned()); + } + + return INFINITE; + } + + bool DoesScriptExist(const std::wstring& scriptPath, std::filesystem::path currentDirectory) + { + std::filesystem::path powershellScriptPath(scriptPath); + bool doesScriptExist = false; + //The file might be on a network drive. + doesScriptExist = std::filesystem::exists(powershellScriptPath); + + //Check on local computer. + if (!doesScriptExist) + { + doesScriptExist = std::filesystem::exists(currentDirectory / powershellScriptPath); + } + + return doesScriptExist; + } + + bool GetRunOnce(const psf::json_object& scriptInformation) + { + auto runOnceObject = scriptInformation.try_get("runOnce"); + if (runOnceObject) + { + return runOnceObject->as_boolean().get(); + } + + return true; + } + + int GetShowWindowAction(const psf::json_object& scriptInformation) + { + auto showWindowObject = scriptInformation.try_get("showWindow"); + if (showWindowObject) + { + bool showWindow = showWindowObject->as_boolean().get(); + if (showWindow) + { + //5 in SW_SHOW. + return SW_SHOW; + } + } + + return SW_HIDE; + } + + bool GetWaitForScriptToFinish(const psf::json_object& scriptInformation) + { + auto waitForStartingScriptToFinishObject = scriptInformation.try_get("waitForScriptToFinish"); + if (waitForStartingScriptToFinishObject) + { + return waitForStartingScriptToFinishObject->as_boolean().get(); + } + + return true; + } + + HRESULT CheckIfShouldRun(bool shouldRunOnce, bool& shouldScriptRun) + { + shouldScriptRun = true; + if (shouldRunOnce) + { + std::wstring runOnceSubKey = L"SOFTWARE\\"; + runOnceSubKey.append(psf::current_package_full_name()); + runOnceSubKey.append(L"\\PSFScriptHasRun "); + + DWORD keyDisposition; + wil::unique_hkey registryHandle; + LSTATUS createResult = RegCreateKeyExW(HKEY_CURRENT_USER, runOnceSubKey.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, ®istryHandle, &keyDisposition); + + if (createResult != ERROR_SUCCESS) + { + return createResult; + } + + if (keyDisposition == REG_OPENED_EXISTING_KEY) + { + shouldScriptRun = false; + } + } + + return S_OK; + } + + bool CheckIfPowershellIsInstalled() + { + wil::unique_hkey registryHandle; + LSTATUS createResult = RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\PowerShell\\1", 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, ®istryHandle, nullptr); + + if (createResult == ERROR_FILE_NOT_FOUND) + { + // If the key cannot be found, powershell is not installed + return false; + } + else if (createResult != ERROR_SUCCESS) + { + // 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; + DWORD bufferSize = sizeof(DWORD); + DWORD type = REG_DWORD; + THROW_IF_WIN32_ERROR_MSG(RegQueryValueExW(registryHandle.get(), L"Install", nullptr, &type, reinterpret_cast(&valueFromRegistry), &bufferSize), + "Error with querying the key to see if PowerShell is installed."); + + if (valueFromRegistry != 1) + { + return false; + } + + return true; + } + std::wstring PathToPowershell() + { + // TODO: Use registry search like in CheckIfPowershellIsInstalled() + std::wstring path = L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; + return path; + } +}; \ No newline at end of file diff --git a/PsfFtaCom/StartMenuCmdScriptWrapper.ps1 b/PsfFtaCom/StartMenuCmdScriptWrapper.ps1 new file mode 100644 index 0000000..fba43c6 --- /dev/null +++ b/PsfFtaCom/StartMenuCmdScriptWrapper.ps1 @@ -0,0 +1,50 @@ +<# +.CMDLET StartMenuCmdWrapperScript.ps1 + +.PARAMETER PackageFamilyName + The PackageFamilyName to be used for the container to run in (eg: PackageName_sighash). + + +.PARAMETER AppID + One of the Application element ID values from the AppXManifest file of the package. + Note: If multiple Application elements are present in the package, it does not matter which one you select. + +.PARAMETER ScriptPath + The location of the cmd/bat script to run. This should be the full path, without quotation marks. + +.PARAMETER ScriptArguments + Optionally arguments to the cmd file (don't include a /c as these are arguments to the script itself, add escaped quotations marks as needed). +#> + +Param ( + + [Parameter(Position=0, Mandatory=$true)] + [string]$PackageFamilyName, + + [Parameter(Position=1, Mandatory=$true)] + [string]$AppID, + + [Parameter(Position=2, Mandatory=$true)] + [string]$ScriptPath, + + [Parameter(Position=3, Mandatory=$false)] + [string]$ScriptArguments +) + +try +{ + write-host "StartMenuCmdScriptWrapper.ps1: Launching script `"$($scriptPath)`" in package $($PackageFamilyName)" + invoke-CommandInDesktopPackage -PackageFamilyName $PackageFamilyName -AppID $AppID -Command "C:\Windows\System32\cmd.exe" -Args "/c `"$($scriptPath)`" $($ScriptArguments) " + write-host "StartMenuCmdScriptWrapper.ps1: returned." + #start-sleep 30 +} +catch +{ + write-host $_.Exception.Message + #ERROR 774 refers to ERROR_ERRORS_ENCOUNTERED. + #This error will be brought up the the user. + start-sleep 20 + exit(774) +} + +exit(0) \ No newline at end of file diff --git a/PsfFtaCom/StartMenuShellLaunchWrapperScript.ps1 b/PsfFtaCom/StartMenuShellLaunchWrapperScript.ps1 new file mode 100644 index 0000000..d0d4016 --- /dev/null +++ b/PsfFtaCom/StartMenuShellLaunchWrapperScript.ps1 @@ -0,0 +1,50 @@ +<# +.CMDLET StartMenuShellLaunchWrapperScript.ps1 + +.PARAMETER PackageFamilyName + The PackageFamilyName to be used for the container to run in (eg: PackageName_sighash). + + +.PARAMETER AppID + One of the Application element ID values from the AppXManifest file of the package. + Note: If multiple Application elements are present in the package, it does not matter which one you select. + +.PARAMETER ScriptPath + The location of the file to run using the local fta. This should be the full path, without quotation marks. + +.PARAMETER ScriptArguments + Optionally arguments to the process started by the fta (add escaped quotations marks as needed). +#> + +Param ( + + [Parameter(Position=0, Mandatory=$true)] + [string]$PackageFamilyName, + + [Parameter(Position=1, Mandatory=$true)] + [string]$AppID, + + [Parameter(Position=2, Mandatory=$true)] + [string]$FilePath, + + [Parameter(Position=3, Mandatory=$false)] + [string]$ScriptArguments +) + +try +{ + write-host "StartMenuShellLaunchWrapperScript.ps1: Launching file `"$($FilePath)`" in package $($PackageFamilyName)" + invoke-CommandInDesktopPackage -PackageFamilyName $PackageFamilyName -AppID $AppID -Command "$($FilePath)" -Args "`"$($ScriptArguments)`"" + write-host "StartMenuShellLaunchWrapperScript.ps1: returned." + #start-sleep 30 +} +catch +{ + write-host $_.Exception.Message + #ERROR 774 refers to ERROR_ERRORS_ENCOUNTERED. + #This error will be brought up the the user. + start-sleep 20 + exit(774) +} + +exit(0) \ No newline at end of file diff --git a/PsfFtaCom/StartProcessHelper.h b/PsfFtaCom/StartProcessHelper.h new file mode 100644 index 0000000..225407e --- /dev/null +++ b/PsfFtaCom/StartProcessHelper.h @@ -0,0 +1,156 @@ +#pragma once +#include +#include +#include "Globals.h" +#include + +HRESULT StartProcess(LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR currentDirectory, int cmdShow, DWORD timeout, bool inheritHandles, DWORD initialCreationFlags, LPPROC_THREAD_ATTRIBUTE_LIST attributeList = nullptr) +{ + + STARTUPINFOEXW startupInfoEx = + { + { + sizeof(startupInfoEx) + , nullptr // lpReserved + , nullptr // lpDesktop + , nullptr // lpTitle + , 0 // dwX + , 0 // dwY + , 0 // dwXSize + , 0 // swYSize + , 0 // dwXCountChar + , 0 // dwYCountChar + , 0 // dwFillAttribute + , STARTF_USESHOWWINDOW // dwFlags + , static_cast(cmdShow) // wShowWindow + } + }; + + PROCESS_INFORMATION processInfo{}; + + startupInfoEx.lpAttributeList = attributeList; + DWORD CreationFlags = initialCreationFlags; // 0; + if (attributeList != nullptr) + { + CreationFlags = EXTENDED_STARTUPINFO_PRESENT; + } + + BOOL bErr = ::CreateProcessW( + applicationName, + commandLine, + nullptr, nullptr, // Process/ThreadAttributes + inheritHandles, //true, // InheritHandles + CreationFlags, + nullptr, // Environment + currentDirectory, + (LPSTARTUPINFO)&startupInfoEx, + &processInfo); + if (bErr == FALSE) + { + return GetLastError(); + } + + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), processInfo.hProcess == INVALID_HANDLE_VALUE); + DWORD waitResult = ::WaitForSingleObject(processInfo.hProcess, timeout); + RETURN_LAST_ERROR_IF_MSG(waitResult != WAIT_OBJECT_0, "Waiting operation failed unexpectedly."); + + DWORD exitCode; + GetExitCodeProcess(processInfo.hProcess, &exitCode); + + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + return ERROR_SUCCESS; // Some apps return codes even when happy. +} + +HRESULT StartProcessAsUser(HANDLE hUserToken, LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR currentDirectory, int cmdShow, DWORD timeout, bool inheritHandles, DWORD initialCreationFlags, LPPROC_THREAD_ATTRIBUTE_LIST attributeList = nullptr) +{ + + STARTUPINFOEXW startupInfoEx = + { + { + sizeof(startupInfoEx) + , nullptr // lpReserved + , nullptr // lpDesktop + , nullptr // lpTitle + , 0 // dwX + , 0 // dwY + , 0 // dwXSize + , 0 // swYSize + , 0 // dwXCountChar + , 0 // dwYCountChar + , 0 // dwFillAttribute + , STARTF_USESHOWWINDOW // dwFlags + , static_cast(cmdShow) // wShowWindow + } + }; + + PROCESS_INFORMATION processInfo{}; + + startupInfoEx.lpAttributeList = attributeList; + DWORD CreationFlags = initialCreationFlags; // 0; + if (attributeList != nullptr) + { + CreationFlags = EXTENDED_STARTUPINFO_PRESENT; + } + + BOOL bErr = ::CreateProcessAsUserW( + hUserToken, + applicationName, + commandLine, + nullptr, nullptr, // Process/ThreadAttributes + inheritHandles, //true, // InheritHandles + CreationFlags, + nullptr, // Environment + currentDirectory, + (LPSTARTUPINFO)&startupInfoEx, + &processInfo); + if (bErr == FALSE) + { + return GetLastError(); + } + + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), processInfo.hProcess == INVALID_HANDLE_VALUE); + DWORD waitResult = ::WaitForSingleObject(processInfo.hProcess, timeout); + RETURN_LAST_ERROR_IF_MSG(waitResult != WAIT_OBJECT_0, "Waiting operation failed unexpectedly."); + + DWORD exitCode; + GetExitCodeProcess(processInfo.hProcess, &exitCode); + + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + return ERROR_SUCCESS; // Some apps return codes even when happy. +} + +void StartWithShellExecute(LPCWSTR verb, std::filesystem::path packageRoot, std::filesystem::path exeName, std::wstring exeArgString, LPCWSTR dirStr, int cmdShow, DWORD timeout) +{ + // Non Exe case, use shell launching to pick up local FTA + // Normally verb should be a nullptr. + auto nonExePath = packageRoot / exeName; + + SHELLEXECUTEINFO shex = { + sizeof(shex) + , SEE_MASK_NOCLOSEPROCESS + , (HWND)nullptr + , verb + , nonExePath.c_str() + , exeArgString.c_str() + , dirStr ? (packageRoot / dirStr).c_str() : nullptr + , static_cast(cmdShow) + }; + + Log("\tUsing Shell launch: %ls %ls", shex.lpFile, shex.lpParameters); + THROW_LAST_ERROR_IF_MSG( + !ShellExecuteEx(&shex), + "ERROR: Failed to create detoured shell process"); + + THROW_LAST_ERROR_IF(shex.hProcess == INVALID_HANDLE_VALUE); + DWORD exitCode = ::WaitForSingleObject(shex.hProcess, timeout); + + // Don't throw an error as we should assume that the process would have appropriately made indications to the user. Log for debug purposes only. + Log("PsfLauncher: Shell Launch: process returned exit code 0x%x", exitCode); + + CloseHandle(shex.hProcess); +} + diff --git a/PsfFtaCom/StartingScriptWrapper.ps1 b/PsfFtaCom/StartingScriptWrapper.ps1 new file mode 100644 index 0000000..ee8aac3 --- /dev/null +++ b/PsfFtaCom/StartingScriptWrapper.ps1 @@ -0,0 +1,26 @@ +<# +.PARAMETER ScriptPathAndArguments + The location of the script to run and the arguments. + +.PARAMETER $errorActionPreferenceForScript + Sets the Error Action PRefrence for this script +#> + +Param ( + [Parameter(Mandatory=$true)] + [string]$ScriptPathAndArguments +) + +try +{ + invoke-expression $scriptPathAndArguments +} +catch +{ + write-host $_.Exception.Message + #ERROR 774 refers to ERROR_ERRORS_ENCOUNTERED. + #This error will be brought up the the user. + exit(774) +} + +exit(0) \ No newline at end of file diff --git a/fixups/DynamicLibraryFixup/DynamicLibraryFixup.vcxproj b/fixups/DynamicLibraryFixup/DynamicLibraryFixup.vcxproj index b1f991c..721755e 100644 --- a/fixups/DynamicLibraryFixup/DynamicLibraryFixup.vcxproj +++ b/fixups/DynamicLibraryFixup/DynamicLibraryFixup.vcxproj @@ -43,7 +43,7 @@ 16.0 {40F9058D-8059-4ED4-859E-7A548A73CA4F} - 10.0 + 10.0.22621.0 diff --git a/fixups/EnvVarFixup/EnvVarFixup.vcxproj b/fixups/EnvVarFixup/EnvVarFixup.vcxproj index 92e6a0d..4acf0cb 100644 --- a/fixups/EnvVarFixup/EnvVarFixup.vcxproj +++ b/fixups/EnvVarFixup/EnvVarFixup.vcxproj @@ -23,7 +23,7 @@ Win32Proj {aa616ed2-6783-40ae-9197-b257e8b17690} EnvVarFixup - 10.0 + 10.0.22621.0 diff --git a/fixups/MFRFixup/MFRFixup.vcxproj b/fixups/MFRFixup/MFRFixup.vcxproj index d6806c7..50705f1 100644 --- a/fixups/MFRFixup/MFRFixup.vcxproj +++ b/fixups/MFRFixup/MFRFixup.vcxproj @@ -28,7 +28,7 @@ Win32Proj {e65c064c-5a3c-422e-a57c-116853eeacd6} MFRFixup - 10.0 + 10.0.22621.0 DynamicLibrary diff --git a/tests/fixups/CompositionTestFixup/CompositionTestFixup.vcxproj b/tests/fixups/CompositionTestFixup/CompositionTestFixup.vcxproj index a4ed367..5d03baf 100644 --- a/tests/fixups/CompositionTestFixup/CompositionTestFixup.vcxproj +++ b/tests/fixups/CompositionTestFixup/CompositionTestFixup.vcxproj @@ -30,7 +30,7 @@ 16.0 {D823682D-A4F6-4F4E-A2BB-D1E28BCC06F5} - 10.0 + 10.0.22621.0 DynamicLibrary diff --git a/tests/fixups/MultiByteToWideCharTestFixup/MultiByteToWideCharTestFixup.vcxproj b/tests/fixups/MultiByteToWideCharTestFixup/MultiByteToWideCharTestFixup.vcxproj index d54a400..41cfb7e 100644 --- a/tests/fixups/MultiByteToWideCharTestFixup/MultiByteToWideCharTestFixup.vcxproj +++ b/tests/fixups/MultiByteToWideCharTestFixup/MultiByteToWideCharTestFixup.vcxproj @@ -30,7 +30,7 @@ 16.0 {0C1F7A43-65DE-4460-A9EB-F44F40AF0968} - 10.0 + 10.0.22621.0 DynamicLibrary diff --git a/tests/fixups/WaitForDebuggerFixup/WaitForDebuggerFixup.vcxproj b/tests/fixups/WaitForDebuggerFixup/WaitForDebuggerFixup.vcxproj index 0155d6d..d7d4932 100644 --- a/tests/fixups/WaitForDebuggerFixup/WaitForDebuggerFixup.vcxproj +++ b/tests/fixups/WaitForDebuggerFixup/WaitForDebuggerFixup.vcxproj @@ -34,7 +34,7 @@ 16.0 {76053BA2-AB6B-4F27-90E1-EE5BFE2EFA70} - 10.0 + 10.0.22621.0 DynamicLibrary