Created
February 26, 2024 16:13
-
-
Save smourier/7da7be16be5f29c619951c462e8c2452 to your computer and use it in GitHub Desktop.
JobObject C# class with wait, support for ShellExecute, etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class JobObject : IDisposable | |
{ | |
private IntPtr _handle; | |
public JobObject(string name = null, bool terminateOnDispose = false) | |
{ | |
Name = name; | |
TerminateOnDispose = terminateOnDispose; | |
_handle = CreateJobObject(IntPtr.Zero, name); | |
if (_handle == IntPtr.Zero) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
} | |
public IntPtr Handle => _handle; | |
public string Name { get; } | |
public bool TerminateOnDispose { get; } | |
public virtual bool EnsureSta { get; set; } = true; // used only in ShellExecute case | |
public virtual bool UpdateJobListAttribute { get; set; } // honored only in ShellExecute case | |
public JOBOBJECT_BASIC_ACCOUNTING_INFORMATION? GetBasicAccountingInformation() | |
{ | |
var info = new JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(); | |
if (!QueryInformationJobObject(CheckDisposed(), JobObjectBasicAccountingInformation, ref info, Marshal.SizeOf(info), out _)) | |
return null; | |
return info; | |
} | |
public int Start(string commandLine) => Start(new ProcessStartInfo(commandLine)); | |
public int Start(string commandLine, string arguments) => Start(new ProcessStartInfo(commandLine, arguments)); | |
public virtual int Start(ProcessStartInfo startInfo) | |
{ | |
if (startInfo == null) | |
throw new ArgumentNullException(nameof(startInfo)); | |
// note we don't support everything in ProcessStartInfo (redirects, security, etc.) ... | |
if (startInfo.UseShellExecute) | |
return ShellExecute(startInfo); | |
var handle = CheckDisposed(); | |
var size = IntPtr.Zero; | |
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref size); | |
var list = Marshal.AllocHGlobal(size); | |
try | |
{ | |
if (!InitializeProcThreadAttributeList(list, 1, 0, ref size)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
try | |
{ | |
if (!UpdateProcThreadAttribute(list, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_JOB_LIST, ref handle, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var startup = new STARTUPINFOEXW | |
{ | |
lpAttributeList = list, | |
StartupInfo = new STARTUPINFOW | |
{ | |
cb = Marshal.SizeOf<STARTUPINFOEXW>(), | |
} | |
}; | |
if (startInfo.WindowStyle != ProcessWindowStyle.Normal) | |
{ | |
startup.StartupInfo.dwFlags |= STARTF.STARTF_USESHOWWINDOW; | |
switch (startInfo.WindowStyle) // SW_HIDE is 0 | |
{ | |
case ProcessWindowStyle.Minimized: | |
startup.StartupInfo.wShowWindow = SW_SHOWMINIMIZED; | |
break; | |
case ProcessWindowStyle.Maximized: | |
startup.StartupInfo.wShowWindow = SW_SHOWMAXIMIZED; | |
break; | |
} | |
} | |
var workingDirectory = startInfo.WorkingDirectory; | |
if (string.IsNullOrEmpty(workingDirectory)) | |
{ | |
workingDirectory = null; | |
} | |
var flags = PROCESS_CREATION_FLAGS.EXTENDED_STARTUPINFO_PRESENT; | |
if (startInfo.CreateNoWindow) | |
{ | |
flags |= PROCESS_CREATION_FLAGS.CREATE_NO_WINDOW; | |
} | |
var commandLine = BuildCommandLine(startInfo); | |
if (!CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, false, flags, IntPtr.Zero, workingDirectory, ref startup, out var pi)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
CloseHandle(pi.hProcess); | |
CloseHandle(pi.hThread); | |
return pi.dwProcessId; | |
} | |
finally | |
{ | |
DeleteProcThreadAttributeList(list); | |
} | |
} | |
finally | |
{ | |
Marshal.FreeHGlobal(list); | |
} | |
} | |
private int ShellExecute(ProcessStartInfo startInfo) | |
{ | |
if (!EnsureSta || Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) | |
return ShellExecuteInSta(startInfo); | |
var ret = 0; | |
var thread = new Thread(state => ret = ShellExecuteInSta(startInfo)) { IsBackground = true }; | |
thread.SetApartmentState(ApartmentState.STA); | |
thread.Start(); | |
thread.Join(); | |
return ret; | |
} | |
private int ShellExecuteInSta(ProcessStartInfo startInfo) | |
{ | |
var handle = CheckDisposed(); | |
var site = new ShellExecuteSite(handle, UpdateJobListAttribute); | |
var unk = Marshal.GetIUnknownForObject(site); | |
try | |
{ | |
var info = new SHELLEXECUTEINFOW | |
{ | |
cbSize = Marshal.SizeOf<SHELLEXECUTEINFOW>(), | |
fMask = SEE_MASK_FLAG_HINST_IS_SITE | SEE_MASK_NOASYNC, | |
lpFile = startInfo.FileName, | |
lpParameters = startInfo.Arguments, | |
lpVerb = startInfo.Verb, | |
lpDirectory = startInfo.WorkingDirectory, | |
hInstApp = unk, | |
}; | |
switch (startInfo.WindowStyle) | |
{ | |
case ProcessWindowStyle.Minimized: | |
info.nShow = SW_SHOWMINIMIZED; | |
break; | |
case ProcessWindowStyle.Maximized: | |
info.nShow = SW_SHOWMAXIMIZED; | |
break; | |
case ProcessWindowStyle.Normal: | |
info.nShow = SW_SHOWNORMAL; | |
break; | |
} | |
if (startInfo.ErrorDialog) | |
{ | |
info.hwnd = startInfo.ErrorDialogParentHandle; | |
} | |
else | |
{ | |
info.fMask |= SEE_MASK_FLAG_NO_UI; | |
} | |
if (!ShellExecuteExW(ref info)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
if (site.IsSuspended) | |
{ | |
AssignProcess(site.ProcessHandle); | |
ResumeThread(site.ThreadHandle); | |
} | |
return site.ProcessId; | |
} | |
finally | |
{ | |
Marshal.Release(unk); | |
} | |
} | |
public void WaitForAllProcessesExit(int milliseconds = Timeout.Infinite) | |
{ | |
if (GetProcessIdList().Count == 0) | |
return; | |
var handle = CheckDisposed(); | |
var portHandle = CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, IntPtr.Zero, 1); | |
if (portHandle == IntPtr.Zero) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
try | |
{ | |
var information = new JOBOBJECT_ASSOCIATE_COMPLETION_PORT { CompletionKey = handle, CompletionPort = portHandle }; | |
if (!SetInformationJobObject(CheckDisposed(), JobObjectAssociateCompletionPortInformation, ref information, Marshal.SizeOf(information))) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
do | |
{ | |
var key = handle; | |
if (!GetQueuedCompletionStatus(portHandle, out var msg, ref key, out var overlapped, milliseconds)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
if (key == handle && msg == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) | |
return; | |
} | |
while (true); | |
} | |
finally | |
{ | |
CloseHandle(portHandle); | |
} | |
} | |
public IEnumerable<Process> GetProcesses() => GetProcessIdList().Select(id => { try { return Process.GetProcessById(id); } catch { return null; } }).Where(p => p != null); | |
public IReadOnlyList<int> GetProcessIdList() | |
{ | |
const int int32Size = 4; | |
var size = 2 * int32Size + IntPtr.Size; // see JOBOBJECT_BASIC_PROCESS_ID_LIST | |
do | |
{ | |
var ptr = Marshal.AllocHGlobal(size); | |
QueryInformationJobObject(CheckDisposed(), JobObjectBasicProcessIdList, ptr, size, out var outSize); | |
if (outSize != size) | |
{ | |
Marshal.FreeHGlobal(ptr); | |
if (outSize <= 2 * int32Size) // error or nothing | |
return Array.Empty<int>(); | |
size = outSize; | |
continue; | |
} | |
var current = ptr + int32Size; | |
var array = new int[Marshal.ReadInt32(current)]; | |
current += int32Size; | |
for (var i = 0; i < array.Length; i++) | |
{ | |
array[i] = Marshal.ReadInt32(current); | |
current += IntPtr.Size; | |
} | |
Marshal.FreeHGlobal(ptr); | |
return array; | |
} | |
while (true); | |
} | |
public bool SetLimits(JOBOBJECT_EXTENDED_LIMIT_INFORMATION information) => SetInformationJobObject(CheckDisposed(), JobObjectExtendedLimitInformation, ref information, Marshal.SizeOf(information)); | |
public bool SetLimits(JOBOBJECT_BASIC_LIMIT_INFORMATION information) => SetInformationJobObject(CheckDisposed(), JobObjectBasicLimitInformation, ref information, Marshal.SizeOf(information)); | |
public bool AssignProcess(int processId) => AssignProcess(Process.GetProcessById(processId)?.Handle ?? IntPtr.Zero); | |
public bool AssignProcess(Process process) => AssignProcess(process.Handle); | |
public bool AssignProcess(IntPtr processHandle) => AssignProcessToJobObject(CheckDisposed(), processHandle); | |
public bool IsProcessInJob(int processId) => IsProcessInJob(Process.GetProcessById(processId)?.Handle ?? IntPtr.Zero); | |
public bool IsProcessInJob(Process process) => IsProcessInJob(process.Handle); | |
public bool IsProcessInJob(IntPtr processHandle) { IsProcessInJob(processHandle, CheckDisposed(), out var isIn); return isIn; } | |
public bool Terminate(int exitCode = 0) => TerminateJobObject(CheckDisposed(), exitCode); | |
public override string ToString() => Name + Handle; | |
protected IntPtr CheckDisposed() { var handle = _handle; if (handle == IntPtr.Zero) throw new ObjectDisposedException(nameof(Handle)); return handle; } | |
public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (disposing) | |
{ | |
var handle = Interlocked.Exchange(ref _handle, IntPtr.Zero); | |
if (handle != IntPtr.Zero) | |
{ | |
if (TerminateOnDispose) TerminateJobObject(handle, 0); | |
CloseHandle(handle); | |
} | |
} | |
} | |
private static string BuildCommandLine(ProcessStartInfo startInfo) | |
{ | |
var sb = new StringBuilder(); | |
var fileName = startInfo.FileName.Trim(); | |
var quoted = fileName.Length > 0 && fileName[0] == '"' && fileName[fileName.Length - 1] == '"'; | |
if (!quoted) | |
{ | |
sb.Append('"'); | |
} | |
sb.Append(fileName); | |
if (!quoted) | |
{ | |
sb.Append('"'); | |
} | |
if (!string.IsNullOrEmpty(startInfo.Arguments)) | |
{ | |
if (sb.Length > 0) | |
{ | |
sb.Append(' '); | |
} | |
sb.Append(startInfo.Arguments); | |
} | |
return sb.ToString(); | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct JOBOBJECT_BASIC_ACCOUNTING_INFORMATION | |
{ | |
public IntPtr TotalUserTime; | |
public IntPtr TotalKernelTime; | |
public IntPtr ThisPeriodTotalUserTime; | |
public IntPtr ThisPeriodTotalKernelTime; | |
public int TotalPageFaultCount; | |
public int TotalProcesses; | |
public int ActiveProcesses; | |
public int TotalTerminatedProcesses; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION | |
{ | |
public long PerProcessUserTimeLimit; | |
public long PerJobUserTimeLimit; | |
public JOBOBJECT_LIMIT_FLAGS LimitFlags; | |
public IntPtr MinimumWorkingSetSize; | |
public IntPtr MaximumWorkingSetSize; | |
public int ActiveProcessLimit; | |
public IntPtr Affinity; | |
public int PriorityClass; | |
public int SchedulingClass; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |
{ | |
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; | |
public IO_COUNTERS IoInfo; | |
public IntPtr ProcessMemoryLimit; | |
public IntPtr JobMemoryLimit; | |
public IntPtr PeakProcessMemoryUsed; | |
public IntPtr PeakJobMemoryUsed; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct IO_COUNTERS | |
{ | |
public ulong ReadOperationCount; | |
public ulong WriteOperationCount; | |
public ulong OtherOperationCount; | |
public ulong ReadTransferCount; | |
public ulong WriteTransferCount; | |
public ulong OtherTransferCount; | |
} | |
[Flags] | |
public enum JOBOBJECT_LIMIT_FLAGS | |
{ | |
JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001, | |
JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002, | |
JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004, | |
JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008, | |
JOB_OBJECT_LIMIT_AFFINITY = 0x00000010, | |
JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020, | |
JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000040, | |
JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080, | |
JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100, | |
JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200, | |
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400, | |
JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800, | |
JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000, | |
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000, | |
JOB_OBJECT_LIMIT_SUBSET_AFFINITY = 0x00004000, | |
JOB_OBJECT_LIMIT_JOB_MEMORY_LOW = 0x00008000, | |
JOB_OBJECT_LIMIT_JOB_READ_BYTES = 0x00010000, | |
JOB_OBJECT_LIMIT_JOB_WRITE_BYTES = 0x00020000, | |
JOB_OBJECT_LIMIT_RATE_CONTROL = 0x00040000, | |
JOB_OBJECT_LIMIT_IO_RATE_CONTROL = 0x00080000, | |
JOB_OBJECT_LIMIT_NET_RATE_CONTROL = 0x00100000, | |
} | |
private const int JobObjectBasicAccountingInformation = 1; | |
private const int JobObjectBasicLimitInformation = 2; | |
private const int JobObjectBasicProcessIdList = 3; | |
private const int JobObjectAssociateCompletionPortInformation = 7; | |
private const int JobObjectExtendedLimitInformation = 9; | |
private const int JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO = 4; | |
private const int PROC_THREAD_ATTRIBUTE_JOB_LIST = 0x2000d; | |
private const int SW_SHOWNORMAL = 1; | |
private const int SW_SHOWMINIMIZED = 2; | |
private const int SW_SHOWMAXIMIZED = 3; | |
private const int SEE_MASK_NOASYNC = 0x100; | |
private const int SEE_MASK_FLAG_NO_UI = 0x400; | |
private const int SEE_MASK_FLAG_HINST_IS_SITE = 0x8000000; | |
private const int E_NOINTERFACE = unchecked((int)0x80004002); | |
[StructLayout(LayoutKind.Sequential)] | |
private struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT | |
{ | |
public IntPtr CompletionKey; | |
public IntPtr CompletionPort; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
private struct STARTUPINFOW | |
{ | |
public int cb; | |
public IntPtr lpReserved; | |
public string lpDesktop; | |
public string lpTitle; | |
public int dwX; | |
public int dwY; | |
public int dwXSize; | |
public int dwYSize; | |
public int dwXCountChars; | |
public int dwYCountChars; | |
public int dwFillAttribute; | |
public STARTF dwFlags; | |
public short wShowWindow; | |
public short cbReserved2; | |
public IntPtr lpReserved2; | |
public IntPtr hStdInput; | |
public IntPtr hStdOutput; | |
public IntPtr hStdError; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct PROCESS_INFORMATION | |
{ | |
public IntPtr hProcess; | |
public IntPtr hThread; | |
public int dwProcessId; | |
public int dwThreadId; | |
} | |
[Flags] | |
private enum STARTF | |
{ | |
STARTF_USESHOWWINDOW = 0x00000001, | |
STARTF_USESIZE = 0x00000002, | |
STARTF_USEPOSITION = 0x00000004, | |
STARTF_USECOUNTCHARS = 0x00000008, | |
STARTF_USEFILLATTRIBUTE = 0x00000010, | |
STARTF_RUNFULLSCREEN = 0x00000020, | |
STARTF_FORCEONFEEDBACK = 0x00000040, | |
STARTF_FORCEOFFFEEDBACK = 0x00000080, | |
STARTF_USESTDHANDLES = 0x00000100, | |
STARTF_USEHOTKEY = 0x00000200, | |
STARTF_TITLEISLINKNAME = 0x00000800, | |
STARTF_TITLEISAPPID = 0x00001000, | |
STARTF_PREVENTPINNING = 0x00002000, | |
STARTF_UNTRUSTEDSOURCE = 0x00008000, | |
STARTF_HOLOGRAPHIC = 0x00040000, | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct STARTUPINFOEXW | |
{ | |
public STARTUPINFOW StartupInfo; | |
public IntPtr lpAttributeList; | |
} | |
[Flags] | |
private enum PROCESS_CREATION_FLAGS | |
{ | |
DEBUG_PROCESS = 0x00000001, | |
DEBUG_ONLY_THIS_PROCESS = 0x00000002, | |
CREATE_SUSPENDED = 0x00000004, | |
DETACHED_PROCESS = 0x00000008, | |
CREATE_NEW_CONSOLE = 0x00000010, | |
NORMAL_PRIORITY_CLASS = 0x00000020, | |
IDLE_PRIORITY_CLASS = 0x00000040, | |
HIGH_PRIORITY_CLASS = 0x00000080, | |
REALTIME_PRIORITY_CLASS = 0x00000100, | |
CREATE_NEW_PROCESS_GROUP = 0x00000200, | |
CREATE_UNICODE_ENVIRONMENT = 0x00000400, | |
CREATE_SEPARATE_WOW_VDM = 0x00000800, | |
CREATE_SHARED_WOW_VDM = 0x00001000, | |
CREATE_FORCEDOS = 0x00002000, | |
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, | |
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, | |
INHERIT_PARENT_AFFINITY = 0x00010000, | |
INHERIT_CALLER_PRIORITY = 0x00020000, | |
CREATE_PROTECTED_PROCESS = 0x00040000, | |
EXTENDED_STARTUPINFO_PRESENT = 0x00080000, | |
PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000, | |
PROCESS_MODE_BACKGROUND_END = 0x00200000, | |
CREATE_SECURE_PROCESS = 0x00400000, | |
CREATE_BREAKAWAY_FROM_JOB = 0x01000000, | |
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, | |
CREATE_DEFAULT_ERROR_MODE = 0x04000000, | |
CREATE_NO_WINDOW = 0x08000000, | |
PROFILE_USER = 0x10000000, | |
PROFILE_KERNEL = 0x20000000, | |
PROFILE_SERVER = 0x40000000, | |
CREATE_IGNORE_SYSTEM_DEFAULT = unchecked((int)0x80000000), | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
private struct SHELLEXECUTEINFOW | |
{ | |
public int cbSize; | |
public int fMask; | |
public IntPtr hwnd; | |
public string lpVerb; | |
public string lpFile; | |
public string lpParameters; | |
public string lpDirectory; | |
public int nShow; | |
public IntPtr hInstApp; | |
public IntPtr lpIDList; | |
public string lpClass; | |
public IntPtr hkeyClass; | |
public int dwHotKey; | |
public IntPtr hIcon; | |
public IntPtr hProcess; | |
} | |
[DllImport("kernel32", SetLastError = true)] | |
private extern static bool CloseHandle(IntPtr handle); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, int dwFlags, IntPtr attribute, ref IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); | |
[DllImport("kernel32")] | |
private static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList); | |
[DllImport("kernel32", CharSet = CharSet.Unicode)] | |
private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool TerminateJobObject(IntPtr hJob, int uExitCode); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool AssignProcessToJobObject(IntPtr hJob, SafeHandle hProcess); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool IsProcessInJob(SafeHandle processHandle, IntPtr jobHandle, out bool result); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool IsProcessInJob(IntPtr processHandle, IntPtr jobHandle, out bool result); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool SetInformationJobObject(IntPtr hJob, int jobObjectInformationClass, ref JOBOBJECT_BASIC_LIMIT_INFORMATION lpJobObjectInformation, int cbJobObjectInformationLength); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool SetInformationJobObject(IntPtr hJob, int jobObjectInformationClass, ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInformation, int cbJobObjectInformationLength); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool SetInformationJobObject(IntPtr hJob, int jobObjectInformationClass, ref JOBOBJECT_ASSOCIATE_COMPLETION_PORT lpJobObjectInformation, int cbJobObjectInformationLength); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool QueryInformationJobObject(IntPtr hJob, int jobObjectInformationClass, ref JOBOBJECT_BASIC_ACCOUNTING_INFORMATION lpJobObjectInformation, int cbJobObjectInformationLength, out int lpReturnLength); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool QueryInformationJobObject(IntPtr hJob, int jobObjectInformationClass, IntPtr lpJobObjectInformation, int cbJobObjectInformationLength, out int lpReturnLength); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern IntPtr CreateIoCompletionPort(IntPtr fileHandle, IntPtr existingCompletionPort, IntPtr completionKey, int numberOfConcurrentThreads); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern bool GetQueuedCompletionStatus(IntPtr completionPort, out int lpNumberOfBytesTransferred, ref IntPtr lpCompletionKey, out IntPtr lpOverlapped, int dwMilliseconds); | |
[DllImport("shell32", SetLastError = true)] | |
private static extern bool ShellExecuteExW(ref SHELLEXECUTEINFOW pExecInfo); | |
[DllImport("kernel32", SetLastError = true)] | |
private static extern int ResumeThread(IntPtr hThread); | |
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool CreateProcess( | |
string lpApplicationName, | |
string lpCommandLine, | |
IntPtr lpProcessAttributes, | |
IntPtr lpThreadAttributes, | |
bool bInheritHandles, | |
PROCESS_CREATION_FLAGS dwCreationFlags, | |
IntPtr lpEnvironment, | |
string lpCurrentDirectory, | |
ref STARTUPINFOEXW lpStartupInfo, | |
out PROCESS_INFORMATION lpProcessInformation); | |
private sealed class ShellExecuteSite : ShellExecuteSite.IServiceProvider, ShellExecuteSite.ICreatingProcess, ShellExecuteSite.ICreatedProcess | |
{ | |
private IntPtr _handle; | |
private bool _updateJobListAttribute; | |
public ShellExecuteSite(IntPtr handle, bool updateJobListAttribute) | |
{ | |
_handle = handle; | |
_updateJobListAttribute = updateJobListAttribute; | |
} | |
public int ProcessId { get; private set; } | |
public IntPtr ProcessHandle { get; private set; } | |
public IntPtr ThreadHandle { get; private set; } | |
public bool IsSuspended { get; private set; } | |
public int QueryService(ref Guid siid, ref Guid riid, out IntPtr ppv) | |
{ | |
if (riid != typeof(ICreatingProcess).GUID && riid != typeof(ICreatedProcess).GUID) | |
{ | |
ppv = IntPtr.Zero; | |
return E_NOINTERFACE; | |
} | |
var unk = Marshal.GetIUnknownForObject(this); | |
var hr = Marshal.QueryInterface(unk, ref riid, out ppv); | |
Marshal.Release(unk); | |
return hr; | |
} | |
public int OnCreating(ICreateProcessInputs inputs) | |
{ | |
if (_updateJobListAttribute && | |
inputs is ICreateProcessInputs2 inputs2 && | |
inputs2.UpdateProcThreadAttribute((IntPtr)PROC_THREAD_ATTRIBUTE_JOB_LIST, ref _handle, (IntPtr)IntPtr.Size) == 0) | |
return 0; | |
inputs.GetCreateFlags(out var createFlags); | |
inputs.SetCreateFlags(createFlags | PROCESS_CREATION_FLAGS.CREATE_SUSPENDED); | |
IsSuspended = true; | |
return 0; | |
} | |
public int OnCreated(ICreateProcessOutputs outputs) | |
{ | |
outputs.GetProcessId(out var id); | |
outputs.GetProcessHandle(out var handle); | |
outputs.GetThreadHandle(out var thandle); | |
ProcessId = id; | |
ProcessHandle = handle; | |
ThreadHandle = thandle; | |
return 0; | |
} | |
[ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
private interface IServiceProvider | |
{ | |
[PreserveSig] | |
int QueryService(ref Guid siid, ref Guid riid, out IntPtr ppv); | |
} | |
[ComImport, Guid("c2b937a9-3110-4398-8a56-f34c6342d244"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
private interface ICreatingProcess | |
{ | |
[PreserveSig] | |
int OnCreating(ICreateProcessInputs pcpi); | |
} | |
[ComImport, Guid("c2b937aa-3110-4398-8a56-f34c6342d244"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
private interface ICreatedProcess | |
{ | |
[PreserveSig] | |
int OnCreated(ICreateProcessOutputs pcpi); | |
} | |
[ComImport, Guid("da31f289-d9a7-4c12-a9e8-0e361e0a3df3"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface ICreateProcessOutputs | |
{ | |
[PreserveSig] | |
int GetProcessHandle(out IntPtr hande); | |
[PreserveSig] | |
int GetProcessId(out int id); | |
[PreserveSig] | |
int GetThreadHandle(out IntPtr handle); | |
} | |
[ComImport, Guid("F6EF6140-E26F-4D82-bAC4-E9BA5FD239A8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface ICreateProcessInputs | |
{ | |
[PreserveSig] | |
int GetCreateFlags(out PROCESS_CREATION_FLAGS pdwCreationFlags); | |
[PreserveSig] | |
int SetCreateFlags(PROCESS_CREATION_FLAGS dwCreationFlags); | |
[PreserveSig] | |
int AddCreateFlags(PROCESS_CREATION_FLAGS dwCreationFlags); | |
[PreserveSig] | |
int SetHotKey(short wHotKey); | |
[PreserveSig] | |
int AddStartupFlags(STARTF dwStartupInfoFlags); | |
[PreserveSig] | |
int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); | |
[PreserveSig] | |
int SetEnvironmentVariable([MarshalAs(UnmanagedType.LPWStr)] string pszName, [MarshalAs(UnmanagedType.LPWStr)] string pszValue); | |
} | |
[ComImport, Guid("a8cfdc36-0812-41e8-822a-0b69e430a412"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface ICreateProcessInputs2 : ICreateProcessInputs | |
{ | |
[PreserveSig] | |
new int GetCreateFlags(out PROCESS_CREATION_FLAGS pdwCreationFlags); | |
[PreserveSig] | |
new int SetCreateFlags(PROCESS_CREATION_FLAGS dwCreationFlags); | |
[PreserveSig] | |
new int AddCreateFlags(PROCESS_CREATION_FLAGS dwCreationFlags); | |
[PreserveSig] | |
new int SetHotKey(short wHotKey); | |
[PreserveSig] | |
new int AddStartupFlags(STARTF dwStartupInfoFlags); | |
[PreserveSig] | |
new int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); | |
[PreserveSig] | |
new int SetEnvironmentVariable([MarshalAs(UnmanagedType.LPWStr)] string pszName, [MarshalAs(UnmanagedType.LPWStr)] string pszValue); | |
[PreserveSig] | |
int UpdateProcThreadAttribute(IntPtr attribute, ref IntPtr lpValue, IntPtr cbSize); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment