Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active June 27, 2024 06:20
Show Gist options
  • Save jborean93/a7c181b1ea0a4c0c8b815af9a8cbe508 to your computer and use it in GitHub Desktop.
Save jborean93/a7c181b1ea0a4c0c8b815af9a8cbe508 to your computer and use it in GitHub Desktop.
PowerShell wrapper around CreateProcess that exposes more low level items
# Copyright: (c) 2021, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
using namespace System.Management.Automation
using namespace System.Management.Automation.Host
using namespace System.Runtime.InteropServices
$typeParams = @{
TypeDefinition = @'
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
namespace ProcessEx
{
internal class NativeHelpers
{
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public Int16 X;
public Int16 Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT
{
public IntPtr CompletionKey;
public IntPtr CompletionPort;
}
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle = false;
public SECURITY_ATTRIBUTES()
{
nLength = (UInt32)Marshal.SizeOf(this);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFOW_RAW
{
public UInt32 cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public UInt32 dwFillAttribute;
public StartupInfoFlags dwFlags;
public ShowWindowFlags wShowWindow;
public UInt16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public class STARTUPINFOW
{
public UInt32 cb;
[MarshalAs(UnmanagedType.LPWStr)] public string lpReserved;
[MarshalAs(UnmanagedType.LPWStr)] public string lpDesktop;
[MarshalAs(UnmanagedType.LPWStr)] public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public UInt32 dwFillAttribute;
public StartupInfoFlags dwFlags;
public UInt16 wShowWindow;
public UInt16 cbReserved2;
public SafeHandle lpReserved2 = new SafeNativeHandle(IntPtr.Zero);
public SafeHandle hStdInput = new SafeNativeHandle(IntPtr.Zero);
public SafeHandle hStdOutput = new SafeNativeHandle(IntPtr.Zero);
public SafeHandle hStdError = new SafeNativeHandle(IntPtr.Zero);
public STARTUPINFOW()
{
cb = (UInt32)Marshal.SizeOf(this);
}
}
[StructLayout(LayoutKind.Sequential)]
public class STARTUPINFOEX
{
public STARTUPINFOW startupInfo;
public SafeHandle lpAttributeList = new SafeMemoryBuffer(IntPtr.Zero);
public STARTUPINFOEX()
{
startupInfo = new STARTUPINFOW();
startupInfo.cb = (UInt32)Marshal.SizeOf(this);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
public IntPtr Sid;
public TokenGroupAttributes Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_GROUPS
{
public Int32 GroupCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public SID_AND_ATTRIBUTES[] Groups;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_USER
{
public SID_AND_ATTRIBUTES User;
}
[Flags]
public enum HandleFlags : uint
{
Inherit = 0x00000001,
ProtectFromClose = 0x00000002,
}
public enum JobObjectInformationClass : uint
{
JobObjectAssociateCompletionPortInformation = 7,
}
public enum ProcessThreadAttribute : uint
{
ParentProcess = 0x00020000,
HandleList = 0x00020002,
GroupAffinity = 0x00030003,
PreferredNode = 0x00020004,
IdealProcessor = 0x00030005,
UmsThread = 0x00030006,
MitigationPolicy = 0x00020007,
SecurityCapabilities = 0x00020009,
ProtectionLevel = 0x0002000B,
JobList = 0x0002000D,
ChildProcessPolicy = 0x0002000E,
AllApplicationPackagesPolicy = 0x0002000F,
Win32kFilter = 0x00020010,
DesktopAppPolicy = 0x00020012,
PsuedoConsole = 0x00020016,
}
[Flags]
public enum TokenGroupAttributes : uint
{
Mandatory = 0x00000001,
EnabledByDefault = 0x00000002,
Enabled = 0x00000004,
Owner = 0x00000010,
Integrity = 0x00000020,
IntegrityEnabled = 0x00000040,
Resource = 0x20000000,
LogonId = 0xC0000000,
}
[Flags]
public enum TokenInformationClass
{
User = 1,
LogonSid = 28,
}
[Flags]
public enum UserObjectInfoIndex
{
UOI_FLAGS = 1,
UOI_NAME = 2,
UOI_TYPE = 3,
UOI_USER_SID = 4,
UOI_HEAPSIZE = 5,
UOI_IO = 6,
}
}
internal class NativeMethods
{
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool AssignProcessToJobObject(
SafeHandle hJob,
SafeHandle hProcess);
[DllImport("User32.dll", SetLastError = true)]
public static extern bool CloseDesktop(
IntPtr hDesktop);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(
IntPtr hObject);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern Int32 ClosePseudoConsole(
IntPtr hPC);
[DllImport("User32.dll", SetLastError = true)]
public static extern bool CloseWindowStation(
IntPtr hWinSta);
[DllImport("Userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateEnvironmentBlock(
out SafeEnvironmentBlock lpEnvironment,
SafeHandle hToken,
bool bInherit);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern SafeNativeHandle CreateIoCompletionPort(
IntPtr FileHandle,
IntPtr ExistingCompletionPort,
UIntPtr CompletionKey,
UInt32 NumberOfConcurrentThreads);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeNativeHandle CreateJobObjectW(
SafeHandle lpJobAttributes,
string lpName);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessW(
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
StringBuilder lpCommandLine,
SafeHandle lpProcessAttributes,
SafeHandle lpThreadAttributes,
bool bInheritHandles,
ProcessCreationFlags dwCreationFlags,
SafeHandle lpEnvironment,
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
NativeHelpers.STARTUPINFOEX lpStartupInfo,
out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessAsUserW(
SafeHandle hToken,
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
StringBuilder lpCommandLine,
SafeHandle lpProcessAttributes,
SafeHandle lpThreadAttributes,
bool bInheritHandles,
ProcessCreationFlags dwCreationFlags,
SafeHandle lpEnvironment,
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
NativeHelpers.STARTUPINFOEX lpStartupInfo,
out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithLogonW(
[MarshalAs(UnmanagedType.LPWStr)] string lpUsername,
[MarshalAs(UnmanagedType.LPWStr)] string lpDomain,
IntPtr lpPassword,
LogonFlags dwLogonFlags,
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
StringBuilder lpCommandLine,
ProcessCreationFlags dwCreationFlags,
SafeHandle lpEnvironment,
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
NativeHelpers.STARTUPINFOEX lpStartupInfo,
out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithTokenW(
SafeHandle hToken,
LogonFlags dwLogonFlags,
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
StringBuilder lpCommandLine,
ProcessCreationFlags dwCreationFlags,
SafeHandle lpEnvironment,
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
NativeHelpers.STARTUPINFOEX lpStartupInfo,
out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern Int32 CreatePseudoConsole(
NativeHelpers.COORD size,
SafeHandle hInput,
SafeHandle hOutput,
ConsoleFlags dwFlags,
out SafeConsoleHandle phPC);
[DllImport("Kernel32.dll")]
public static extern void DeleteProcThreadAttributeList(
IntPtr lpAttributeList);
[DllImport("Userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DuplicateHandle(
SafeHandle hSourceProcessHandle,
SafeHandle hSourceHandle,
SafeHandle hTargetProcessHandle,
out IntPtr lpTargetHandle,
UInt32 dwDesiredAccess,
bool bInheritHandle,
DuplicateOptions dwOptions);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr GetCommandLineW();
[DllImport("Kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
[DllImport("Kernel32.dll")]
public static extern Int32 GetCurrentThreadId();
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool GetExitCodeProcess(
SafeHandle hProcess,
out Int32 lpExitCode);
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool GetQueuedCompletionStatus(
SafeHandle CompletionPort,
out UInt32 lpNumberOfBytesTransferred,
out UIntPtr lpCompletionKey,
out IntPtr lpOverlapped,
UInt32 dwMilliseconds);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
public static extern void GetStartupInfoW(
ref NativeHelpers.STARTUPINFOW_RAW lpStartupInfo);
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetThreadDesktop(
Int32 dwThreadId);
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern bool GetTokenInformation(
SafeHandle TokenHandle,
NativeHelpers.TokenInformationClass TokenInformationClass,
IntPtr TokenInformation,
Int32 TokenInformationLength,
out Int32 ReturnLength);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetUserObjectInformationW(
SafeHandle hObj,
NativeHelpers.UserObjectInfoIndex nIndex,
IntPtr pvInfo,
Int32 nLength,
out Int32 lpnLengthNeeded);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool InitializeProcThreadAttributeList(
IntPtr lpAttributeList,
Int32 dwAttributeCount,
UInt32 dwFlags,
ref IntPtr lpSize);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeDesktop OpenDesktopW(
[MarshalAs(UnmanagedType.LPWStr)] string lpszDesktop,
UInt32 dwFlags,
bool fInherit,
DesktopAccessRights dwDesiredAccess);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern SafeNativeHandle OpenProcess(
ProcessAccessRights dwDesiredAccess,
bool bInheritHandle,
Int32 dwProcessId);
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern bool OpenProcessToken(
SafeHandle ProcessHandle,
TokenAccessLevels DesiredAccess,
out SafeNativeHandle TokenHandle);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeWindowStation OpenWindowStationW(
[MarshalAs(UnmanagedType.LPWStr)] string lpszWinSta,
bool fINherit,
StationAccessRights dwDesiredAccess);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern UInt32 ResumeThread(
SafeHandle hThread);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool SetHandleInformation(
SafeHandle hObject,
NativeHelpers.HandleFlags dwMask,
NativeHelpers.HandleFlags dwFlags);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool SetInformationJobObject(
SafeHandle hJob,
NativeHelpers.JobObjectInformationClass JobObjectInformationClass,
IntPtr lpJobObjectInformation,
Int32 cbJobObjectInformationLength);
[DllImport("User32.dll", SetLastError = true)]
public static extern bool SetProcessWindowStation(
SafeHandle hWinSta);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool UpdateProcThreadAttribute(
SafeProcThreadAttribute lpAttributeList,
UInt32 dwFlags,
UIntPtr Attribute,
IntPtr lpValue,
IntPtr cbSize,
IntPtr lpPreviousValue,
IntPtr lpReturnSize);
[DllImport("Kernel32.dll")]
public static extern UInt32 WaitForSingleObject(
SafeHandle hHandle,
UInt32 dwMilliseconds);
}
internal class SafeDesktop : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeDesktop() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.CloseDesktop(handle);
}
}
internal class SafeEnvironmentBlock : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeEnvironmentBlock() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.DestroyEnvironmentBlock(handle);
}
}
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeMemoryBuffer() : base(true) { }
public SafeMemoryBuffer(int cb) : base(true)
{
base.SetHandle(Marshal.AllocHGlobal(cb));
}
public SafeMemoryBuffer(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
}
internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeNativeHandle() : base(true) { }
public SafeNativeHandle(IntPtr handle) : this(handle, true) { }
public SafeNativeHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle)
{
base.SetHandle(handle);
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(handle);
}
}
internal class SafeProcThreadAttribute : SafeHandleZeroOrMinusOneIsInvalid
{
internal List<IntPtr> values = new List<IntPtr>();
public SafeProcThreadAttribute() : base(true) { }
public SafeProcThreadAttribute(int attributeCount) : base(true)
{
if (attributeCount == 0)
{
base.SetHandle(IntPtr.Zero);
return;
}
IntPtr size = IntPtr.Zero;
NativeMethods.InitializeProcThreadAttributeList(IntPtr.Zero, attributeCount, 0, ref size);
IntPtr h = Marshal.AllocHGlobal((int)size);
base.SetHandle(h);
if (!NativeMethods.InitializeProcThreadAttributeList(h, attributeCount, 0, ref size))
throw new Win32Exception(String.Format("InitializeProcThreadAttributeList({0}) failed", attributeCount));
}
protected override bool ReleaseHandle()
{
foreach (IntPtr val in values)
{
Marshal.FreeHGlobal(val);
}
NativeMethods.DeleteProcThreadAttributeList(handle);
Marshal.FreeHGlobal(handle);
return true;
}
}
internal class SafeWindowStation : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeWindowStation() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.CloseWindowStation(handle);
}
}
public class SafeConsoleHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeConsoleHandle() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.ClosePseudoConsole(handle) == 0;
}
}
public class SafeDuplicateHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeHandle _process;
private bool _ownsHandle;
public SafeDuplicateHandle(IntPtr handle, SafeHandle process) : this(handle, process, true) { }
public SafeDuplicateHandle(IntPtr handle, SafeHandle process, bool ownsHandle) : base(true)
{
base.SetHandle(handle);
_process = process;
_ownsHandle = ownsHandle;
}
protected override bool ReleaseHandle()
{
if (_ownsHandle)
{
ProcessMethods.DuplicateHandle(_process, new SafeNativeHandle(handle, false), 0, 0, false,
DuplicateOptions.CloseSource);
}
_process.Dispose();
return true;
}
}
[Flags]
public enum ConsoleColor : uint
{
ForegroundBlue = 0x00000001,
ForegroundGreen = 0x00000002,
ForegroundRead = 0x00000004,
ForegroundIntensity = 0x00000008,
BackgroundBlue = 0x00000010,
BackgroundGreen = 0x00000020,
BackgroundRead = 0x00000040,
BackgroundIntensity = 0x00000080,
}
[Flags]
public enum ConsoleFlags : uint
{
None = 0x00000000,
InheritCursor = 0x00000001,
}
[Flags]
public enum DesktopAccessRights
{
ReadObjects = 0x00000001,
CreateWindow = 0x00000002,
CreateMenu = 0x00000004,
HookControl = 0x00000008,
JournalRecord = 0x00000010,
JournalPlayback = 0x00000020,
Enumerate = 0x00000040,
WriteObjects = 0x00000080,
SwitchDesktop = 0x00000100,
Delete = 0x00010000,
ReadControl = 0x00020000,
WriteDAC = 0x00040000,
WriteOwner = 0x00080000,
StandardRightsRequired = Delete | ReadControl | WriteDAC | WriteOwner,
Synchronize = 0x00100000,
AccessSystemSecurity = 0x01000000,
AllAccess = StandardRightsRequired | 0x1FF,
}
[Flags]
public enum DuplicateOptions : uint
{
CloseSource = 0x00000001,
SameAccess = 0x00000002,
}
[Flags]
public enum LogonFlags : uint
{
None = 0x00000000,
WithProfile = 0x00000001,
NetcredentialsOnly = 0x00000002,
}
[Flags]
public enum ProcessAccessRights
{
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
CreateProcess = 0x00000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
SuspendResume = 0x00000800,
QueryLimitedInformation = 0x00001000,
Delete = 0x00010000,
ReadControl = 0x00020000,
WriteDAC = 0x00040000,
WriteOwner = 0x00080000,
StandardRightsRequired = Delete | ReadControl | WriteDAC | WriteOwner,
Synchronize = 0x00100000,
AccessSystemSecurity = 0x01000000,
AllAccess = StandardRightsRequired | Synchronize | 0x1FFF,
}
[Flags]
public enum ProcessCreationFlags : uint
{
None = 0x00000000,
DebugProcess = 0x00000001,
DebugOnlyThisProcess = 0x00000002,
CreateSuspended = 0x00000004,
DetachedProcess = 0x00000008,
CreateNewConsole = 0x00000010,
NormalPriorityClass = 0x00000020,
IdlePriorityClass = 0x00000040,
HighPriorityClass = 0x00000080,
RealtimePriorityClass = 0x00000100,
CreateNewProcessGroup = 0x00000200,
CreateUnicodeEnvironment = 0x00000400,
CreateSeparateWowVdm = 0x00000800,
CreateSharedWowVdm = 0x00001000,
CreateForceDos = 0x00002000,
BelowNormalPriorityClass = 0x00004000,
AboveNormalPriorityClass = 0x00008000,
InheritParentAffinity = 0x00010000,
InheritCallerPriority = 0x00020000,
CreateProctectedProcess = 0x00040000,
ExtendedStartupInfoPresent = 0x00080000,
ProcessModeBackgroundBegin = 0x00100000,
ProcessModeBackgroundEnd = 0x00200000,
CreateSecureProcess = 0x00400000,
CreateBreakawayFromJob = 0x01000000,
CreatePreserveCodeAuthzLevel = 0x02000000,
CreateDefaultErrorMode = 0x04000000,
CreateNoWindow = 0x08000000,
ProfileUser = 0x10000000,
ProfileKernel = 0x20000000,
ProfileServer = 0x40000000,
CreateIgnoreSystemDefault = 0x80000000,
}
public enum ShowWindowFlags : ushort
{
Hide = 0x0000,
Normal = 0x0001,
ShowNormal = 0x0001,
ShowMinimized = 0x0002,
ShowMaximized = 0x0003,
Maximize = 0x0003,
ShowNoActivate = 0x0004,
Show = 0x0006,
Minimize = 0x0007,
ShowNA = 0x0008,
Restore = 0x0009,
ShowDefault = 0x0010,
ForceMinimize = 0x0011,
}
[Flags]
public enum StartupInfoFlags : uint
{
UseShowWindow = 0x00000001,
UseSize = 0x00000002,
UsePosition = 0x00000004,
UseCountChars = 0x00000008,
UseFillAttribute = 0x00000010,
RunFullScreen = 0x00000020,
ForceOnFeedback = 0x00000040,
ForceOffFeedback = 0x00000080,
UseStdHandles = 0x00000100,
UseHotKey = 0x00000200,
TitleIsLinkName = 0x00000800,
TitleIsAppId = 0x00001000,
PreventPinning = 0x00002000,
UntrustedSource = 0x00008000,
}
[Flags]
public enum StationAccessRights
{
EnumDesktops = 0x00000001,
ReadAttributes = 0x00000002,
AccessClipboard = 0x000000004,
CreateDesktop = 0x00000008,
WriteAttributes = 0x00000010,
AccessGlobalAtoms = 0x00000020,
ExtWindows = 0x00000040,
Enumerate = 0x00000100,
ReadScreen = 0x00000200,
Delete = 0x00010000,
ReadControl = 0x00020000,
WriteDAC = 0x00040000,
WriteOwner = 0x00080000,
StandardRightsRequired = Delete | ReadControl | WriteDAC | WriteOwner,
Synchronize = 0x00100000,
AccessSystemSecurity = 0x01000000,
AllAccess = StandardRightsRequired | 0x37F,
}
[Flags]
public enum ThreadAccessRights
{
Terminate = 0x00000001,
SuspendResume = 0x00000002,
GetContext = 0x00000008,
SetContext = 0x00000010,
SetInformation = 0x00000020,
QueryInformation = 0x00000040,
SetThreadToken = 0x00000080,
Impersonate = 0x00000100,
DirectImpersonation = 0x00000200,
SetLimitedInformation = 0x00000400,
QueryLimitedInformation = 0x00000800,
Delete = 0x00010000,
ReadControl = 0x00020000,
WriteDAC = 0x00040000,
WriteOwner = 0x00080000,
StandardRightsRequired = Delete | ReadControl | WriteDAC | WriteOwner,
Synchronize = 0x00100000,
AccessSystemSecurity = 0x01000000,
AllAccess = StandardRightsRequired | Synchronize | 0xFFF,
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_msg = String.Format("{0} ({1}, Win32ErrorCode {2} - 0x{2:X8})", message, base.Message, errorCode);
}
public override string Message { get { return _msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public class ProcessInformation : IDisposable
{
public string CommandLine { get; internal set; }
public SafeHandle Process { get; internal set; }
public SafeHandle Thread { get; internal set; }
public int ProcessId { get; internal set; }
public int ThreadId { get; internal set; }
public void Dispose()
{
if (Process != null)
Process.Dispose();
if (Thread != null)
Thread.Dispose();
GC.SuppressFinalize(this);
}
~ProcessInformation() { Dispose(); }
}
public abstract class SecurityAttributes<T>
where T : NativeObjectSecurity
{
public bool InheritHandle { get; set; }
public T SecurityDescriptor { get; set; }
}
public class ProcessSecurityAttributes : SecurityAttributes<ProcessSecurity> { }
public class ThreadSecurityAttributes : SecurityAttributes<ThreadSecurity> { }
public class StartupInfo
{
public string Reserved { get; set; }
public string Desktop { get; set; }
public string Title { get; set; }
public Coordinates? Position { get; set; }
public Size? WindowSize { get; set; }
public Size? CountChars { get; set; }
public ConsoleColor FillAttribute { get; set; }
public StartupInfoFlags Flags { get; set; }
public ShowWindowFlags ShowWindow { get; set; }
public byte[] Reserved2 { get; set; }
public SafeHandle StandardInput { get; set; }
public SafeHandle StandardOutput { get; set; }
public SafeHandle StandardError { get; set; }
// ProcThreadAttributes
public SafeHandle ConPTY { get; set; }
public SafeHandle[] InheritedHandles { get; set; }
public Process ParentProcess { get; set; }
}
public class ConsoleMethods
{
public static SafeConsoleHandle CreatePseudoConsole(Coordinates size, SafeHandle input, SafeHandle output,
ConsoleFlags flags)
{
if (size.X > Int16.MaxValue || size.X < Int16.MinValue)
throw new ArgumentException(String.Format("X cannot be larger than {0} or smaller than {1}",
Int16.MaxValue, Int16.MinValue));
if (size.Y > Int16.MaxValue || size.Y < Int16.MinValue)
throw new ArgumentException(String.Format("Y cannot be larger than {0} or smaller than {1}",
Int16.MaxValue, Int16.MinValue));
NativeHelpers.COORD coordinates = new NativeHelpers.COORD()
{
X = (Int16)size.X,
Y = (Int16)size.Y,
};
SafeConsoleHandle handle;
Int32 res = NativeMethods.CreatePseudoConsole(coordinates, input, output, flags, out handle);
if (res != 0)
throw new Win32Exception("CreatePseudoConsole() failed");
return handle;
}
}
public class JobMethods
{
public static void AssignProcessToJobObject(SafeHandle job, SafeHandle process)
{
if (!NativeMethods.AssignProcessToJobObject(job, process))
throw new Win32Exception("AssignProcessToJobObject() failed");
}
public static SafeHandle CreateCompletionPort()
{
SafeNativeHandle ioPort = NativeMethods.CreateIoCompletionPort((IntPtr)(-1), IntPtr.Zero,
UIntPtr.Zero, 1);
if (ioPort.IsInvalid)
throw new Win32Exception("CreateIoCompletionPort() failed");
return ioPort;
}
public static SafeHandle CreateJob<T>(SecurityAttributes<T> jobAttributes, string name) where T : NativeObjectSecurity
{
using (SafeMemoryBuffer secAttrs = ProcessMethods.CreateSecurityAttributes(jobAttributes))
{
SafeNativeHandle job = NativeMethods.CreateJobObjectW(secAttrs, name);
if (job.IsInvalid)
throw new Win32Exception("CreateJobObjectW() failed");
return job;
}
}
}
public class ProcessMethods
{
private delegate Int32 CreateProcessDelegate(string applicationName, StringBuilder commandLine,
SafeHandle processAttributes, SafeHandle threadAttributes, bool inheritHandles,
ProcessCreationFlags creationflags, SafeHandle environment, string currentDirectory,
NativeHelpers.STARTUPINFOEX startupInfo, out NativeHelpers.PROCESS_INFORMATION processInfo,
Dictionary<string, object> ext);
public static ProcessInformation CreateProcess(string applicationName, string commandLine,
ProcessSecurityAttributes processAttributes, ThreadSecurityAttributes threadAttributes,
bool inheritHandles, ProcessCreationFlags creationFlags, IDictionary environment,
string currentDirectory, StartupInfo startupInfo, bool newEnvironment)
{
using (SafeHandle userToken = OpenProcessToken(GetCurrentProcess(), TokenAccessLevels.Query))
{
return CreateProcessInternal(applicationName, commandLine, processAttributes, threadAttributes,
inheritHandles, creationFlags, environment, currentDirectory, startupInfo, newEnvironment,
userToken, "CreateProcess", null, CreateProcessDel);
}
}
private static Int32 CreateProcessDel(string applicationName, StringBuilder commandLine,
SafeHandle processAttributes, SafeHandle threadAttributes, bool inheritHandles,
ProcessCreationFlags creationflags, SafeHandle environment, string currentDirectory,
NativeHelpers.STARTUPINFOEX startupInfo, out NativeHelpers.PROCESS_INFORMATION processInfo,
Dictionary<string, object> ext)
{
bool res = NativeMethods.CreateProcessW(applicationName, commandLine, processAttributes, threadAttributes,
inheritHandles, creationflags, environment, currentDirectory, startupInfo, out processInfo);
return res ? 0 : Marshal.GetLastWin32Error();
}
public static ProcessInformation CreateProcessAsUser(SafeHandle token, string applicationName,
string commandLine, ProcessSecurityAttributes processAttributes, ThreadSecurityAttributes threadAttributes,
bool inheritHandles, ProcessCreationFlags creationFlags, IDictionary environment, string currentDirectory,
StartupInfo startupInfo, bool newEnvironment)
{
Dictionary<string, object> ext = new Dictionary<string, object>()
{
{ "token", token },
};
return CreateProcessInternal(applicationName, commandLine, processAttributes, threadAttributes,
inheritHandles, creationFlags, environment, currentDirectory, startupInfo, newEnvironment, token,
"CreateProcessAsUser", ext, CreateProcessAsUserDel);
}
private static Int32 CreateProcessAsUserDel(string applicationName, StringBuilder commandLine,
SafeHandle processAttributes, SafeHandle threadAttributes, bool inheritHandles,
ProcessCreationFlags creationflags, SafeHandle environment, string currentDirectory,
NativeHelpers.STARTUPINFOEX startupInfo, out NativeHelpers.PROCESS_INFORMATION processInfo,
Dictionary<string, object> ext)
{
SafeHandle token = (SafeHandle)ext["token"];
SecurityIdentifier sidToAdd = null;
try
{
sidToAdd = Token.GetTokenLogonSid(token);
}
catch (Win32Exception e)
{
if (e.NativeErrorCode != 1168) // ERROR_NOT_FOUND
throw;
sidToAdd = Token.GetTokenUser(token);
}
GrantStationDesktopAccess(sidToAdd, startupInfo.startupInfo.lpDesktop);
bool res = NativeMethods.CreateProcessAsUserW(token, applicationName, commandLine,
processAttributes, threadAttributes, inheritHandles, creationflags, environment, currentDirectory,
startupInfo, out processInfo);
return res ? 0 : Marshal.GetLastWin32Error();
}
public static ProcessInformation CreateProcessWithLogon(PSCredential credential, LogonFlags logonFlags,
string applicationName, string commandLine, ProcessCreationFlags creationFlags, IDictionary environment,
string currentDirectory, StartupInfo startupInfo)
{
Dictionary<string, object> ext = new Dictionary<string, object>()
{
{ "credential", credential },
{ "logonFlags", logonFlags },
};
return CreateProcessInternal(applicationName, commandLine, null, null, false, creationFlags, environment,
currentDirectory, startupInfo, false, null, "CreateProcessWithLogon", ext, CreateProcessWithLogonDel);
}
private static Int32 CreateProcessWithLogonDel(string applicationName, StringBuilder commandLine,
SafeHandle processAttributes, SafeHandle threadAttributes, bool inheritHandles,
ProcessCreationFlags creationflags, SafeHandle environment, string currentDirectory,
NativeHelpers.STARTUPINFOEX startupInfo, out NativeHelpers.PROCESS_INFORMATION processInfo,
Dictionary<string, object> ext)
{
// Fails with the extended startup info.
creationflags &= ~ProcessCreationFlags.ExtendedStartupInfoPresent;
if (startupInfo.lpAttributeList.DangerousGetHandle() != IntPtr.Zero)
throw new ArgumentException("CreateProcessWithLogon does not support extended startup information");
PSCredential credential = (PSCredential)ext["credential"];
LogonFlags logonFlags = (LogonFlags)ext["logonFlags"];
string username = credential.UserName;
string domain = null;
if (credential.UserName.Contains("\\"))
{
string[] userSplit = credential.UserName.Split(new char[1] { '\\' }, 2);
domain = userSplit[0];
username = userSplit[1];
}
// When no Desktop is specified Windows seems to set the Logon Session ID to the current process when it
// logs on the specified user allowing it to access the inherited station/desktop so we don't have to
// change the DACL for that. When explicitly set we still need to add our user to the specified
// station/desktop.
if (!String.IsNullOrEmpty(startupInfo.startupInfo.lpDesktop))
{
// We don't know the logon session SID as the function will create that so just use the account SID
// on the new Station/Desktop ACE.
SecurityIdentifier targetAccount = (SecurityIdentifier)new NTAccount(credential.UserName).Translate(
typeof(SecurityIdentifier));
GrantStationDesktopAccess(targetAccount, startupInfo.startupInfo.lpDesktop);
}
IntPtr passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(credential.Password);
try
{
bool res = NativeMethods.CreateProcessWithLogonW(username, domain, passwordPtr, logonFlags,
applicationName, commandLine, creationflags, environment, currentDirectory, startupInfo,
out processInfo);
return res ? 0 : Marshal.GetLastWin32Error();
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
}
}
public static ProcessInformation CreateProcessWithToken(SafeHandle token, LogonFlags logonFlags,
string applicationName, string commandLine, ProcessCreationFlags creationFlags, IDictionary environment,
string currentDirectory, StartupInfo startupInfo)
{
Dictionary<string, object> ext = new Dictionary<string, object>()
{
{ "token", token },
{ "logonFlags", logonFlags },
};
return CreateProcessInternal(applicationName, commandLine, null, null, false, creationFlags,
environment, currentDirectory, startupInfo, false, null, "CreateProcessWithToken", ext,
CreateProcessWithTokenDel);
}
private static Int32 CreateProcessWithTokenDel(string applicationName, StringBuilder commandLine,
SafeHandle processAttributes, SafeHandle threadAttributes, bool inheritHandles,
ProcessCreationFlags creationflags, SafeHandle environment, string currentDirectory,
NativeHelpers.STARTUPINFOEX startupInfo, out NativeHelpers.PROCESS_INFORMATION processInfo,
Dictionary<string, object> ext)
{
// Fails with the extended startup info.
creationflags &= ~ProcessCreationFlags.ExtendedStartupInfoPresent;
if (startupInfo.lpAttributeList.DangerousGetHandle() != IntPtr.Zero)
throw new ArgumentException("CreateProcessWithToken does not support extended startup information");
SafeHandle token = (SafeHandle)ext["token"];
LogonFlags logonFlags = (LogonFlags)ext["logonFlags"];
SecurityIdentifier sidToAdd = null;
try
{
sidToAdd = Token.GetTokenLogonSid(token);
}
catch (Win32Exception e)
{
if (e.NativeErrorCode != 1168) // ERROR_NOT_FOUND
throw;
sidToAdd = Token.GetTokenUser(token);
}
GrantStationDesktopAccess(sidToAdd, startupInfo.startupInfo.lpDesktop);
bool res = NativeMethods.CreateProcessWithTokenW(token, logonFlags, applicationName, commandLine,
creationflags, environment, currentDirectory, startupInfo, out processInfo);
return res ? 0 : Marshal.GetLastWin32Error();
}
public static SafeDuplicateHandle DuplicateHandle(SafeHandle currentProcess, SafeHandle handle,
Int32 targetProcess, UInt32 desiredAccess, bool inheritHandle, DuplicateOptions options)
{
if (currentProcess == null)
currentProcess = new SafeNativeHandle(IntPtr.Zero, false);
SafeHandle target;
if (targetProcess == 0)
target = new SafeNativeHandle(IntPtr.Zero, false);
else
target = OpenProcess(ProcessAccessRights.DupHandle, false, targetProcess);
IntPtr newHandle;
if (!NativeMethods.DuplicateHandle(currentProcess, handle, target, out newHandle, desiredAccess,
inheritHandle, options))
{
throw new Win32Exception("DuplicateHandle() failed");
}
return new SafeDuplicateHandle(newHandle, target);
}
public static string GetCommandLine()
{
IntPtr buffer = NativeMethods.GetCommandLineW();
return Marshal.PtrToStringUni(buffer);
}
public static SafeHandle GetCurrentProcess()
{
// We should not dispose of these objects.
return new SafeNativeHandle(NativeMethods.GetCurrentProcess(), false);
}
public static Int32 GetProcessExitCode(SafeHandle process)
{
NativeMethods.WaitForSingleObject(process, 0xFFFFFFFF);
Int32 exitCode;
if (!NativeMethods.GetExitCodeProcess(process, out exitCode))
throw new Win32Exception("GetExitCodeProcess() failed");
return exitCode;
}
public static StartupInfo GetStartupInfo()
{
NativeHelpers.STARTUPINFOW_RAW si = new NativeHelpers.STARTUPINFOW_RAW();
si.cb = (uint)Marshal.SizeOf(si);
NativeMethods.GetStartupInfoW(ref si);
byte[] reserved2 = null;
if (si.cbReserved2 > 0)
{
reserved2 = new byte[si.cbReserved2];
Marshal.Copy(si.lpReserved2, reserved2, 0, reserved2.Length);
}
return new StartupInfo()
{
Reserved = Marshal.PtrToStringUni(si.lpReserved),
Desktop = Marshal.PtrToStringUni(si.lpDesktop),
Title = Marshal.PtrToStringUni(si.lpTitle),
Position = new Coordinates(si.dwX, si.dwY),
WindowSize = new Size(si.dwXSize, si.dwYSize),
CountChars = new Size(si.dwXCountChars, si.dwYCountChars),
FillAttribute = (ConsoleColor)si.dwFillAttribute,
Flags = (StartupInfoFlags)si.dwFlags,
ShowWindow = (ShowWindowFlags)si.wShowWindow,
Reserved2 = reserved2,
StandardInput = new SafeNativeHandle(si.hStdInput, false),
StandardOutput = new SafeNativeHandle(si.hStdOutput, false),
StandardError = new SafeNativeHandle(si.hStdError, false),
};
}
public static SafeHandle OpenProcessToken(SafeHandle process, TokenAccessLevels access)
{
SafeNativeHandle t;
if (!NativeMethods.OpenProcessToken(process, access, out t))
throw new Win32Exception("OpenProcessToken() failed");
return t;
}
public static void ResumeThread(SafeHandle thread)
{
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
throw new Win32Exception("ResumeThread() failed");
}
public static void ResumeAndWait(ProcessInformation processInfo)
{
// Thanks to Raymond for these details https://devblogs.microsoft.com/oldnewthing/20130405-00/?p=4743
int compPortSize = Marshal.SizeOf(typeof(NativeHelpers.JOBOBJECT_ASSOCIATE_COMPLETION_PORT));
using (SafeHandle job = JobMethods.CreateJob<NativeObjectSecurity>(null, null))
using (SafeHandle ioPort = JobMethods.CreateCompletionPort())
using (SafeMemoryBuffer compPortPtr = new SafeMemoryBuffer(compPortSize))
{
var compPort = new NativeHelpers.JOBOBJECT_ASSOCIATE_COMPLETION_PORT()
{
CompletionKey = job.DangerousGetHandle(),
CompletionPort = ioPort.DangerousGetHandle(),
};
Marshal.StructureToPtr(compPort, compPortPtr.DangerousGetHandle(), false);
if (!NativeMethods.SetInformationJobObject(job,
NativeHelpers.JobObjectInformationClass.JobObjectAssociateCompletionPortInformation,
compPortPtr.DangerousGetHandle(), compPortSize))
{
throw new Win32Exception("SetInformationJobObject() failed");
}
JobMethods.AssignProcessToJobObject(job, processInfo.Process);
// Resume the thread and wait until it has exited.
ResumeThread(processInfo.Thread);
NativeMethods.WaitForSingleObject(processInfo.Process, 0xFFFFFFFF);
// Continue to poll the job until it receives JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO (4) that indicates
// any other processes in that job have finished.
UInt32 completionCode = 0xFFFFFFFF;
UIntPtr completionKey;
IntPtr overlapped;
while (NativeMethods.GetQueuedCompletionStatus(ioPort, out completionCode,
out completionKey, out overlapped, 0xFFFFFFFF) && completionCode != 4) { }
}
}
internal static SafeMemoryBuffer CreateSecurityAttributes<T>(SecurityAttributes<T> attributes)
where T : NativeObjectSecurity
{
IntPtr lpAttributes = IntPtr.Zero;
if (attributes != null)
{
NativeHelpers.SECURITY_ATTRIBUTES attr = new NativeHelpers.SECURITY_ATTRIBUTES()
{
bInheritHandle = attributes.InheritHandle,
};
byte[] secBytes = new byte[0];
if (attributes.SecurityDescriptor != null)
secBytes = attributes.SecurityDescriptor.GetSecurityDescriptorBinaryForm();
int structureSize = Marshal.SizeOf(attr);
lpAttributes = Marshal.AllocHGlobal(structureSize + secBytes.Length);
if (secBytes.Length > 0)
{
IntPtr secPtr = IntPtr.Add(lpAttributes, structureSize);
attr.lpSecurityDescriptor = secPtr;
Marshal.Copy(secBytes, 0, secPtr, secBytes.Length);
}
Marshal.StructureToPtr(attr, lpAttributes, false);
}
return new SafeMemoryBuffer(lpAttributes);
}
private static ProcessInformation CreateProcessInternal(string applicationName, string commandLine,
ProcessSecurityAttributes processAttributes, ThreadSecurityAttributes threadAttributes,
bool inheritHandles, ProcessCreationFlags creationFlags, IDictionary environment,
string currentDirectory, StartupInfo startupInfo, bool newEnvironment, SafeHandle userToken,
string nativeName, Dictionary<string, object> ext, CreateProcessDelegate impl)
{
if (String.IsNullOrWhiteSpace(applicationName))
applicationName = null;
if (String.IsNullOrWhiteSpace(currentDirectory))
currentDirectory = null;
creationFlags |= ProcessCreationFlags.ExtendedStartupInfoPresent;
NativeHelpers.STARTUPINFOEX si = new NativeHelpers.STARTUPINFOEX();
si.startupInfo.dwFlags = startupInfo.Flags;
if (!String.IsNullOrWhiteSpace(startupInfo.Reserved))
si.startupInfo.lpReserved = startupInfo.Reserved;
if (!String.IsNullOrWhiteSpace(startupInfo.Desktop))
si.startupInfo.lpDesktop = startupInfo.Desktop;
if (!String.IsNullOrWhiteSpace(startupInfo.Title))
si.startupInfo.lpTitle = startupInfo.Title;
if (startupInfo.Position != null)
{
si.startupInfo.dwX = ((Coordinates)startupInfo.Position).X;
si.startupInfo.dwY = ((Coordinates)startupInfo.Position).Y;
si.startupInfo.dwFlags |= StartupInfoFlags.UsePosition;
}
if (startupInfo.WindowSize != null)
{
si.startupInfo.dwXSize = ((Size)startupInfo.WindowSize).Width;
si.startupInfo.dwYSize = ((Size)startupInfo.WindowSize).Height;
si.startupInfo.dwFlags |= StartupInfoFlags.UseSize;
}
if (startupInfo.CountChars != null)
{
si.startupInfo.dwXCountChars = ((Size)startupInfo.CountChars).Width;
si.startupInfo.dwYCountChars = ((Size)startupInfo.CountChars).Height;
si.startupInfo.dwFlags |= StartupInfoFlags.UseCountChars;
}
if (startupInfo.FillAttribute != 0)
{
si.startupInfo.dwFillAttribute = (UInt32)startupInfo.FillAttribute;
si.startupInfo.dwFlags |= StartupInfoFlags.UseFillAttribute;
}
if (startupInfo.ShowWindow != 0)
{
si.startupInfo.wShowWindow = (UInt16)startupInfo.ShowWindow;
si.startupInfo.dwFlags |= StartupInfoFlags.UseShowWindow;
}
using (var lpReserved2 = CreateMemoryBuffer(startupInfo.Reserved2))
using (var stdin = PrepareStdioHandle(startupInfo.StandardInput, startupInfo.ParentProcess))
using (var stdout = PrepareStdioHandle(startupInfo.StandardOutput, startupInfo.ParentProcess))
using (var stderr = PrepareStdioHandle(startupInfo.StandardError, startupInfo.ParentProcess))
using (var procThreadAttr = CreateProcThreadAttributes(startupInfo, stdin, stdout, stderr))
using (var lpProcessAttr = CreateSecurityAttributes(processAttributes))
using (var lpThreadAttr = CreateSecurityAttributes(threadAttributes))
using (var lpEnvironment = CreateEnvironmentPointer(environment, newEnvironment, userToken))
{
if (startupInfo.Reserved2 != null)
si.startupInfo.cbReserved2 = (UInt16)startupInfo.Reserved2.Length;
si.startupInfo.lpReserved2 = lpReserved2;
si.startupInfo.hStdInput = stdin;
si.startupInfo.hStdOutput = stdout;
si.startupInfo.hStdError = stderr;
if (!(stdin.IsInvalid && stdout.IsInvalid && stderr.IsInvalid))
si.startupInfo.dwFlags |= StartupInfoFlags.UseStdHandles;
si.lpAttributeList = procThreadAttr;
if (lpEnvironment.DangerousGetHandle() != IntPtr.Zero)
creationFlags |= ProcessCreationFlags.CreateUnicodeEnvironment;
StringBuilder commandLineBuff = new StringBuilder(commandLine);
NativeHelpers.PROCESS_INFORMATION pi;
Int32 res = impl(applicationName, commandLineBuff, lpProcessAttr, lpThreadAttr, inheritHandles,
creationFlags, lpEnvironment, currentDirectory, si, out pi, ext);
if (res != 0)
throw new Win32Exception(res, String.Format("{0}() failed", nativeName));
return new ProcessInformation
{
CommandLine = commandLineBuff.ToString(),
Process = new SafeNativeHandle(pi.hProcess),
Thread = new SafeNativeHandle(pi.hThread),
ProcessId = pi.dwProcessId,
ThreadId = pi.dwThreadId,
};
}
}
private static SafeHandle CreateEnvironmentPointer(IDictionary environment, bool newEnvironment, SafeHandle userToken)
{
if (newEnvironment)
{
SafeEnvironmentBlock envBlock = null;
if (!NativeMethods.CreateEnvironmentBlock(out envBlock, userToken, false))
throw new Win32Exception("CreateEnvironmentBlock() failed");
return envBlock;
}
IntPtr lpEnvironment = IntPtr.Zero;
if (environment != null && environment.Count > 0)
{
StringBuilder environmentString = new StringBuilder();
foreach (DictionaryEntry kv in environment)
environmentString.AppendFormat("{0}={1}\0", kv.Key, kv.Value);
environmentString.Append('\0');
lpEnvironment = Marshal.StringToHGlobalUni(environmentString.ToString());
}
return new SafeMemoryBuffer(lpEnvironment);
}
private static SafeHandle CreateMemoryBuffer(byte[] data)
{
if (data == null || data.Length < 1)
return new SafeMemoryBuffer(IntPtr.Zero);
SafeMemoryBuffer buffer = new SafeMemoryBuffer(data.Length);
Marshal.Copy(data, 0, buffer.DangerousGetHandle(), data.Length);
return buffer;
}
private static SafeProcThreadAttribute CreateProcThreadAttributes(StartupInfo startupInfo, SafeHandle stdin,
SafeHandle stdout, SafeHandle stderr)
{
int count = 0;
if (startupInfo.ParentProcess != null)
count += 1;
if (startupInfo.InheritedHandles != null && startupInfo.InheritedHandles.Length > 0)
count += 1;
if (startupInfo.ConPTY != null)
count += 1;
SafeProcThreadAttribute attr = new SafeProcThreadAttribute(count);
try
{
if (count == 0)
return attr;
if (startupInfo.ParentProcess != null)
{
IntPtr val = Marshal.AllocHGlobal(IntPtr.Size);
attr.values.Add(val);
Marshal.WriteIntPtr(val, (IntPtr)startupInfo.ParentProcess.Handle);
UpdateProcThreadAttribute(attr, NativeHelpers.ProcessThreadAttribute.ParentProcess, val,
IntPtr.Size);
}
if (startupInfo.ConPTY != null)
{
IntPtr val = startupInfo.ConPTY.DangerousGetHandle();
UpdateProcThreadAttribute(attr, NativeHelpers.ProcessThreadAttribute.PsuedoConsole, val,
IntPtr.Size);
}
if (startupInfo.InheritedHandles != null && startupInfo.InheritedHandles.Length > 0)
{
// We use a hashset so we don't duplicate handles which will cause a failure
HashSet<IntPtr> handles = new HashSet<IntPtr>();
foreach (SafeHandle handle in startupInfo.InheritedHandles)
{
if (startupInfo.ParentProcess == null)
{
NativeHelpers.HandleFlags flags = NativeHelpers.HandleFlags.Inherit;
SetHandleInformation(handle, flags, flags);
}
handles.Add(handle.DangerousGetHandle());
}
// If we are explicitly inheriting some handles we need to ensure our stdio handles are also in
// the list so they can be used. These should have already been duplicated when CreateProcess
// prepared them for STARTUPINFO.
foreach (SafeHandle pipe in new[] { stdin, stdout, stderr })
{
if (pipe.IsInvalid)
continue;
handles.Add(pipe.DangerousGetHandle());
}
int valueSize = IntPtr.Size * handles.Count;
IntPtr val = Marshal.AllocHGlobal(valueSize);
attr.values.Add(val);
IntPtr valOffset = val;
foreach (IntPtr handle in handles)
{
Marshal.WriteIntPtr(valOffset, handle);
valOffset = IntPtr.Add(valOffset, IntPtr.Size);
}
UpdateProcThreadAttribute(attr, NativeHelpers.ProcessThreadAttribute.HandleList, val, valueSize);
}
}
catch
{
attr.Dispose();
throw;
}
return attr;
}
private static void UpdateProcThreadAttribute(SafeProcThreadAttribute attr,
NativeHelpers.ProcessThreadAttribute name, IntPtr value, Int32 size)
{
if (!NativeMethods.UpdateProcThreadAttribute(attr, 0, (UIntPtr)name, value,
(IntPtr)size, IntPtr.Zero, IntPtr.Zero))
{
throw new Win32Exception(String.Format("UpdateProcThreadAttribute({0}) failed", name.ToString()));
}
}
private static void GrantStationDesktopAccess(SecurityIdentifier identity, string targetObject)
{
string station = null;
string desktop = null;
if (!String.IsNullOrEmpty(targetObject))
{
desktop = targetObject;
if (targetObject.Contains("\\"))
{
string[] split = targetObject.Split(new char[] { '\\' }, 2);
station = split[0];
desktop = split[1];
}
}
using (SafeHandle currentStation = StationDesktop.GetProcessWindowStation())
{
string currentStationName = StationDesktop.GetObjectName(currentStation);
// The handle from GetProcessWindowStation has an explicit don't close so it doesn't matter that
// we use this in a using block twice.
SafeHandle targetStation = currentStation;
bool otherStation = false;
if (!(station == null || String.Equals(station, currentStationName,
StringComparison.OrdinalIgnoreCase)))
{
targetStation = StationDesktop.OpenWindowStation(station, false,
StationAccessRights.ReadControl | StationAccessRights.WriteDAC);
otherStation = true;
}
using (targetStation)
{
// Ensures the identity has full access to the target station.
StationSecurity stationSec = new StationSecurity(targetStation, AccessControlSections.Access);
stationSec.AddAccessRule(
stationSec.AccessRuleFactory(identity, (int)StationAccessRights.AllAccess, false,
InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));
stationSec.Persist(targetStation, AccessControlSections.Access);
// If we opened the station we need to set it to the current process station so we can open the
// requested desktop.
if (otherStation)
{
StationDesktop.SetProcessWindowStation(targetStation);
}
try
{
SafeHandle desktopHandle;
if (desktop == null)
desktopHandle = StationDesktop.GetCurrentThreadDesktop();
else
{
DesktopAccessRights access = DesktopAccessRights.ReadControl |
DesktopAccessRights.WriteDAC |
DesktopAccessRights.ReadObjects |
DesktopAccessRights.WriteObjects;
desktopHandle = StationDesktop.OpenDesktop(desktop, false, access);
}
using (desktopHandle)
{
DesktopSecurity deskSec = new DesktopSecurity(desktopHandle, AccessControlSections.Access);
deskSec.AddAccessRule(
deskSec.AccessRuleFactory(identity, (int)DesktopAccessRights.AllAccess, false,
InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));
deskSec.Persist(desktopHandle, AccessControlSections.Access);
}
}
finally
{
if (otherStation)
StationDesktop.SetProcessWindowStation(currentStation);
}
}
}
}
private static SafeHandle PrepareStdioHandle(SafeHandle handle, Process targetProcess)
{
if (handle == null)
return new SafeNativeHandle(IntPtr.Zero, false);
if (targetProcess != null)
{
// We need to duplicate the handle into the new parent process so it can be inherited.
SafeHandle currentProcess = GetCurrentProcess();
return DuplicateHandle(currentProcess, handle, targetProcess.Id, 0, true,
DuplicateOptions.SameAccess);
}
else
{
// We don't need to duplicate the handle into the target process so the child will inherit it.
// Just make sure it's inheritable here and return that one.
NativeHelpers.HandleFlags flags = NativeHelpers.HandleFlags.Inherit;
SetHandleInformation(handle, flags, flags);
// The caller will explicitly dispose of this handle, because we don't have ownership of the handle we
// don't want to close the underlying handle, that's for whatever created the handle in the first
// place to do.
return new SafeNativeHandle(handle.DangerousGetHandle(), false);
}
}
private static SafeNativeHandle OpenProcess(ProcessAccessRights access, bool inherit, Int32 processId)
{
SafeNativeHandle handle = NativeMethods.OpenProcess(access, inherit, processId);
if (handle.IsInvalid)
{
throw new Win32Exception(String.Format("OpenProcess({0}, {1}, {2}) failed",
access, inherit, processId));
}
return handle;
}
private static void SetHandleInformation(SafeHandle handle, NativeHelpers.HandleFlags mask,
NativeHelpers.HandleFlags flags)
{
if (!NativeMethods.SetHandleInformation(handle, mask, flags))
throw new Win32Exception(String.Format("SetHandleInformation({0}, {1}) failed", mask, flags));
}
}
public class Token
{
public static SecurityIdentifier GetTokenUser(SafeHandle handle)
{
using (SafeMemoryBuffer buffer = GetTokenInformation(handle, NativeHelpers.TokenInformationClass.User))
{
var tokenUser = (NativeHelpers.TOKEN_USER)Marshal.PtrToStructure(buffer.DangerousGetHandle(),
typeof(NativeHelpers.TOKEN_USER));
return new SecurityIdentifier(tokenUser.User.Sid);
}
}
public static SecurityIdentifier GetTokenLogonSid(SafeHandle handle)
{
using (SafeMemoryBuffer buffer = GetTokenInformation(handle, NativeHelpers.TokenInformationClass.LogonSid))
{
var tokenGroups = (NativeHelpers.TOKEN_GROUPS)Marshal.PtrToStructure(buffer.DangerousGetHandle(),
typeof(NativeHelpers.TOKEN_GROUPS));
return new SecurityIdentifier(tokenGroups.Groups[0].Sid);
}
}
private static SafeMemoryBuffer GetTokenInformation(SafeHandle handle,
NativeHelpers.TokenInformationClass infoClass)
{
Int32 returnLength;
NativeMethods.GetTokenInformation(handle, infoClass, IntPtr.Zero, 0, out returnLength);
SafeMemoryBuffer buffer = new SafeMemoryBuffer(returnLength);
if (!NativeMethods.GetTokenInformation(handle, infoClass, buffer.DangerousGetHandle(), returnLength,
out returnLength))
{
buffer.Dispose();
throw new Win32Exception(String.Format("GetTokenInformation({0}) failed", infoClass.ToString()));
}
return buffer;
}
}
public class StationDesktop
{
public static SafeHandle GetProcessWindowStation()
{
IntPtr station = NativeMethods.GetProcessWindowStation();
if (station == IntPtr.Zero)
throw new Win32Exception("GetProcessWindowStation() failed");
// We should not dispose of these objects.
return new SafeNativeHandle(station, false);
}
public static SafeHandle GetCurrentThreadDesktop()
{
return GetThreadDesktop(NativeMethods.GetCurrentThreadId());
}
public static SafeHandle GetThreadDesktop(Int32 threadId)
{
IntPtr desktop = NativeMethods.GetThreadDesktop(threadId);
if (desktop == IntPtr.Zero)
throw new Win32Exception(String.Format("GetThreadDesktop({0}) failed", threadId));
// We should not dispose of these objects.
return new SafeNativeHandle(desktop, false);
}
public static string GetObjectName(SafeHandle handle)
{
using (SafeMemoryBuffer buffer = GetUserObjectInformation(handle,
NativeHelpers.UserObjectInfoIndex.UOI_NAME))
{
return Marshal.PtrToStringUni(buffer.DangerousGetHandle());
}
}
public static SafeHandle OpenDesktop(string desktop, bool inherit, DesktopAccessRights access)
{
SafeDesktop desktopHandle = NativeMethods.OpenDesktopW(desktop, 0, inherit, access);
if (desktopHandle.IsInvalid)
throw new Win32Exception(String.Format("OpenDesktop({0}) failed", desktop));
return desktopHandle;
}
public static SafeHandle OpenWindowStation(string station, bool inherit, StationAccessRights access)
{
SafeWindowStation stationHandle = NativeMethods.OpenWindowStationW(station, inherit, access);
if (stationHandle.IsInvalid)
throw new Win32Exception(String.Format("OpenWindowStation({0}) failed", station));
return stationHandle;
}
public static void SetProcessWindowStation(SafeHandle winSta)
{
if (!NativeMethods.SetProcessWindowStation(winSta))
throw new Win32Exception("SetProcessWindowStation() failed");
}
private static SafeMemoryBuffer GetUserObjectInformation(SafeHandle handle,
NativeHelpers.UserObjectInfoIndex index)
{
Int32 bytesNeeded;
NativeMethods.GetUserObjectInformationW(handle, index, IntPtr.Zero, 0, out bytesNeeded);
SafeMemoryBuffer buffer = new SafeMemoryBuffer(bytesNeeded);
if (!NativeMethods.GetUserObjectInformationW(handle, index, buffer.DangerousGetHandle(),
bytesNeeded, out bytesNeeded))
{
throw new Win32Exception(String.Format("GetUserObjectInformationW({0}) failed", index.ToString()));
}
return buffer;
}
}
public abstract class NativeKernelSecurity<TRight, TRule> : NativeObjectSecurity
where TRule : AccessRule
{
public override Type AccessRightType { get { return typeof(TRight); } }
public override Type AccessRuleType { get { return typeof(TRule); } }
public override Type AuditRuleType { get { throw new NotImplementedException(); } }
protected NativeKernelSecurity(ResourceType resourceType) : base(false, resourceType) { }
protected NativeKernelSecurity(ResourceType resourceType, SafeHandle handle,
AccessControlSections includeSections)
: base(false, resourceType, handle, includeSections) { }
public override AccessRule AccessRuleFactory(IdentityReference identityReference, int accessMask,
bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags,
AccessControlType type)
{
return (TRule)Activator.CreateInstance(typeof(TRule), identityReference, (TRight)(object)accessMask,
isInherited, inheritanceFlags, propagationFlags, type);
}
public new void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); }
public AuthorizationRuleCollection GetAccessRules(Type targetType)
{
return GetAccessRules(true, false, targetType);
}
public override AuditRule AuditRuleFactory(IdentityReference identityReference, int accessMask,
bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)
{
throw new NotImplementedException();
}
public new void Persist(SafeHandle handle, AccessControlSections includeSections)
{
base.Persist(handle, includeSections);
}
}
public class DesktopSecurity : NativeKernelSecurity<DesktopAccessRights, DesktopAccessRule>
{
public DesktopSecurity() : base(ResourceType.WindowObject) { }
public DesktopSecurity(SafeHandle handle, AccessControlSections includeSections)
: base(ResourceType.WindowObject, handle, includeSections) { }
}
public class DesktopAccessRule : AccessRule
{
public DesktopAccessRights ProcessAccessRights { get { return (DesktopAccessRights)AccessMask; } }
public DesktopAccessRule(IdentityReference identityReference, DesktopAccessRights accessMask, bool isInherited,
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
: base(identityReference, (int)accessMask, isInherited, inheritanceFlags, propagationFlags, type) { }
}
public class ProcessSecurity : NativeKernelSecurity<ProcessAccessRights, ProcessAccessRule>
{
public ProcessSecurity() : base(ResourceType.KernelObject) { }
public ProcessSecurity(SafeHandle handle, AccessControlSections includeSections)
: base(ResourceType.KernelObject, handle, includeSections) { }
}
public class ProcessAccessRule : AccessRule
{
public ProcessAccessRights ProcessAccessRights { get { return (ProcessAccessRights)AccessMask; } }
public ProcessAccessRule(IdentityReference identityReference, ProcessAccessRights accessMask, bool isInherited,
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
: base(identityReference, (int)accessMask, isInherited, inheritanceFlags, propagationFlags, type) { }
}
public class StationSecurity : NativeKernelSecurity<StationAccessRights, StationAccessRule>
{
public StationSecurity() : base(ResourceType.WindowObject) { }
public StationSecurity(SafeHandle handle, AccessControlSections includeSections)
: base(ResourceType.WindowObject, handle, includeSections) { }
}
public class StationAccessRule : AccessRule
{
public StationAccessRights ProcessAccessRights { get { return (StationAccessRights)AccessMask; } }
public StationAccessRule(IdentityReference identityReference, StationAccessRights accessMask, bool isInherited,
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
: base(identityReference, (int)accessMask, isInherited, inheritanceFlags, propagationFlags, type) { }
}
public class ThreadSecurity : NativeKernelSecurity<ThreadAccessRights, ThreadAccessRule>
{
public ThreadSecurity() : base(ResourceType.KernelObject) { }
public ThreadSecurity(SafeHandle handle, AccessControlSections includeSections)
: base(ResourceType.KernelObject, handle, includeSections) { }
}
public class ThreadAccessRule : AccessRule
{
public ThreadAccessRights ThreadAccessRights { get { return (ThreadAccessRights)AccessMask; } }
public ThreadAccessRule(IdentityReference identityReference, ThreadAccessRights accessMask, bool isInherited,
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
: base(identityReference, (int)accessMask, isInherited, inheritanceFlags, propagationFlags, type) { }
}
}
'@
}
if ((Get-Variable -Name IsCoreCLR -ErrorAction SilentlyContinue) -and $IsCoreCLR) {
# PowerShell Core does not include the assemblies we need by default. Also using -ReferencedAssemblies removes
# the default assemblies that Add-Type adds so we need to re-add them all + the extra ones required.
$pwshLocation = Split-Path -Path ([PSObject].Assembly.Location) -Parent
$pwshRefAssemblyPattern = [IO.Path]::Combine($pwshLocation, 'ref', '*.dll')
$assemblies = [System.Collections.Generic.List[System.String]](Get-Item -Path $pwshRefAssemblyPattern).FullName
$assemblies.Add([PSObject].Assembly.Location)
$assemblies.Add([Security.Principal.TokenAccessLevels].Assembly.Location)
$assemblies.Add([Security.AccessControl.NativeObjectSecurity].Assembly.Location)
$typeParams.ReferencedAssemblies = $assemblies
}
Add-Type @typeParams
class ProcessTransformAttribute : ArgumentTransformationAttribute {
[object] Transform([EngineIntrinsics]$engineIntrinsics, [object]$InputData) {
$outputData = switch ($InputData) {
{ $_ -is [Diagnostics.Process] } { $_ }
{ $_ -is [Int32] } {
Get-Process -Id $_
}
default {
throw [Management.Automation.ArgumentTransformationMetadataException]::new(
"Could not convert input '$_' to a valid Process object."
)
}
}
return $outputData
}
}
Function Resolve-ExecutablePath {
<#
.SYNOPSIS
Tries to resolve the file path to a valid executable.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
$FilePath
)
# Ensure the path has an extension set, default to .exe
if (-not [IO.Path]::HasExtension($FilePath)) {
$FilePath = "$FilePath.exe"
}
# See the if path is resolvable using the normal PATH logic. Also resolves absolute paths and relative paths if
# they exist.
$command = Get-Command -Name $FilePath -CommandType Application -ErrorAction SilentlyContinue
if ($command) {
$command.Path
return
}
# Just hope for the best and use whatever was provided.
$FilePath
}
Function ConvertTo-EscapedArgument {
<#
.SYNOPSIS
Escapes an argument value so it can be used in a call to CreateProcess safely as 1 argument.
.PARAMETER InputObject
The argument(s) to escape.
#>
[CmdletBinding()]
[OutputType([String])]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[AllowEmptyString()]
[AllowNull()]
[String[]]
$InputObject
)
process {
if (-not $InputObject) {
return '""'
}
foreach ($argument in $InputObject) {
if (-not $argument) {
return '""'
}
elseif ($argument -notmatch '[\s"]') {
return $argument
}
# Replace any double quotes in an argument with '\"'
$argument = $argument -replace '"', '\"'
# Double up on any '\' chars that preceded '\"'
$argument = $argument -replace '(\\+)\\"', '$1$1\"'
# Double up '\' at the end of the argument so it doesn't escape end quote.
$argument = $argument -replace '(\\+)$', '$1$1'
# Finally wrap the entire argument in double quotes now we've escaped the double quotes within
'"{0}"' -f $argument
}
}
}
Function Copy-HandleToProcess {
<#
.SYNOPSIS
Opens the handle in another process.
.DESCRIPTION
Opens the handle in the process specified. This can be used for New-StartupInfo -InheritedHandles when the
-ParentProcess parameter is set.
.PARAMETER Handle
The handle to open in the other process.
.PARAMETER Process
The process to open the handle in.
.EXAMPLE
$parentProcess = Get-Process -Id 6624
$fs = [IO.File]::Open('C:\temp\file.txt', 'Create', 'Write', 'ReadWrite')
try {
$newHandle = Copy-HandleToProcess -Handle $fs.SafeFileHandle -Process $parentProcess
}
finally {
$fs.Dispose()
}
try {
$si = New-StartupInfo -InheritedHandles $newHandle -ParentProcess $parentProcess
Start-ProcessEx powershell.exe -StartupInfo $si -Wait
}
finally {
$newHandle.Dispose()
}
.NOTES
The new handle should be disposed so it is closed in the process it was copied to.
#>
[OutputType([ProcessEx.SafeDuplicateHandle])]
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[SafeHandle]
$Handle,
[Parameter(Mandatory)]
[ProcessTransformAttribute()]
[Diagnostics.Process]
$Process
)
[ProcessEx.ProcessMethods]::DuplicateHandle(
[ProcessEx.ProcessMethods]::GetCurrentProcess(),
$Handle,
$Process.Id,
0,
$true,
[ProcessEx.DuplicateOptions]::SameAccess
)
}
Function New-ConPTY {
<#
.SYNOPSIS
Create a pseudo console PTY.
.DESCRIPTION
Create a pseudo console PTY to be used to transfer data into and out from a console process.
.PARAMETER Width
The number of horizontal characters of the new buffer.
.PARAMETER Height
The number of vertical characters of the new buffer.
.PARAMETER InputPipe
A readable stream that represents data to write as the input to the buffer. The input bytes represent a UTF-8
encoded string.
.PARAMETER OutputPipe
A writable stream that represents data output from the buffer. The bytes represent a UTF-8 encoded string.
.PARAMETER Flags
Custom flags that control how the buffer is created.
.EXAMPLE
$inputStream = [IO.FileStream]::Open('C:\temp\input', 'Open', 'Read', 'ReadWrite')
$outputStream = [IO.FileStream]::Open('C:\temp\output', 'Create', 'Write', 'ReadWrite')
$conPTY = New-ConPty -Width 80 -Height 60 -InputPipe $inputStream.SafeFileHandle -OutputPipe $outputStream.SafeFileHandle
try {
$si = New-StartupInfo -ConPTY $conPTY
Start-ProcessEx powershell.exe '[Console]::WriteLine([Char]::ConvertFromUtf32(0x1F603))' -StartupInfo $si -Wait
}
finally {
$inputStream.Dispose()
$outputStream.Dispose()
$conPTY.Dispose()
}
.NOTES
This is supported from Windows 10 (build 1809) or newer.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[Alias('X')]
[Int16]
$Width,
[Parameter(Mandatory)]
[Alias('Y')]
[Int16]
$Height,
[Parameter(Mandatory)]
[SafeHandle]
$InputPipe,
[Parameter(Mandatory)]
[SafeHandle]
$OutputPipe,
[ProcessEx.ConsoleFlags]
$Flags = 'None'
)
$size = [Coordinates]::new($Width, $Height)
[ProcessEx.ConsoleMethods]::CreatePseudoConsole($size, $InputPipe, $OutputPipe, $Flags)
}
Function New-StartupInfo {
<#
.SYNOPSIS
Create StartupInfo for creating a process.
.DESCRIPTION
Create the StartupInfo object that is used to define low level details when creating a process.
.PARAMETER Desktop
The name of the Windows Desktop or both the Windows Desktop and Station (station\desktop) for the new process.
If not set then it defaults to the current station and desktop.
.PARAMETER Title
This does not apply to GUI processes and console processes created in the same console. For a new console process
created with a new console window this is the title to display in the title bar and if not set the executable path
is used.
.PARAMETER Position
The window position with X being the horizontal offset and Y being the vertical offset from the top left corner of
the screen.
.PARAMETER WindowSize
The size of the window to create.
.PARAMETER CountChars
Size of the console screen buffer.
.PARAMETER FillAttribute
Used to control the color of the background and foreground of the new console process.
.PARAMETER Flags
Flags to set for the new process. Some flags are always set when other startup info parameters are specified.
.PARAMETER WindowStyle
The window style of the new process.
.PARAMETER Reserved
This parameter refers to the string lpReserved field in the STARTUPINFO structure used to create a process. It is
unused and undocumented in Windows.
.PARAMETER Reserved2
This parameter refers to the byte lpReserved2 field and cbReserved2 in the STARTUPINFO structure. This is unused
and undocumented but has some used for sneaking data into a process.
.PARAMETER StandardInput
A readable SafeHandle that is set to the new process' stdin pipe. This handle will be explicitly set as
inheritable process wide even if it wasn't opened as an inheritable object. This cannot be used with
with -DisableInheritance on Start-ProcessEx.
.PARAMETER StandardOutput
A writable SafeHandle that is set to the new process' stdout pipe. This handle will be explicitly set as
inheritable process wide even if it wasn't opened as an inheritable object. This cannot be used with
-DisableInheritance on Start-ProcessEx.
.PARAMETER StandardError
A writable SafeHandle that is set to the new process' stderr pipe. This handle will be explicitly set as
inheritable process wide even if it wasn't opened as an inheritable object. This cannot be used with
-DisableInheritance on Start-ProcessEx.
.PARAMETER ConPTY
The pseudo console handle that represents the console input and output pipelines. Use New-ConsolePTY to create
this handle. This cannot be set alongside the StandardInput/Output/Error parameters. This will not work with
-ParentProcess as there is no way to open the ConPTY in the parent process for inheritance. This will not work with
Start-ProcessWith due to the restrictions in the underlying APIs it calls.
.PARAMETER InheritedHandles
A list of handles to explicit allow the new process to inherit from. If omitted then the child will inherit all
the parent process' handles that were opened with inheritability. When -ParentProcess is not set, all these
handles will be changed to an inheritable handle in the current process. When -ParentProcess is set then these
handles need to be a valid handle in the parent process itself. Use Copy-HandleToProcess to open them in the
parent process. This will not work with Start-ProcessWith due to the restrictions in the underlying APIs it calls.
.PARAMETER ParentProcess
The parent process to create this as a child for. If the parent process is running under a different user or a
different elevation context of the current user then this will fail unless you are running as SYSTEM. This will
not work with Start-ProcessWith due to the restrictions in the underlying APIs it calls.
.NOTES
See http://www.catch22.net/tuts/undocumented-createprocess# for some tricks with the reserved fields.
#>
[OutputType([ProcessEx.StartupInfo])]
[CmdletBinding(DefaultParameterSetName='STDIO')]
param (
[String]
$Desktop,
[String]
$Title,
[Coordinates]
$Position,
[Size]
$WindowSize,
[Size]
$CountChars,
[ProcessEx.ConsoleColor]
$FillAttribute = 0,
[ProcessEx.StartupInfoFlags]
$Flags = 0,
[Diagnostics.ProcessWindowStyle]
$WindowStyle = 0,
[String]
$Reserved,
[Byte[]]
$Reserved2,
[Parameter(ParameterSetName='STDIO')]
[SafeHandle]
$StandardInput,
[Parameter(ParameterSetName='STDIO')]
[SafeHandle]
$StandardOutput,
[Parameter(ParameterSetName='STDIO')]
[SafeHandle]
$StandardError,
[Parameter(ParameterSetName='ConPTY')]
[SafeHandle]
$ConPTY,
[SafeHandle[]]
$InheritedHandles,
[ProcessTransformAttribute()]
[Diagnostics.Process]
$ParentProcess
)
[ProcessEx.ShowWindowFlags]$showWindow = switch ($WindowStyle) {
Normal { 'ShowNormal' }
Hidden { 'Hide' }
Minimized { 'Minimize' }
Maximized { 'Maximized' }
default { 'Normal' }
}
[ProcessEx.StartupInfo]@{
Reserved = $Reserved
Desktop = $Desktop
Title = $title
Position = $Position
WindowSize = $WindowSize
CountChars = $CountChars
FillAttribute = $FillAttribute
Flags = $Flags
ShowWindow = $showWindow
Reserved2 = $Reserved2
StandardInput = $StandardInput
StandardOutput = $StandardOutput
StandardError = $StandardError
ConPTY = $ConPTY
InheritedHandles = $InheritedHandles
ParentProcess = $ParentProcess
}
}
Function Get-StartupInfo {
<#
.SYNOPSIS
Get the STARUTPINFOW structure for the current process.
#>
[OutputType([ProcessEx.StartupInfo])]
param ()
[ProcessEx.ProcessMethods]::GetStartupInfo()
}
Function Start-ProcessEx {
<#
.SYNOPSIS
Start a new process.
.DESCRIPTION
Like Start-Process but exposes a few more low level functionality and focuses specifically on the Win32 API
CreateProcess or CreateProcessAsUser when -Token is specified.
.PARAMETER FilePath
The executable to start.
.PARAMETER ArgumentList
A list of arguments to run with the filepath. These arguments are automatically escaped based on the Win32
argument escaping rules. If you wish to provide arguments as a literal string without escaping use the
-CommandLine option instead of this and -FilePath.
.PARAMETER CommandLine
Used instead of -FilePath and -ArgumentList to run a new process with the literal string provided. This string is
not escaped so you need to ensure it is valid for your use case. You can also specify -ApplicationName to be more
explicit about what executable to run but that isn't required.
.PARAMETER ApplicationName
Used with -CommandLine as the full path to the executable to run. This is useful if the -CommandLine executable
path contains spaces and you wish to be explicit about what to run.
.PARAMETER WorkingDirectory
The working directory to set for the new process, defaults to the current process working dir if not defined.
.PARAMETER StartupInfo
The process StartupInfo details to use. Use the New-StartupInfo command to define this value based on your needs.
.PARAMETER CreationFlags
The process CreationFlags, see https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
for a description of each possible flag. Defaults to 'None' if -StartupInfo contains a ConPTY otherwise the default
is 'CreateNewConsole' to ensure the console application is created in a new console window instead of sharing the
existing console. You should not set 'CreateNewConsole' if a ConPTY is specified as that will have the new process
ignore the ConPTY and use the new conhost allocated. You cannot set 'CreateSuspended' with the -Wait parameter.
.PARAMETER ProcessAttribute
Set the security descriptor of the new process.
.PARAMETER ThreadAttribute
Set the security descriptor of the new main thread of the process.
.PARAMETER Environment
A dictionary containing explicit environment variables to use for the new process. These env vars will be used
instead of the existing process environment variables if defined. Cannot be used with -UseNewEnvironment.
.PARAMETER Token
Create the process to run with the access token specified. This access token can be retrieved through other
functions like LogonUser, duplicating from an existing process, etc. Using this parameter calls
CreateProcessAsUser instead of CreateProcess which requires the SeIncreaseQuotaPrivilege privilege and also
requires the SeAssignPrimaryTokenPrivilege privilege if the token is not a restricted version of the callers
primary token. The SeAssignPrimaryTokenPrivilege privilege is not given to administrators by default so check with
'whoami.exe /priv' to see if your account has this privilege.
Be aware that this process will add the Logon Session SID of this token to the station and desktop security
descriptor specified by Desktop on -StartupInfo. If no startup info was specified then the current station and
desktop is used. Running this with multiple tokens could hit the maximum allowed size in a ACL.
.PARAMETER UseNewEnvironment
Instead of inheriting the current process environment variables, use a brand new set of environment variables for
the current user. Cannot be used with -Environment.
.PARAMETER DisableInheritance
Explicitly disable all the handles in the current process from being inherited with the new process. This cannot
be used if -StartupInfo has an explicit StandardInput/Output/Error or InheritedHandles list.
.PARAMETER Wait
Wait for the process and any of the processes they may spawn to finish before returning. This cannot be set with
-CreationFlags CreateSuspended.
.PARAMETER PassThru
Return the Diagnostics.Process object for the process that was created.
.PARAMETER Raw
When combined with -PassThru this will return the ProcessInformation object with the original process/thread
handles and their identifiers.
.EXAMPLE Start a new console process
Start-ProcessEx -FilePath powershell.exe
.EXAMPLE Start a process with arguments
Start-ProcessEx -FilePath pwsh.exe -ArgumentList '-NoProfile', '-Command', 'echo "hi"'
.EXAMPLE Start a process with redirected stdout to a file
$fs = [IO.File]::Open('C:\temp\stdout.txt', 'Create', 'Write', 'ReadWrite')
try {
Start-ProcessEx -FilePath cmd.exe /c echo hi -StartupInfo (New-StartupInfo -StandardOutput $fs.SafeFileHandle) -Wait
}
finally {
$fs.Dispose()
}
#>
[OutputType([Diagnostics.Process], [ProcessEx.ProcessInformation])]
[CmdletBinding(DefaultParameterSetName='FilePath')]
param (
[Parameter(Mandatory, Position=0, ParameterSetName='FilePath')]
[String]
$FilePath,
[Parameter(ValueFromRemainingArguments, ParameterSetName='FilePath')]
[String[]]
$ArgumentList,
[Parameter(Mandatory, ParameterSetName='CommandLine')]
[String]
$CommandLine,
[Parameter(ParameterSetName='CommandLine')]
[String]
$ApplicationName,
[String]
$WorkingDirectory,
[ProcessEx.StartupInfo]
$StartupInfo,
[ProcessEx.ProcessCreationFlags]
$CreationFlags = 'CreateNewConsole',
[ProcessEx.ProcessSecurityAttributes]
$ProcessAttribute,
[ProcessEx.ThreadSecurityAttributes]
$ThreadAttribute,
[Collections.IDictionary]
$Environment,
[SafeHandle]
$Token,
[Switch]
$UseNewEnvironment,
[Switch]
$DisableInheritance,
[Switch]
$Wait,
[Switch]
$PassThru,
[Switch]
$Raw
)
process {
if ($PSCmdlet.ParameterSetName -eq 'FilePath') {
$ApplicationName = Resolve-ExecutablePath -FilePath $FilePath
$CommandLine = $FilePath | ConvertTo-EscapedArgument
if ($ArgumentList) {
$CommandLine += " $(($ArgumentList | ConvertTo-EscapedArgument) -join " ")"
}
}
if (-not $StartupInfo) {
$StartupInfo = New-StartupInfo
}
if ($null -ne $StartupInfo.ConPTY -and $CreationFlags -eq [ProcessEx.ProcessCreationFlags]::CreateNewConsole) {
$CreationFlags = [ProcessEx.ProcessCreationFlags]::None
}
# We always want to spawn it suspended so we can get the Process object. We just need to figure out if we need
# to resume it ourselves or whether the caller wanted to keep it suspended or not.
$suspend = $CreationFlags.HasFlag([ProcessEx.ProcessCreationFlags]::CreateSuspended)
$CreationFlags = $CreationFlags -bor [ProcessEx.ProcessCreationFlags]::CreateSuspended
if ($Wait -and $suspend) {
Write-Error -Message "Cannot use -Wait with -CreationFlags CreateSuspended together" -Category InvalidArgument
return
}
if ($null -ne $Environment -and $Environment.Count -gt 0 -and $UseNewEnvironment) {
Write-Error -Message "Cannot use -Environment and -UseNewEnvironment together" -Category InvalidArgument
return
}
if ($StartupInfo.InheritedHandles.Length -and $DisableInheritance) {
Write-Error -Message "Cannot use StartupInfo InheritedHandles and -DisableInheritance together" -Category InvalidArgument
return
}
$detailLines = @(
"`tApplicationName: $ApplicationName",
"`tCommandLine: $CommandLine",
"`tDisableInheritance: $DisableInheritance",
"`tCreationFlags: $CreationFlags",
"`tWorkingDirectory: $WorkingDirectory"
)
Write-Verbose -Message "Creating process with:`r`n$($detailLines -join "`r'n")"
$procInfo = $null
try {
if ($Token) {
Write-Verbose -Message "Calling CreateProcessAsUser"
$procInfo = [ProcessEx.ProcessMethods]::CreateProcessAsUser(
$Token,
$ApplicationName,
$CommandLine,
$ProcessAttribute,
$ThreadAttribute,
(-not $DisableInheritance),
$CreationFlags,
$Environment,
$WorkingDirectory,
$StartupInfo,
$UseNewEnvironment
)
}
else {
Write-Verbose -Message "Calling CreateProcess"
$procInfo = [ProcessEx.ProcessMethods]::CreateProcess(
$ApplicationName,
$CommandLine,
$ProcessAttribute,
$ThreadAttribute,
(-not $DisableInheritance),
$CreationFlags,
$Environment,
$WorkingDirectory,
$StartupInfo,
$UseNewEnvironment
)
}
Write-Verbose -Message "Process created with PID $($procInfo.ProcessId) and TID $($procInfo.ThreadId)"
$proc = Get-Process -Id $procInfo.ProcessId
if ($Wait) {
Write-Verbose -Message "Resuming process and waiting for it to complete"
[ProcessEx.ProcessMethods]::ResumeAndWait($procInfo)
}
elseif (-not $suspend) {
Write-Verbose -Message "Resuming process and continuing on"
[ProcessEx.ProcessMethods]::ResumeThread($procInfo.Thread)
}
if ($PassThru) {
if ($Raw) {
$procInfo
}
else {
$proc
}
}
}
finally {
if ($procInfo -and -not $Raw) {
$procInfo.Dispose()
}
}
}
}
Function Start-ProcessWith {
<#
.SYNOPSIS
Start a new process with credentials or a token.
.DESCRIPTION
Like Start-Process but exposes a few more low level functionality and focuses specifically on the Win32 API
CreateProcessWithLogon or CreateProcessWithToken. Use this instead of Start-ProcessEx if you wish to start a
process as another user but do no have the SeAssignPrimaryTokenPrivilege privileges required by the -Token
parameter on Start-ProcessEx.
.PARAMETER FilePath
The executable to start.
.PARAMETER ArgumentList
A list of arguments to run with the filepath. These arguments are automatically escaped based on the Win32
argument escaping rules. If you wish to provide arguments as a literal string without escaping use the
-CommandLine option instead of this and -FilePath.
.PARAMETER CommandLine
Used instead of -FilePath and -ArgumentList to run a new process with the literal string provided. This string is
not escaped so you need to ensure it is valid for your use case. You can also specify -ApplicationName to be more
explicit about what executable to run but that isn't required.
.PARAMETER ApplicationName
Used with -CommandLine as the full path to the executable to run. This is useful if the -CommandLine executable
path contains spaces and you wish to be explicit about what to run.
.PARAMETER Credential
The user credentials to start the new process with. This will use the CreateProcessWithLogon API which logs on the
user as an interactive logon or a NewCredential (network access change) logon when '-LogonFlags NetCredentialsOnly'
is specified. This API can be called by standard user accounts as it has no sensitive privilege requirements.
When -StartupInfo specified a custom station/desktop, the function will add the SID specified by the username to
the station and desktop's security descriptor with AllAccess. When no startup info or Desktop is not set then the
current station/desktop is used without any adjustments to their security descriptors.
.PARAMETER Token
Creates the process to run with the specified access token instead of explicit credentials. This access token can
be retrieved by using other APIs like LogonUser or by duplicating an existing access token of a running process.
The token should have been opened with Query, Duplicate, and AssignPrimary rights. This will use the
CreateProcessWithToken API which requires the SeImpersonatePrivilege privileged usually held by administrators on
the host.
The Logon Session SID of the token will be added to the security descriptor of the station/desktop specified by the
-StartupInfo Desktop property with AllAccess. If no startup info is specified or Desktop is not set then it is
added to the station/desktop of the calling process. Running this with multiple tokens could hit the maximum
allowed size in a ACL.
.PARAMETER LogonFlags
Custom flags to control the logon process. The following options can be set:
None - No special behaviour
WithProfile - Loads the user profile into the HKCU hive
NetCredentialsOnly - The token credentials of the new process is only used for network authentication
.PARAMETER WorkingDirectory
The working directory to set for the new process, defaults to the current process working dir if not defined.
.PARAMETER StartupInfo
The process StartupInfo details to use. Use the New-StartupInfo command to define this value based on your needs.
.PARAMETER CreationFlags
The process CreationFlags, see https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
for a description of each possible flag. Default to
'CreateNewConsole, CreateDefaultErrorMode, CreateNewProcessGroup' which is also the same 'None'. You cannot set
'CreateSuspended' with the -Wait parameter.
.PARAMETER Environment
A dictionary containing explicit environment variables to use for the new process. These env vars will be used
instead of the new users environment block.
.PARAMETER Wait
Wait for the process and any of the processes they may spawn to finish before returning. This cannot be set with
-CreationFlags CreateSuspended.
.PARAMETER PassThru
Return the Diagnostics.Process object for the process that was created.
.PARAMETER Raw
When combined with -PassThru this will return the ProcessInformation object with the original process/thread
handles and their identifiers.
.EXAMPLE Start a new console process as another user
Start-ProcessWith -FilePath powershell.exe -Credential (Get-Credential)
.EXAMPLE Start a process with another token
$token = [Win32.Native]::LogonUser('username', 'password') # This is a mock example
Start-ProcessWith -FilePath pwsh.exe -ArgumentList '-NoProfile', '-Command', 'echo "hi"' -Token $token
.EXAMPLE Start a process with redirected stdout to a file
$fs = [IO.File]::Open('C:\temp\stdout.txt', 'Create', 'Write', 'ReadWrite')
try {
Start-ProcessWith -FilePath cmd.exe /c echo hi -StartupInfo (New-StartupInfo -StandardOutput $fs.SafeFileHandle) -Wait -Credential (Get-Credential)
}
finally {
$fs.Dispose()
}
.NOTES
This cmdlet uses the CreateProcessWithLogon or CreateProcessWithToken APIs which rely on the secondary logon
service in Windows. While it allows less privileges than CreateProcessAsUser (Start-ProcessEx -Token ...) there
are some disadvantages:
* The maximum command line length is 1024 characters (-ArgumentList or -CommandLine)
* You cannot specify the process or thread security attributes for a custom security descriptor on either
* Handles in the current process cannot be inherited into the child process, you can still specify STDIO handles
* You cannot specify any extended startup information like a parent proces, inherited handles, or a pseudo console
* The environment is not inherited from the current process, it will be created from scratch from the new user profile
* The console is always created in a new window and cannot inherit the current console
#>
[OutputType([Diagnostics.Process], [ProcessEx.ProcessInformation])]
[CmdletBinding(DefaultParameterSetName='FilePathCredential')]
param (
[Parameter(Mandatory, Position=0, ParameterSetName='FilePathCredential')]
[Parameter(Mandatory, Position=0, ParameterSetName='FilePathToken')]
[String]
$FilePath,
[Parameter(ValueFromRemainingArguments, ParameterSetName='FilePathCredential')]
[Parameter(ValueFromRemainingArguments, ParameterSetName='FilePathToken')]
[String[]]
$ArgumentList,
[Parameter(Mandatory, ParameterSetName='CommandLineCredential')]
[Parameter(Mandatory, ParameterSetName='CommandLineToken')]
[String]
$CommandLine,
[Parameter(ParameterSetName='CommandLineCredential')]
[Parameter(ParameterSetName='CommandLineToken')]
[String]
$ApplicationName,
[Parameter(Mandatory, ParameterSetName='FilePathCredential')]
[Parameter(Mandatory, ParameterSetName='CommandLineCredential')]
[PSCredential]
$Credential,
[Parameter(Mandatory, ParameterSetName='FilePathToken')]
[Parameter(Mandatory, ParameterSetName='CommandLineToken')]
[SafeHandle]
$Token,
[ProcessEx.LogonFlags]
$LogonFlags = 'None',
[String]
$WorkingDirectory,
[ProcessEx.StartupInfo]
$StartupInfo,
[ProcessEx.ProcessCreationFlags]
$CreationFlags = 'CreateNewConsole, CreateDefaultErrorMode, CreateNewProcessGroup',
[Collections.IDictionary]
$Environment,
[Switch]
$Wait,
[Switch]
$PassThru,
[Switch]
$Raw
)
process {
if ($PSCmdlet.ParameterSetName -in @('FilePathCredential', 'FilePathToken')) {
$ApplicationName = Resolve-ExecutablePath -FilePath $FilePath
$CommandLine = $FilePath | ConvertTo-EscapedArgument
if ($ArgumentList) {
$CommandLine += " $(($ArgumentList | ConvertTo-EscapedArgument) -join " ")"
}
}
if (-not $StartupInfo) {
$StartupInfo = New-StartupInfo
}
if ($StartupInfo.InheritedHandles) {
Write-Error -Message "Start-ProcessWith cannot be used with InheritedHandles" -Category InvalidArgument
}
if ($StartupInfo.ParentProcess) {
Write-Error -Message "Start-ProcessWith cannot be used with a ParentProcess" -Category InvalidArgument
}
if ($StartupInfo.ConPTY) {
Write-Error -Message "Start-ProcessWith cannot be used with a custom ConPTY" -Category InvalidArgument
return
}
# We always want to spawn it suspended so we can get the Process object. We just need to figure out if we need
# to resume it ourselves or whether the caller wanted to keep it suspended or not.
$suspend = $CreationFlags.HasFlag([ProcessEx.ProcessCreationFlags]::CreateSuspended)
$CreationFlags = $CreationFlags -bor [ProcessEx.ProcessCreationFlags]::CreateSuspended
if ($Wait -and $suspend) {
Write-Error -Message "Cannot use -Wait with -CreationFlags CreateSuspended together" -Category InvalidArgument
return
}
$detailLines = @(
"`tApplicationName: $ApplicationName",
"`tCommandLine: $CommandLine",
"`tCreationFlags: $CreationFlags",
"`tWorkingDirectory: $WorkingDirectory"
)
Write-Verbose -Message "Creating process with:`r`n$($detailLines -join "`r'n")"
$procInfo = $null
try {
if ($Token) {
$procInfo = [ProcessEx.ProcessMethods]::CreateProcessWithToken(
$Token,
$LogonFlags,
$ApplicationName,
$CommandLine,
$CreationFlags,
$Environment,
$WorkingDirectory,
$StartupInfo
)
}
else {
$procInfo = [ProcessEx.ProcessMethods]::CreateProcessWithLogon(
$Credential,
$LogonFlags,
$ApplicationName,
$CommandLine,
$CreationFlags,
$Environment,
$WorkingDirectory,
$StartupInfo
)
}
Write-Verbose -Message "Process created with PID $($procInfo.ProcessId) and TID $($procInfo.ThreadId)"
$proc = Get-Process -Id $procInfo.ProcessId
if ($Wait) {
Write-Verbose -Message "Resuming process and waiting for it to complete"
[ProcessEx.ProcessMethods]::ResumeAndWait($procInfo)
}
elseif (-not $suspend) {
Write-Verbose -Message "Resuming process and continuing on"
[ProcessEx.ProcessMethods]::ResumeThread($procInfo.Thread)
}
if ($PassThru) {
if ($Raw) {
$procInfo
}
else {
$proc
}
}
}
finally {
if ($procInfo -and -not $Raw) {
$procInfo.Dispose()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment