Skip to content

Instantly share code, notes, and snippets.

@smourier
Created February 26, 2024 16:13
Show Gist options
  • Save smourier/7da7be16be5f29c619951c462e8c2452 to your computer and use it in GitHub Desktop.
Save smourier/7da7be16be5f29c619951c462e8c2452 to your computer and use it in GitHub Desktop.
JobObject C# class with wait, support for ShellExecute, etc.
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