Skip to content

Commit

Permalink
Merge pull request #230 from npuvvada/telemetry_events_master
Browse files Browse the repository at this point in the history
Merge InPackageContext, PowerShell script errorhandling and telemetry events from develop to master
  • Loading branch information
npuvvada authored Feb 24, 2023
2 parents 0f35538 + c4daede commit 0211fd2
Show file tree
Hide file tree
Showing 43 changed files with 887 additions and 191 deletions.
4 changes: 0 additions & 4 deletions PsfLauncher/Globals.h

This file was deleted.

3 changes: 0 additions & 3 deletions PsfLauncher/PsfLauncher.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@
<ClInclude Include="Logger.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="Globals.h">
<Filter>inc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="StartingScriptWrapper.ps1" />
Expand Down
110 changes: 48 additions & 62 deletions PsfLauncher/PsfPowershellScriptRunner.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#pragma once
#include "psf_runtime.h"
#include "StartProcessHelper.h"
#include "Globals.h"
#include <wil\resource.h>
#include <known_folders.h>
#include "proc_helper.h"
#include "psf_tracelogging.h"

#ifndef SW_SHOW
#define SW_SHOW 5
Expand All @@ -13,6 +14,7 @@
#define SW_HIDE 0
#endif


class PsfPowershellScriptRunner
{
public:
Expand Down Expand Up @@ -89,64 +91,6 @@ class PsfPowershellScriptRunner
}

private:
struct MyProcThreadAttributeList
{
private:
// To make sure the attribute value persists we cache it here.
// 0x02 is equivalent to PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_DISABLE_PROCESS_TREE
// The documentation can be found here:
//https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute
// This attribute tells PSF to
// 1. Create Powershell in the same container as PSF.
// 2. Any windows Powershell makes 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;
std::unique_ptr<_PROC_THREAD_ATTRIBUTE_LIST> attributeList;

public:

MyProcThreadAttributeList()
{
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]));

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
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.");
}

~MyProcThreadAttributeList()
{
DeleteProcThreadAttributeList(attributeList.get());
}

LPPROC_THREAD_ATTRIBUTE_LIST get()
{
return attributeList.get();
}

};

