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