struct ScriptInformation
{
std::wstring scriptPath;
Expand All @@ -163,7 +107,7 @@ class PsfPowershellScriptRunner

ScriptInformation m_startingScriptInformation;
ScriptInformation m_endingScriptInformation;
MyProcThreadAttributeList m_AttributeList;
ProcThreadAttributeList m_AttributeList;

void RunScript(ScriptInformation& script)
{
Expand All @@ -182,9 +126,10 @@ class PsfPowershellScriptRunner
return;
}

DWORD exitCode = ERROR_SUCCESS;
if (script.waitForScriptToFinish)
{
HRESULT startScriptResult = StartProcess(nullptr, script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, m_AttributeList.get());
HRESULT startScriptResult = StartProcess(nullptr, script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, m_AttributeList.get(), &exitCode);

if (script.stopOnScriptError)
{
Expand All @@ -194,9 +139,30 @@ class PsfPowershellScriptRunner
else
{
//We don't want to stop on an error and we want to run async
std::thread pwrShellThread = std::thread(StartProcess, nullptr, script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, m_AttributeList.get());
std::thread pwrShellThread = std::thread(StartProcess, nullptr, script.commandString.data(), script.currentDirectory.c_str(), script.showWindowAction, script.timeout, m_AttributeList.get(), &exitCode);
pwrShellThread.detach();
}

if (exitCode != ERROR_SUCCESS)
{
// when powershell fails to run, reset first run of the powershell(when "runOnce" is set) till powershell successfully runs once
THROW_IF_FAILED(resetFirstRunStatus(script.shouldRunOnce));
}

DWORD execPolicyFailExitCode = 0x01;
if (exitCode == execPolicyFailExitCode)
{
MessageBoxEx(NULL, (script.scriptPath + std::wstring(L" failed due to an execution policy restriction. To change the executing policy, execute \"Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine\" and re-run the application.")).c_str(), L"Package Support Framework", MB_OK | MB_ICONWARNING, 0);
}
else if (exitCode != ERROR_SUCCESS)
{
std::wstring exitCodeStr = std::to_wstring(exitCode);
MessageBoxEx(NULL, (script.scriptPath + std::wstring(L" execution failed with Exit Code ") + exitCodeStr + std::wstring(L". To fix this, please run ") + script.scriptPath + std::wstring(L" standalone in a PowerShell window and confirm that the value of $LASTEXITCODE is 0 (Success) before using it with PSF.")).c_str(), L"Package Support Framework", MB_OK | MB_ICONWARNING, 0);
}

const wchar_t* scriptType = (&script == &(this->m_startingScriptInformation)) ? L"StartScript" : L"EndScript";
psf::TraceLogScriptInformation(scriptType, script.scriptPath.c_str(), script.commandString.c_str(), script.waitForScriptToFinish,
script.timeout, script.shouldRunOnce, script.showWindowAction, exitCode);
}

ScriptInformation MakeScriptInformation(const psf::json_object* scriptInformation, bool stopOnScriptError, std::wstring scriptExecutionMode, std::filesystem::path currentDirectory, std::filesystem::path packageRoot, std::filesystem::path packageWritableRoot)
Expand Down Expand Up @@ -445,6 +411,26 @@ class PsfPowershellScriptRunner
return S_OK;
}

// resets first run status of powershell by deleting "PSFScriptHasRun" registry key in appplication package hive.
HRESULT resetFirstRunStatus(bool shouldRunOnce)
{
if (shouldRunOnce)
{
std::wstring runOnceSubKey = L"SOFTWARE\\";
runOnceSubKey.append(psf::current_package_full_name());
runOnceSubKey.append(L"\\PSFScriptHasRun ");

LSTATUS deleteResult = RegDeleteKeyExW(HKEY_CURRENT_USER, runOnceSubKey.c_str(), KEY_WOW64_64KEY, 0);

if (deleteResult != ERROR_SUCCESS)
{
return deleteResult;
}
}

return S_OK;
}

bool CheckIfPowershellIsInstalled()
{
wil::unique_hkey registryHandle;
Expand Down
43 changes: 41 additions & 2 deletions PsfLauncher/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Based on the manifest's application ID, the `psfLauncher` looks for the app's la

PSF Launcher also supports running an additional "monitor" app, intended for PsfMonitor. You use PsfMonitor to view the output in conjuction with TraceFixup injection configured to output to events.

PSF Launcher also supports running external processes in package context which can be enabled by setting "inPackageContext" field.

Finally, PsfLauncher also support some limited PowerShell scripting.

## Configuration
Expand Down Expand Up @@ -288,6 +290,35 @@ Note that the scriptPath must point to a powershell ps1 file; it may be a full p

The use of scriptExecutionMode may only be necessary in environments when Group Policy setting of default PowerShell ExecutionPolicy is expressed.

### Example 5
This example shows using "inPackageContext" feature to run every process spanned by an executable in same package context.

```json
{
"applications": [
{
"id": "Sample",
"executable": "Sample.exe",
"inPackageContext": true
}
]
}
```

```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<applications>
<application>
<id>Sample</id>
<executable>Sample.exe</executable>
<inPackageContext>true</inPackageContext>
</application>
</applications>
</configuration>
```
This allows every process spanned by Sample.exe to run in the same package context. For example, if Sample.exe triggers cmd.exe outside of the package(C:\Windows\System32\cmd.exe). This configuration allows to create cmd.exe in the same package context of current application.

### Json Schema

| Array | key | Value |
Expand All @@ -307,15 +338,16 @@ The use of scriptExecutionMode may only be necessary in environments when Group
| applications | startScript | (Optional) If present, used to define a PowerShell script that will be run prior running the application executable. |
| | | `'waitForScriptToFinish'` - (Optional, default=true) Boolean. When true, PsfLauncher will wait for the script to complete or timeout before running the application executable. |
| | | `'timeout'` - (Optional, default is none) Expressed in ms. Only applicable if waitForScriptToFinish is true. If a timeout occurs it is treated as an error for the purpose of `'stopOnScriptError'`. The value 0 means an immediate timeout, if you do not want a timeout do not specify a value. |
| | | `'runOnce'` - (Optional, default=false) Boolean. When true, the script will only be run the first time the user runs the application. |
| | | `'runOnce'` - (Optional, default=true) Boolean. When true, the script will only be run the first time the user runs the application. If script fails to run, it will be run on every launch of application till it runs successfully once. |
| | | `'showWindow'` - (Optional, default=true). Boolean. When false, the PowerShell window is hidden. |
| | | `'scriptPath'` - Relative or full path to a ps1 file. May be in package or on a network share. Use of pseudo-variables or environment variables are supported. |
| | | `'scriptArguments'` - (Optional) Arguments for the `'scriptPath'` PowerShell file. Use of pseudo-variables or environment variables are supported. Multiple arguments(and arguments with space) are provided by enclosing each argument in single quote. ("scriptArguments": "'<Arg1>' '<Arg2>'"). |
| applications | endScript | (Optional) If present, used to define a PowerShell script that will be run after completion of the application executable. |
| | | `'runOnce'` - (Optional, default=false) Boolean. When true, the script will only be run the first time the user runs the application. |
| | | `'runOnce'` - (Optional, default=true) Boolean. When true, the script will only be run the first time the user runs the application. If script fails to run, it will be run on every launch of application till it runs successfully once. |
| | | `'showWindow'` - (Optional, default=true). Boolean. When false, the PowerShell window is hidden. |
| | | `'scriptPath'` - Relative or full path to a ps1 file. May be in package or on a network share. Use of pseudo-variables or environment variables are supported. |
| | | `'scriptArguments'` - (Optional) Arguments for the `'scriptPath'` PowerShell file. Use of pseudo-variables or environment variables are supported. |
| applications | inPackageContext | (Optional, default=false) Boolean. When true, all the processes spanned by this executable will be run in the same package context. |
| processes | executable | In most cases, this will be the name of the `executable` configured above with the path and file extension removed. Multiple arguments(and arguments with space) are provided by enclosing each argument in single quote. ("scriptArguments": "'<Arg1>' '<Arg2>'").|
| fixups | dll | Package-relative path to the fixup, .msix/.appx to load. |
| fixups | config | (Optional) Controls how the fixup dl behaves. The exact format of this value varies on a fixup-by-fixup basis as each fixup can interpret this "blob" as it wants. |
Expand All @@ -332,6 +364,13 @@ The PSF Launcher supports the use of two special purpose "pseudo-variables". The
| %MsixPackageRoot% | The root folder of the package. While nominally this would be a subfolder under "C:\\Program Files\\WindowsApps" it is possible for the volume to be mounted in other locations. |
| %MsixWritablePackageRoot% | The package specific redirection location for this user when the FileRedirectionFixup is in use. |

### inPackageContext
This Configuration allows all created process from a running executable to operate in same package context. This includes any child processes that might not be part of the package. This might be useful in situations where applications span processes that are outside package but needs to be run in the same package context, or when PSF fixups need to be applied to processes external to the package.

When an external process is launched by an executable, all of its app data is stored in its native app data folder and hence not containerized. This feature allows the app data of the external process to be virtualized in per user per app data folder of the package.

This configuration is not needed when process spanned by the executable are already part of the current package. This feature can be enabled when there is a need of running external process in the current package context.

=======
Submit your own fixup(s) to the community:
1. Create a private fork for yourself
Expand Down
10 changes: 7 additions & 3 deletions PsfLauncher/StartProcessHelper.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#pragma once
#include <windows.h>
#include "Logger.h"
#include "Globals.h"
#include <wil\resource.h>
#include "proc_helper.h"

HRESULT StartProcess(LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR currentDirectory, int cmdShow, DWORD timeout, LPPROC_THREAD_ATTRIBUTE_LIST attributeList = nullptr)
HRESULT StartProcess(LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR currentDirectory, int cmdShow, DWORD timeout, LPPROC_THREAD_ATTRIBUTE_LIST attributeList = nullptr, _Out_ DWORD *exitCode = nullptr)
{

STARTUPINFOEXW startupInfoEx =
Expand All @@ -25,7 +25,6 @@ HRESULT StartProcess(LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR curren
, static_cast<WORD>(cmdShow) // wShowWindow
}
};

PROCESS_INFORMATION processInfo{};

startupInfoEx.lpAttributeList = attributeList;
Expand All @@ -49,9 +48,14 @@ HRESULT StartProcess(LPCWSTR applicationName, LPWSTR commandLine, LPCWSTR curren
"ERROR: Failed to create a process for %ws",
applicationName);


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.");
if (exitCode != nullptr)
{
GetExitCodeProcess(processInfo.hProcess, exitCode);
}
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);

Expand Down
Loading

0 comments on commit 0211fd2

Please sign in to comment.