using Microsoft.Win32.SafeHandles; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.AccessControl; using Vanara.Extensions; using Vanara.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.Kernel32; #if NET20 namespace System.IO { /// Specifies whether the underlying handle is inheritable by child processes. public enum HandleInheritability { /// Specifies that the handle is not inheritable by child processes. None = 0, /// Specifies that the handle is inheritable by child processes. Inheritable = 1, } } #endif namespace Vanara.Diagnostics { /// The job limit type exceeded as communicated by a . public enum JobLimit { /// The or value was exceeded. IoRateControlTolerance = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, /// The value was exceeded. IoReadBytes = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_READ_BYTES, /// The value was exceeded. IoWriteBytes = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_WRITE_BYTES, /// The value was exceeded. JobLowMemory = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW, /// The value was exceeded. JobMemory = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, /// The or value was exceeded. NetRateControlTolerance = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, /// The value was exceeded. PerJobUserTime = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, /// The or value was exceeded. RateControlTolerance = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, } /// /// Represents a system Job Object that allows groups of processes to be managed as a unit. Job objects are nameable, securable, sharable /// objects that control attributes of the processes associated with them. Operations performed on a job object affect all processes /// associated with the job object. Examples include enforcing limits such as working set size and process priority or terminating all /// processes associated with a job. For more information see /// Job Objects. /// /// public class Job : IDisposable { internal SafeHJOB hJob; private readonly IoCompletionPort complPort; private bool disposedValue = false; private JobLimits limits; private JobNotifications notifications; private JobProcessCollection processes; private JobSettings settings; private JobStatistics stats; private Job(SafeHJOB jobHandle) { if (jobHandle is null || jobHandle.IsNull) throw new ArgumentNullException(nameof(jobHandle)); hJob = jobHandle; // Create completion port complPort = IoCompletionPort.Create(); complPort.AddKeyHandler(hJob.DangerousGetHandle().ToUIntPtr(), FireEventThread); AssociateCompletionPort(complPort.Handle, hJob.DangerousGetHandle().ToUIntPtr()); } /// Finalizes an instance of the class. ~Job() => Dispose(false); /// Action with a parameter passed by reference. /// The parameter type. /// The first parameter value. internal delegate void RefAction(ref T t1); /// Function with parameter passed by reference. /// The type of the parameter. /// The type of the return value. /// The type instance on which to act. /// The return value. internal delegate T2 RefFunc(ref T1 t1); /// /// Indicates that a process associated with the job exited with an exit code that indicates an abnormal exit (see the list following /// this table). /// public event EventHandler AbnormalProcessExit; /// /// Indicates that the active process count has been decremented to 0. For example, if the job currently has two active processes, /// the system sends this message after they both terminate. /// public event EventHandler ActiveProcessCountZero; /// Indicates that the active process limit has been exceeded. public event EventHandler ActiveProcessLimitExceeded; /// /// Indicates that the Settings property is set to /// and the end-of-job time limit has been reached. Upon posting this message, the time limit is canceled and /// the job's processes can continue to run. /// public event EventHandler EndOfJobTime; /// /// Indicates that a process has exceeded a per-process time limit. The system sends this message after the process termination has /// been requested. /// public event EventHandler EndofProcessTime; /// /// Indicates that a process associated with the job caused the job to exceed the job-wide memory limit (if one is in effect). The /// system does not send this message if the process has not yet reported its process identifier. /// public event EventHandler JobMemoryLimitExceeded; /// /// Indicates that a process associated with a job that has registered for resource limit notifications has exceeded one or more /// limits. The system does not send this message if the process has not yet reported its process identifier. /// public event EventHandler JobNotificationLimitExceeded; /// /// Indicates that a process has been added to the job. Processes added to a job at the time a completion port is associated are also reported. /// public event EventHandler NewProcess; /// Indicates that a process associated with the job has exited. public event EventHandler ProcessExited; /// /// Indicates that a process associated with the job has exceeded its memory limit (if one is in effect). The system does not send /// this message if the process has not yet reported its process identifier. /// public event EventHandler ProcessMemoryLimitExceeded; /// Exposes the handle (HJOB) of the job. /// The handle. public IntPtr Handle => hJob.DangerousGetHandle(); /// Notification limits that can be set for various properties. /// The notifications. public JobNotifications Notifications => notifications ?? (notifications = new JobNotifications(this)); /// Gets the processes assigned to this job. /// The process list for the job. public IReadOnlyCollection Processes => processes ?? (processes = new JobProcessCollection(this)); /// Gets or sets the list of processor groups to which the job is currently assigned. public IEnumerable ProcessorGroups { get { using var mem = new SafeHGlobalHandle(4); uint req; while (!QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformation, mem, mem.Size, out req)) { Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_MORE_DATA); mem.Size = req; } return mem.ToEnumerable((int)req / 2).TakeWhile(id => id > 0); } set { using var mem = SafeHGlobalHandle.CreateFromList(value); if (!SetInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformation, mem, mem.Size)) Win32Error.ThrowLastError(); } } /// Gets the hard limits for different runtime values of the job object. /// The runtime limits. public JobLimits RuntimeLimits => limits ?? (limits = new JobLimits(this)); /// Gets the job settings. /// The job settings. public JobSettings Settings => settings ?? (settings = new JobSettings(this)); /// Usage statistics for the job. /// Usage statistics. public JobStatistics Statistics => stats ?? (stats = new JobStatistics(this)); /// Creates or opens a job object. /// /// The name of the job. The name is limited to MAX_PATH characters. Name comparison is case-sensitive. /// If lpName is , the job is created without a name. /// /// If lpName matches the name of an existing event, semaphore, mutex, waitable timer, or file-mapping object, the function fails and /// an exception corresponding to ERROR_INVALID_HANDLE is thrown. This occurs because these objects share the same namespace. /// /// The object can be created in a private namespace. For more information, see Object Namespaces. /// /// Terminal Services: The name can have a "Global" or "Local" prefix to explicitly create the object in the global or session /// namespace. The remainder of the name can contain any character except the backslash character (). For more information, see /// Kernel Object Namespaces. /// /// /// /// An optional instance that specifies the security descriptor for the job object and determines whether /// child processes can inherit the returned handle. If , the job object gets a default security descriptor and /// the handle cannot be inherited. The ACLs in the default security descriptor for a job object come from the primary or /// impersonation token of the creator. /// /// /// If this value is , processes created by this process will inherit the handle. /// Otherwise, the processes do not inherit this handle. /// /// /// If the function succeeds, the return value is a object. The handle has the JOB_OBJECT_ALL_ACCESS access right. /// /// /// /// When a job is created, its accounting information is initialized to zero, all limits are inactive, and there are no associated /// processes. To assign a process to a job object, use the AssignProcessToJobObject function. To get or set limits for a job, use /// the object's properties. /// /// /// All processes associated with a job must run in the same session. A job is associated with the session of the first process to be /// assigned to the job. /// /// Windows Server 2003 and Windows XP: A job is associated with the session of the process that created it. /// /// If the job has the KillOnJobClose property set to , closing the last job object handle terminates all /// associated processes and then destroys the job object itself. /// /// public static Job Create(string jobName = null, JobSecurity jobSecurity = null, HandleInheritability inheritable = HandleInheritability.None) { var sa = GetSecAttr(jobSecurity, inheritable == HandleInheritability.Inheritable, out var hMem); var job = new Job(CreateJobObject(sa, jobName)); hMem?.Dispose(); return job; } /// Performs an implicit conversion from to . /// The job. /// The result of the conversion. public static implicit operator HJOB(Job job) => job.hJob; /// Performs an implicit conversion from to . /// The Job instance. /// The result of the conversion. public static implicit operator SafeWaitHandle(Job job) => new SafeWaitHandle(job.hJob.DangerousGetHandle(), false); /// Opens an existing job object. /// /// The name of the job to be opened. Name comparisons are case sensitive. /// This function can open objects in a private namespace. For more information, see Object Namespaces. /// /// Terminal Services: The name can have a "Global\" or "Local\" prefix to explicitly open the object in the global or session /// namespace. The remainder of the name can contain any character except the backslash character (\). For more information, see /// Kernel Object Namespaces. /// /// /// /// The access to the job object. This parameter can be one or more of the job object access rights. This access right is checked /// against any security descriptor for the object. /// /// /// If this value is , processes created by this process will inherit the handle. /// Otherwise, the processes do not inherit this handle. /// /// If the function succeeds, the return value is a object. public static Job Open(string jobName, JobAccessRight desiredAccess = JobAccessRight.JOB_OBJECT_ALL_ACCESS, HandleInheritability inheritable = HandleInheritability.None) => new Job(OpenJobObject((uint)desiredAccess, inheritable == HandleInheritability.Inheritable, jobName)); /// Assigns a process to an existing job object. /// /// /// The process to associate with the job object. The process must have the PROCESS_SET_QUOTA and PROCESS_TERMINATE access rights. /// /// /// If the process is already associated with a job, this job must be empty or it must be in the hierarchy of nested jobs to which /// the process already belongs, and it cannot have UI limits set. /// /// /// Windows 7, Windows Server 2008 R2, Windows XP with SP3, Windows Server 2008, Windows Vista and Windows Server 2003: The /// process must not already be assigned to a job; if it is, the function fails with . This /// behavior changed starting in Windows 8 and Windows Server 2012. /// /// Terminal Services: All processes within a job must run within the same session as the job. /// public void AssignProcess(Process process) { if (process is null) throw new ArgumentNullException(nameof(process)); CheckState(); if (!AssignProcessToJobObject(hJob, process)) Win32Error.ThrowLastError(); } /// Associates a completion port with this job. You can associate one completion port with a job. /// /// The completion port to use in the CompletionPort parameter of the PostQueuedCompletionStatus function when messages are sent on /// behalf of the job. /// /// Windows 8, Windows Server 2012, Windows 8.1, Windows Server 2012 R2, Windows 10 and Windows Server 2016: Specify /// to remove the association between the current completion port and the job. /// /// /// /// The value to use in the dwCompletionKey parameter of PostQueuedCompletionStatus when messages are sent on behalf of the job. /// public void AssociateCompletionPort(HANDLE completionPort, UIntPtr key = default) => CheckThenSet((ref JOBOBJECT_ASSOCIATE_COMPLETION_PORT i) => { i.CompletionKey = key; i.CompletionPort = completionPort; }); /// Determines whether the process is running in this job. /// /// The process to be tested. The handle must have the PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access right. /// /// if the job contains the specified process; otherwise, . public bool ContainsProcess(Process process) { if (process is null) throw new ArgumentNullException(nameof(process)); CheckState(); if (!IsProcessInJob(process, hJob, out var isIn)) Win32Error.ThrowLastError(); return isIn; } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Gets information about the control of the I/O rate for a job object. /// /// The name of the volume to query. If this value is , the function gets the information about I/O rate /// control for the job for all of the volumes for the system. /// /// /// An array of JOBOBJECT_IO_RATE_CONTROL_INFORMATION structures that contain the information about I/O rate control for the job. /// public JOBOBJECT_IO_RATE_CONTROL_INFORMATION[] GetIoRateControlInformation(string VolumeName = null) => QueryIoRateControlInformationJobObject(hJob, VolumeName); /// /// Grants or denies access to a handle to a User object to a job that has a user-interface restriction. When access is granted, all /// processes associated with the job can subsequently recognize and use the handle. When access is denied, the processes can no /// longer use the handle. For more information see User Objects. /// /// A handle to the User object. /// /// If this parameter is , all processes associated with the job can recognize and use the handle. If the /// parameter is , the processes cannot use the handle. /// /// /// /// /// The GrantUserAccess function can be called only from a process not associated with the job specified by the hJob /// parameter. The User handle must not be owned by a process or thread associated with the job. /// /// To create user-interface restrictions, user the property. /// public void GrantUserAccess(IUserHandle hUserObj, bool grant) { if (!User32.UserHandleGrantAccess(hUserObj.DangerousGetHandle(), hJob, grant)) Win32Error.ThrowLastError(); } /// /// Starts a process resource by specifying the name of an application and a set of command-line arguments, and associates the /// resource with a new Process component which is assigned to this job. /// /// The name of an application file to run in the process. /// Command-line arguments to pass when starting the process. /// /// A new that is associated with the process resource, or if no process resource is /// started. Note that a new process that's started alongside already running instances of the same process will be independent from /// the others. In addition, Start may return a non-null Process with its property already set to /// . In this case, the started process may have activated an existing instance of itself and then exited. /// public Process StartProcess(string filename, string arguments = null) => !string.IsNullOrEmpty(filename) ? StartProcess(new ProcessStartInfo(filename, arguments)) : throw new ArgumentNullException(nameof(filename)); /// /// Starts the process resource that is specified by the parameter containing process start information (for example, the file name /// of the process to start) and associates the resource with a new Process component which is assigned to this job. /// /// /// The that contains the information that is used to start the process, including the file name and /// any command-line arguments. /// /// /// A new that is associated with the process resource, or if no process resource is /// started. Note that a new process that's started alongside already running instances of the same process will be independent from /// the others. In addition, Start may return a non-null Process with its property already set to /// . In this case, the started process may have activated an existing instance of itself and then exited. /// public Process StartProcess(ProcessStartInfo startInfo) { startInfo.UseShellExecute = false; var proc = new Process { StartInfo = startInfo }; proc.StartEx(CREATE_PROCESS.CREATE_SUSPENDED); try { AssignProcess(proc); } catch { proc.Kill(); throw; } proc.ResumePrimaryThread(); return proc; } /// /// Terminates all processes currently associated with the job. If the job is nested, this function terminates all processes /// currently associated with the job and all of its child jobs in the hierarchy. /// /// The exit code to be used by all processes and threads in the job object. public void TerminateAllProcesses(uint exitCode = 0) { CheckState(); if (!TerminateJobObject(hJob, exitCode)) Win32Error.ThrowLastError(); } internal static SECURITY_ATTRIBUTES GetSecAttr(JobSecurity sec, bool inheritable, out ISafeMemoryHandle hMem) { hMem = null; if (sec is null && !inheritable) return null; hMem = new SafeHGlobalHandle(sec.GetSecurityDescriptorBinaryForm()); return new SECURITY_ATTRIBUTES { bInheritHandle = inheritable, lpSecurityDescriptor = hMem.DangerousGetHandle() }; } internal void CheckState() { if (disposedValue) throw new InvalidOperationException("Object has been disposed."); } internal T CheckThenGet(JOBOBJECTINFOCLASS iClass = 0) where T : struct => CheckThenGet(n => n, iClass); internal T CheckThenGet(Func func, JOBOBJECTINFOCLASS iClass = 0) where T2 : struct { CheckState(); if (iClass == 0 && !CorrespondingTypeAttribute.CanGet(out iClass)) throw new InvalidOperationException("Invalid property retrieval."); var n = QueryInformationJobObject(hJob, iClass); return func(n); } internal void CheckThenSet(RefAction action, JOBOBJECTINFOCLASS iClass = 0) where T : struct { CheckState(); if (iClass == 0 && !CorrespondingTypeAttribute.CanSet(out iClass)) throw new InvalidOperationException("Invalid property retrieval."); var info = CorrespondingTypeAttribute.CanGet(iClass, typeof(T)) ? QueryInformationJobObject(hJob, iClass) : default; action?.Invoke(ref info); SetInformationJobObject(hJob, iClass, info); } /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { limits?.Dispose(); notifications?.Dispose(); processes?.Dispose(); settings?.Dispose(); stats?.Dispose(); } // Close the completion port handle complPort.Dispose(); // Close the job. hJob.Dispose(); disposedValue = true; } } private void FireEventThread(uint msg, UIntPtr key, IntPtr ppid) { if (disposedValue) return; var t = new JobEventArgs((JOB_OBJECT_MSG)msg, ppid.ToInt32()); switch (t.JobMessage) { case JOB_OBJECT_MSG.JOB_OBJECT_MSG_END_OF_JOB_TIME: EndOfJobTime?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_END_OF_PROCESS_TIME: EndofProcessTime?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: ActiveProcessLimitExceeded?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: ActiveProcessCountZero?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_NEW_PROCESS: NewProcess?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_EXIT_PROCESS: ProcessExited?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: AbnormalProcessExit?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: ProcessMemoryLimitExceeded?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_JOB_MEMORY_LIMIT: JobMemoryLimitExceeded?.Invoke(this, t); break; case JOB_OBJECT_MSG.JOB_OBJECT_MSG_NOTIFICATION_LIMIT: var vi = GetViolation(); Debug.WriteLine($"Notification: {vi.ViolationLimitFlags}"); foreach (var l in vi.ViolationLimitFlags.GetFlags().Cast()) { object v = null, n = null; switch (l) { case JobLimit.JobMemory: v = vi.JobMemory; n = vi.JobMemoryLimit; break; case JobLimit.PerJobUserTime: v = vi.PerJobUserTime; n = vi.PerJobUserTimeLimit; break; case JobLimit.IoReadBytes: v = vi.IoReadBytes; n = vi.IoReadBytesLimit; break; case JobLimit.IoWriteBytes: v = vi.IoWriteBytes; n = vi.IoWriteBytesLimit; break; case JobLimit.RateControlTolerance: v = vi.RateControlTolerance; n = vi.RateControlToleranceLimit; break; case JobLimit.IoRateControlTolerance: v = vi.IoRateControlTolerance; n = vi.IoRateControlToleranceLimit; break; case JobLimit.JobLowMemory: v = vi.JobMemory; n = vi.JobLowMemoryLimit; break; case JobLimit.NetRateControlTolerance: v = vi.NetRateControlTolerance; n = vi.NetRateControlToleranceLimit; break; default: Debug.WriteLine($"Unable to process notification: {vi.ViolationLimitFlags}, {vi.LimitFlags}"); continue; } JobNotificationLimitExceeded?.Invoke(this, new JobNotificationEventArgs(t.JobMessage, t.ProcessId, l, v, n)); } break; default: break; } JOBOBJECT_LIMIT_VIOLATION_INFORMATION_2 GetViolation() { using var mem = SafeHeapBlock.CreateFromStructure(); if (!QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectLimitViolationInformation2, mem, mem.Size, out _)) { Debug.WriteLine($"Failed to get JOBOBJECT_LIMIT_VIOLATION_INFORMATION_2: {Win32Error.GetLastError()}"); if (!QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectLimitViolationInformation, mem, (uint)Marshal.SizeOf(typeof(JOBOBJECT_LIMIT_VIOLATION_INFORMATION)), out _)) Debug.WriteLine($"Failed to get JOBOBJECT_LIMIT_VIOLATION_INFORMATION: {Win32Error.GetLastError()}"); } return mem.ToStructure(); } } internal class JobProcessCollection : JobHelper, IReadOnlyCollection { public JobProcessCollection(Job j) : base(j) { } /// /// The total number of processes currently associated with the job. When a process is associated with a job, but the association /// fails because of a limit violation, this value is temporarily incremented. When the terminated process exits and all /// references to the process are released, this value is decremented. /// public int Count => (int)job.CheckThenGet().ActiveProcesses; public IEnumerator GetEnumerator() => new Enumerator(job); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private class Enumerator : IEnumerator { private int i; private Job job; private Process[] procs; public Enumerator(Job j) { job = j; Reset(); } public Process Current => procs[i]; object IEnumerator.Current => Current; public void Dispose() => job = null; public bool MoveNext() => ++i < procs.Length; public void Reset() { i = -1; using var mem = SafeHGlobalHandle.CreateFromStructure(); while (!Kernel32.QueryInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectBasicProcessIdList, mem.DangerousGetHandle(), mem.Size, out _)) { Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_MORE_DATA); mem.Size *= 2; } var l = mem.ToStructure(); procs = mem.ToEnumerable((int)l, 8).Select(p => { try { return Process.GetProcessById((int)p.ToUInt32()); } catch { return null; } }).Where(p => p != null).ToArray(); } } } } /// Contains information about a job object message. /// public class JobEventArgs : EventArgs { internal JobEventArgs(JOB_OBJECT_MSG msg, int id = 0) { JobMessage = msg; ProcessId = id; } /// Gets the type of job message posted. /// The job message. public JOB_OBJECT_MSG JobMessage { get; } /// Gets the process identifier of the process referred to by the message. /// The process identifier. This value can be 0. public int ProcessId { get; } } /// Base class for other classes that support the object. /// public abstract class JobHelper : IDisposable { /// The job object. protected Job job; /// Initializes a new instance of the class. /// Name of the job. protected JobHelper(string jobName) : this(Job.Open(jobName, JobAccessRight.JOB_OBJECT_QUERY | JobAccessRight.JOB_OBJECT_SET_ATTRIBUTES)) { } /// Initializes a new instance of the class. /// The job. protected JobHelper(Job job) => this.job = job; /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() => job = null; /// Gets field values from JOBOBJECT_BASIC_LIMIT_INFORMATION. /// The return type. /// The limit flag. /// The method to get the field. /// The value. internal T? GetBasic(JOBOBJECT_LIMIT_FLAGS flag, Func getter) where T : struct => job.CheckThenGet(n => n.LimitFlags.IsFlagSet(flag) ? (T?)getter(n) : null); /// Sets a field value in JOBOBJECT_BASIC_LIMIT_INFORMATION. /// The field type. /// The limit flag. /// The value. /// The method to set the field. internal void SetBasic(JOBOBJECT_LIMIT_FLAGS flag, T? value, Job.RefAction setter) where T : struct => job.CheckThenSet((ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => { i.LimitFlags = (JOBOBJECT_LIMIT_FLAGS)0xFF & i.LimitFlags.SetFlags(flag, value.HasValue); setter(ref i); }); } /// Settings for that set limits for different runtime values. /// public class JobLimits : JobHelper { /// Initializes a new instance of the class. /// Name of the job. public JobLimits(string jobName) : base(jobName) { } /// Initializes a new instance of the class. /// The job. public JobLimits(Job job) : base(job) { } /// /// Gets or sets the active process limit for the job. /// /// If you try to associate a process with a job, and this causes the active process count to exceed this limit, the process is /// terminated and the association fails. /// /// public uint? ActiveProcessLimit { get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS, n => n.ActiveProcessLimit); set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.ActiveProcessLimit = value.GetValueOrDefault()); } /// /// Gets or sets the job's CPU rate as a hard limit. If set, after the job reaches its CPU cycle limit for the current scheduling /// interval, no threads associated with the job will run until the next interval. /// /// /// Specifies the portion of processor cycles that the threads in a job object can use during each scheduling interval, as a /// percentage of cycles. This value is greater than 0.0 and less than equal to 100.0. If this value is , then /// this setting is disabled. /// /// Value must be greater than 0.0 and less than equal to 100.0. public double? CpuRateLimit { get => job.CheckThenGet((JOBOBJECT_CPU_RATE_CONTROL_INFORMATION n) => n.ControlFlags.IsFlagSet(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE) ? n.Union.CpuRate / 100.0 : (double?)null); set { if (value.HasValue && (value.Value <= 0.0 || value.Value > 100.0)) throw new ArgumentOutOfRangeException(nameof(CpuRateLimit)); job.CheckThenSet((ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION i) => { i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, value.HasValue); i.Union.CpuRate = value.HasValue ? (uint)(value.Value * 100) : 0; }); } } /// Gets or sets the CPU rate for the job as limited by minimum and maximum rates. /// /// /// Specifies the minimum and maximum portions of the processor cycles that the threads in a job object can reserve during each /// scheduling interval. Specify these rates as a percentage from 0.0 to 100.0. /// /// /// For the minimum rates to work correctly, the sum of the minimum rates for all of the job objects in the system cannot exceed /// 100%. After the job reaches the maximum limit for a scheduling interval, no threads associated with the job can run until the /// next scheduling interval. /// /// If this value is , then this setting is disabled. /// /// /// Values must be greater than or equal to 0.0 and less than equal to 100.0 and the minimum value must be less than the maximum value. /// public (double minPortion, double maxPortion)? CpuRatePortion { get => job.CheckThenGet((JOBOBJECT_CPU_RATE_CONTROL_INFORMATION n) => n.ControlFlags.IsFlagSet(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_MIN_MAX_RATE | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE) ? (n.Union.MinRate / 100.0, n.Union.MaxRate / 100.0) : ((double, double)?)null); set { if (value.HasValue && (value.Value.minPortion < 0.0 || value.Value.minPortion > 100.0 || value.Value.maxPortion < 0.0 || value.Value.maxPortion > 100.0 || value.Value.minPortion > value.Value.maxPortion)) throw new ArgumentOutOfRangeException(nameof(CpuRatePortion)); job.CheckThenSet((ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION i) => { i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_MIN_MAX_RATE | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, value.HasValue); i.Union.MinRate = value.HasValue ? (ushort)(value.Value.minPortion * 100) : (ushort)0; i.Union.MaxRate = value.HasValue ? (ushort)(value.Value.maxPortion * 100) : (ushort)0; }); } } /// Gets or sets the job's CPU rate when calculated based on its relative weight to the weight of other jobs. /// /// /// Specifies the scheduling weight of the job object, which determines the share of processor time given to the job relative to /// other workloads on the processor. /// /// /// This member can be a value from 1 through 9, where 1 is the smallest share and 9 is the largest share. The default is 5, which /// should be used for most workloads. /// /// If this value is , then this setting is disabled. /// /// weight1to9 public int? CpuRateRelativeWeight { get => job.CheckThenGet((JOBOBJECT_CPU_RATE_CONTROL_INFORMATION n) => n.ControlFlags.IsFlagSet(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE) ? (int)n.Union.Weight : (int?)null); set { if (value.HasValue && (value.Value < 1 || value.Value > 9)) throw new ArgumentOutOfRangeException(nameof(CpuRateRelativeWeight)); job.CheckThenSet((ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION i) => { i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, value.HasValue); i.Union.Weight = value.HasValue ? (uint)value.Value : 0; }); } } /// /// Gets or sets the limit for the virtual memory that can be committed for the job. /// If this value is , then this setting is disabled. /// public ulong? JobMemoryLimit { get => job.CheckThenGet((JOBOBJECT_EXTENDED_LIMIT_INFORMATION n) => n.BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY) ? (ulong?)n.JobMemoryLimit : null); set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, value.HasValue); i.JobMemoryLimit = value.GetValueOrDefault(); }); } /// /// Gets or sets the maximum bandwidth for outgoing network traffic for the job, in bytes. /// If this value is , then this setting is disabled. /// public ulong? MaxBandwidth { get { var info = job.CheckThenGet(); return info.ControlFlags.IsFlagSet(JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH) ? (ulong?)info.MaxBandwidth : null; } set { job.CheckThenSet((ref JOBOBJECT_NET_RATE_CONTROL_INFORMATION i) => { i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH | JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE, value.HasValue); i.MaxBandwidth = value.GetValueOrDefault(); }); } } /// /// Gets or sets the per-job user-mode execution time limit. /// /// The system adds the current time of the processes associated with the job to this limit. For example, if you set this limit to 1 /// minute, and the job has a process that has accumulated 5 minutes of user-mode time, the limit actually enforced is 6 minutes. /// /// /// The system periodically checks to determine whether the sum of the user-mode execution time for all processes is greater than /// this end-of-job limit. If it is, the action specified in the EndOfJobTimeAction property is carried out. By default, all /// processes are terminated and the status code is set to ERROR_NOT_ENOUGH_QUOTA. /// /// If this value is , then this setting is disabled. /// public TimeSpan? PerJobUserTimeLimit { get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, n => n.PerJobUserTimeLimit); set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.PerJobUserTimeLimit = value.GetValueOrDefault()); } /// /// Gets or sets the per-process user-mode execution time limit. /// /// The system periodically checks to determine whether each process associated with the job has accumulated more user-mode time than /// the set limit. If it has, the process is terminated. /// /// If the job is nested, the effective limit is the most restrictive limit in the job chain. /// If this value is , then this setting is disabled. /// public TimeSpan? PerProcessUserTimeLimit { get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_TIME, n => n.PerProcessUserTimeLimit); set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_TIME, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.PerProcessUserTimeLimit = value.GetValueOrDefault()); } /// /// Gets or sets the limit for the virtual memory that can be committed by a process. /// If this value is , then this setting is disabled. /// public ulong? ProcessMemoryLimit { get => job.CheckThenGet((JOBOBJECT_EXTENDED_LIMIT_INFORMATION n) => n.BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_MEMORY) ? (ulong?)n.ProcessMemoryLimit : null); set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_MEMORY, value.HasValue); i.ProcessMemoryLimit = value.GetValueOrDefault(); }); } /// /// Gets or sets the working set size in bytes for each process associated with the job. /// Both min and max must be zero or non-zero. /// If this value is , then this setting is disabled. /// public (SizeT min, SizeT max)? WorkingSetSize { get => job.CheckThenGet((JOBOBJECT_BASIC_LIMIT_INFORMATION n) => n.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_WORKINGSET) ? (n.MinimumWorkingSetSize, n.MaximumWorkingSetSize) : ((SizeT, SizeT)?)null); set { if (value.HasValue && ((value.Value.min == SizeT.Zero && value.Value.max != SizeT.Zero) || (value.Value.max == SizeT.Zero && value.Value.min != SizeT.Zero) || value.Value.min > value.Value.max)) throw new ArgumentOutOfRangeException(nameof(WorkingSetSize)); job.CheckThenSet((ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => { i.LimitFlags = i.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_WORKINGSET, value.HasValue); i.MinimumWorkingSetSize = value.HasValue ? value.Value.min : SizeT.Zero; i.MaximumWorkingSetSize = value.HasValue ? value.Value.max : SizeT.Zero; }); } } } /// Contains information about a job object limit notification message. /// public class JobNotificationEventArgs : JobEventArgs { internal JobNotificationEventArgs(JOB_OBJECT_MSG msg, int id, JobLimit k, object v, object n) : base(msg, id) { Limit = k; ReportedValue = v; NotificationLimit = n; } /// Gets the limit which was exceeded. /// The limit. public JobLimit Limit { get; } /// Gets the value of the notification limit. /// The notification limit value. public object NotificationLimit { get; } /// Gets the value of the limited item at the time of the notification. /// The reported value at the time of notification. public object ReportedValue { get; } } /// Settings for that set notification limits for different properties. /// public class JobNotifications : JobHelper { /// Initializes a new instance of the class. /// Name of the job. public JobNotifications(string jobName) : base(jobName) { } /// Initializes a new instance of the class. /// The job. public JobNotifications(Job job) : base(job) { } /// /// /// Gets or sets the extent to which a job can exceed its I/O rate control limits during the interval specified by the /// IoRateControlToleranceInterval member. /// /// If this value is , then this setting is disabled. /// This member can be one of the following values. If no value is specified, ToleranceHigh is used. /// /// /// /// Value /// Meaning /// /// /// ToleranceLow /// The job can exceed its I/O rate control limits for 20% of the tolerance interval. /// /// /// ToleranceMedium /// The job can exceed its I/O rate control limits for 40% of the tolerance interval. /// /// /// ToleranceHigh /// The job can exceed its I/O rate control limits for 60% of the tolerance interval. /// /// /// /// public JOBOBJECT_RATE_CONTROL_TOLERANCE? IoRateControlTolerance { get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, i => i.IoRateControlTolerance); set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => { i.IoRateControlTolerance = value.GetValueOrDefault(); if (value.HasValue && i.IoRateControlToleranceInterval == 0) i.IoRateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort; }); } /// /// /// Gets or sets the interval during which a job's I/O usage is monitored to determine whether the job has exceeded its I/O rate /// control limits. /// /// If this value is , then this setting is disabled. /// This member can be one of the following values. If no value is specified, ToleranceIntervalShort is used. /// /// /// /// Value /// Meaning /// /// /// ToleranceIntervalShort /// The tolerance interval is 10 seconds. /// /// /// ToleranceIntervalMedium /// The tolerance interval is one minute. /// /// /// ToleranceIntervalLong /// The tolerance interval is 10 minutes. /// /// /// /// public JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL? IoRateControlToleranceInterval { get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, i => i.IoRateControlToleranceInterval); set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => { i.IoRateControlToleranceInterval = value.GetValueOrDefault(); if (value.HasValue && i.IoRateControlTolerance == 0) i.IoRateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceHigh; }); } /// /// Gets or sets the notification limit for total I/O bytes read by all processes in the job. /// If this value is , then this setting is disabled. /// public ulong? IoReadBytesLimit { get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_READ_BYTES, i => i.IoReadBytesLimit); set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_READ_BYTES, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.IoReadBytesLimit = value.GetValueOrDefault()); } /// /// Gets or sets the notification limit for total I/O bytes written by all processes in the job. /// If this value is , then this setting is disabled. /// public ulong? IoWriteBytesLimit { get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_WRITE_BYTES, i => i.IoWriteBytesLimit); set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_WRITE_BYTES, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.IoWriteBytesLimit = value.GetValueOrDefault()); } /// /// The notification limit minimum for the total virtual memory that can be committed by all processes in the job, in bytes. The /// minimum value is 4096. /// If this value is , then this setting is disabled. /// public ulong? JobLowMemoryLimit { get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW, i => i.JobLowMemoryLimit); set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => i.JobLowMemoryLimit = value.GetValueOrDefault()); } /// /// The notification limit for total virtual memory that can be committed by all processes in the job, in bytes. The minimum value is 4096. /// If this value is , then this setting is disabled. /// public ulong? JobMemoryLimit { get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, i => i.JobMemoryLimit); set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.JobMemoryLimit = value.GetValueOrDefault()); } /// /// /// Gets or sets the extent to which a job can exceed its network rate control limits during the interval specified by the /// NetRateControlToleranceInterval member. /// /// If this value is , then this setting is disabled. /// This member can be one of the following values. If no value is specified, ToleranceHigh is used. /// /// /// /// Value /// Meaning /// /// /// ToleranceLow /// The job can exceed its network rate control limits for 20% of the tolerance interval. /// /// /// ToleranceMedium /// The job can exceed its network rate control limits for 40% of the tolerance interval. /// /// /// ToleranceHigh /// The job can exceed its network rate control limits for 60% of the tolerance interval. /// /// /// /// public JOBOBJECT_RATE_CONTROL_TOLERANCE? NetRateControlTolerance { get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, i => i.NetRateControlTolerance); set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => { i.NetRateControlTolerance = value.GetValueOrDefault(); if (value.HasValue && i.NetRateControlToleranceInterval == 0) i.NetRateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort; }); } /// /// /// Gets or sets the interval during which a job's network usage is monitored to determine whether the job has exceeded its network /// rate control limits. /// /// If this value is , then this setting is disabled. /// This member can be one of the following values. If no value is specified, ToleranceIntervalShort is used. /// /// /// /// Value /// Meaning /// /// /// ToleranceIntervalShort /// The tolerance interval is 10 seconds. /// /// /// ToleranceIntervalMedium /// The tolerance interval is one minute. /// /// /// ToleranceIntervalLong /// The tolerance interval is 10 minutes. /// /// /// /// public JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL? NetRateControlToleranceInterval { get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, i => i.NetRateControlToleranceInterval); set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => { i.NetRateControlToleranceInterval = value.GetValueOrDefault(); if (value.HasValue && i.NetRateControlTolerance == 0) i.NetRateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceHigh; }); } /// /// Gets or sets the notification limit for per-job user-mode execution time, in 100-nanosecond ticks. /// If this value is , then this setting is disabled. /// /// The system adds the accumulated execution time of processes associated with the job to this limit when the limit is set. For /// example, if a process associated with the job has already accumulated 5 minutes of user-mode execution time and the limit is set /// to 1 minute, the limit actually enforced is 6 minutes. /// /// /// To specify PerJobUserTimeLimit as an enforceable limit and terminate processes in jobs that exceed the limit, see the /// JOBOBJECT_BASIC_LIMIT_INFORMATION structure. /// /// public TimeSpan? PerJobUserTimeLimit { get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, i => i.PerJobUserTimeLimit); set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.PerJobUserTimeLimit = value.GetValueOrDefault()); } /// /// /// Gets or sets the extent to which a job can exceed its CPU rate control limits during the interval specified by the /// RateControlToleranceInterval member. /// /// If this value is , then this setting is disabled. /// This member can be one of the following values. If no value is specified, ToleranceHigh is used. /// /// /// /// Value /// Meaning /// /// /// ToleranceLow /// The job can exceed its CPU rate control limits for 20% of the tolerance interval. /// /// /// ToleranceMedium /// The job can exceed its CPU rate control limits for 40% of the tolerance interval. /// /// /// ToleranceHigh /// The job can exceed its CPU rate control limits for 60% of the tolerance interval. /// /// /// /// public JOBOBJECT_RATE_CONTROL_TOLERANCE? RateControlTolerance { get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, i => i.RateControlTolerance); set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => { i.RateControlTolerance = value.GetValueOrDefault(); if (value.HasValue && i.RateControlToleranceInterval == 0) i.RateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort; }); } /// /// /// Gets or sets the interval during which a job's CPU usage is monitored to determine whether the job has exceeded its CPU rate /// control limits. /// /// If this value is , then this setting is disabled. /// This member can be one of the following values. If no value is specified, ToleranceIntervalShort is used. /// /// /// /// Value /// Meaning /// /// /// ToleranceIntervalShort /// The tolerance interval is 10 seconds. /// /// /// ToleranceIntervalMedium /// The tolerance interval is one minute. /// /// /// ToleranceIntervalLong /// The tolerance interval is 10 minutes. /// /// /// /// public JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL? RateControlToleranceInterval { get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, i => i.RateControlToleranceInterval); set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => { i.RateControlToleranceInterval = value.GetValueOrDefault(); if (value.HasValue && i.RateControlTolerance == 0) i.RateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceHigh; }); } /// Gets field values from JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION. /// The return type. /// The limit flag. /// The method to get the field. /// The value. private T? Get1(JOBOBJECT_LIMIT_FLAGS flag, Func getter) where T : struct => job.CheckThenGet(n => n.LimitFlags.IsFlagSet(flag) ? (T?)getter(n) : null); /// Gets field values from JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2. /// The return type. /// The limit flag. /// The method to get the field. /// The value. private T? Get2(JOBOBJECT_LIMIT_FLAGS flag, Func getter) where T : struct => job.CheckThenGet(n => n.LimitFlags.IsFlagSet(flag) ? (T?)getter(n) : null); /// Sets a field value in JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION. /// The field type. /// The limit flag. /// The value. /// The method to set the field. private void Set1(JOBOBJECT_LIMIT_FLAGS flag, T? value, Job.RefAction setter) where T : struct => job.CheckThenSet((ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => { i.LimitFlags = i.LimitFlags.SetFlags(flag, value.HasValue); setter(ref i); }); /// Sets a field value in JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2. /// The field type. /// The limit flag. /// The value. /// The method to set the field. private void Set2(JOBOBJECT_LIMIT_FLAGS flag, T? value, Job.RefAction setter) where T : struct => job.CheckThenSet((ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => { i.LimitFlags = i.LimitFlags.SetFlags(flag, value.HasValue); setter(ref i); }); } /// Represents the security access rights of a job object. public class JobSecurity : ObjectSecurity { /// public JobSecurity() : base(true, System.Security.AccessControl.ResourceType.KernelObject) { } } /// Settings related to job objects. public class JobSettings : JobHelper { /// Initializes a new instance of the class. /// Name of the job. public JobSettings(string jobName) : base(jobName) { } /// Initializes a new instance of the class. /// The job. public JobSettings(Job job) : base(job) { } /// /// Gets or sets the processor affinity for all processes associated with the job. /// /// The affinity must be a subset of the system affinity mask obtained by calling the GetProcessAffinityMask function. The /// affinity of each thread is set to this value, but threads are free to subsequently set their affinity, as long as it is a subset /// of the specified affinity mask. Processes cannot set their own affinity mask. /// /// If this value is , then this setting is disabled. /// public UIntPtr? Affinity { get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_AFFINITY, n => n.Affinity); set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_AFFINITY, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.Affinity = value.GetValueOrDefault()); } /// /// If any process associated with the job creates a child process using the CREATE_BREAKAWAY_FROM_JOB flag while this value is /// , the child process is not associated with the job. /// public bool ChildProcessBreakawayAllowed { get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_BREAKAWAY_OK); set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_BREAKAWAY_OK, value); }); } /// /// If , allows any process associated with the job to create child processes that are not associated with the /// job. If the job is nested and its immediate job object allows breakaway, the child process breaks away from the immediate job /// object and from each job in the parent job chain, moving up the hierarchy until it reaches a job that does not permit breakaway. /// If the immediate job object does not allow breakaway, the child process does not break away even if jobs in its parent job chain /// allow it. /// public bool ChildProcessSilentBreakawayAllowed { get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK); set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, value); }); } /// /// If , Forces a call to the SetErrorMode function with the SEM_NOGPFAULTERRORBOX flag for each process /// associated with the job. If an exception occurs and the system calls the UnhandledExceptionFilter function, the debugger will be /// given a chance to act. If there is no debugger, the functions returns EXCEPTION_EXECUTE_HANDLER. Normally, this will cause /// termination of the process with the exception code as the exit status. /// public bool DieOnUnhandledException { get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION); set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, value); }); } /// /// The value to use for the Differentiated Service code point (DSCP) field to turn on network quality of service (QoS) for all /// outgoing network traffic generated by the processes of the job object. The valid range is from 0x00 through 0x3F. For information /// about DSCP, see Differentiated Services. /// If this value is , then this setting is disabled. /// public byte? DscpTag { get { var info = job.CheckThenGet(); return info.ControlFlags.IsFlagSet(JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_DSCP_TAG | JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE) ? (byte?)info.DscpTag : null; } set { if (value > 0x3F) throw new ArgumentOutOfRangeException(nameof(DscpTag)); var flag = JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_DSCP_TAG; if (value.HasValue) flag |= JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE; job.CheckThenSet((ref JOBOBJECT_NET_RATE_CONTROL_INFORMATION i) => { i.ControlFlags = flag; i.DscpTag = value.GetValueOrDefault(); }); } } /// Gets or sets the list of processor groups to which the job is currently assigned. public IEnumerable GroupAffinity { get { var gaSz = Marshal.SizeOf(typeof(GROUP_AFFINITY)); using var mem = new SafeHGlobalHandle(gaSz); uint req; while (!QueryInformationJobObject(job.hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx, mem, mem.Size, out req)) { Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_MORE_DATA); mem.Size = req; } return mem.ToArray((int)req / gaSz); } set { using var mem = SafeHGlobalHandle.CreateFromList(value); if (!SetInformationJobObject(job.hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx, mem, mem.Size)) Win32Error.ThrowLastError(); } } /// Causes all processes associated with the job to terminate when the last handle to the job is closed. /// to kill all processes when the job is closed; otherwise, . public bool KillOnJobClose { get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE); set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, value); }); } /// /// Gets or sets the priority class for all processes associated with the job. /// /// Processes and threads cannot modify their priority class. The calling process must enable the SE_INC_BASE_PRIORITY_NAME privilege. /// /// If this value is , then this setting is disabled. /// public ProcessPriorityClass? PriorityClass { get => (ProcessPriorityClass?)GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PRIORITY_CLASS, n => n.PriorityClass); set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PRIORITY_CLASS, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.PriorityClass = (uint)value.GetValueOrDefault()); } /// /// Gets or sets the scheduling class for all processes associated with the job. /// /// The valid values are 0 to 9. Use 0 for the least favorable scheduling class relative to other threads, and 9 for the most /// favorable scheduling class relative to other threads. By default, this value is 5. To use a scheduling class greater than 5, the /// calling process must enable the SE_INC_BASE_PRIORITY_NAME privilege. /// /// If this value is , then this setting is disabled. /// public uint? SchedulingClass { get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SCHEDULING_CLASS, n => n.SchedulingClass); set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SCHEDULING_CLASS, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.SchedulingClass = value.GetValueOrDefault()); } /// /// Determines if all processes will be terminated when the end-of-job time limit has been exceeded or if only an event will be sent. /// The default is to terminate all processes. /// /// If , the system terminates all processes and sets the exit status to ERROR_NOT_ENOUGH_QUOTA. The processes /// cannot prevent or delay their own termination. The job object is set to the signaled state and remains signaled until this limit /// is reset. No additional processes can be assigned to the job until the limit is reset. This is the default termination action. /// /// /// If , the system generates the event. After the completion packet is posted, /// the system clears the end-of-job time limit, and processes in the job can continue their execution. If no completion port is /// associated with the job when the time limit has been exceeded, the action taken is the same as for JOB_OBJECT_TERMINATE_AT_END_OF_JOB. /// /// public bool TerminateProcessesAtEndOfJobTimeLimit { get => job.CheckThenGet().EndOfJobTimeAction == JOBOBJECT_END_OF_JOB_TIME_ACTION.JOB_OBJECT_TERMINATE_AT_END_OF_JOB; set => job.CheckThenSet((ref JOBOBJECT_END_OF_JOB_TIME_INFORMATION i) => i.EndOfJobTimeAction = (JOBOBJECT_END_OF_JOB_TIME_ACTION)(value ? 0 : 1)); } /// /// The restriction class for the user interface. This member can be one or more of the following values. /// /// /// /// Value /// Meaning /// /// /// JOB_OBJECT_UILIMIT_DESKTOP /// /// Prevents processes associated with the job from creating desktops and switching desktops using the CreateDesktop and /// SwitchDesktop functions. /// /// /// /// JOB_OBJECT_UILIMIT_DISPLAYSETTINGS /// Prevents processes associated with the job from calling the ChangeDisplaySettings function. /// /// /// JOB_OBJECT_UILIMIT_EXITWINDOWS /// Prevents processes associated with the job from calling the ExitWindows or ExitWindowsEx function. /// /// /// JOB_OBJECT_UILIMIT_GLOBALATOMS /// /// Prevents processes associated with the job from accessing global atoms. When this flag is used, each job has its own atom table. /// /// /// /// JOB_OBJECT_UILIMIT_HANDLES /// Prevents processes associated with the job from using USER handles owned by processes not associated with the same job. /// /// /// JOB_OBJECT_UILIMIT_READCLIPBOARD /// Prevents processes associated with the job from reading data from the clipboard. /// /// /// JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS /// Prevents processes associated with the job from changing system parameters by using the SystemParametersInfo function. /// /// /// JOB_OBJECT_UILIMIT_WRITECLIPBOARD /// Prevents processes associated with the job from writing data to the clipboard. /// /// /// /// public JOBOBJECT_UILIMIT_FLAGS UIRestrictionsClass { get => job.CheckThenGet().UIRestrictionsClass; set => job.CheckThenSet((ref JOBOBJECT_BASIC_UI_RESTRICTIONS i) => i.UIRestrictionsClass = value); } } /// Gets statistics for a job object. /// public class JobStatistics : JobHelper { /// Initializes a new instance of the class with a job name. /// Name of the job. public JobStatistics(string jobName) : base(jobName) { } /// Initializes a new instance of the class with a job instance. /// The job. public JobStatistics(Job job) : base(job) { } /// The number of I/O operations performed, other than read and write operations. public ulong OtherOperationCount => job.CheckThenGet().IoInfo.OtherOperationCount; /// The number of bytes transferred during operations other than read and write operations. public ulong OtherTransferCount => job.CheckThenGet().IoInfo.OtherTransferCount; /// The peak memory usage of all processes currently associated with the job. public ulong PeakJobMemoryUsed => job.CheckThenGet().PeakJobMemoryUsed; /// The peak memory used by any process ever associated with the job. public ulong PeakProcessMemoryUsed => job.CheckThenGet().PeakProcessMemoryUsed; /// The number of read operations performed. public ulong ReadOperationCount => job.CheckThenGet().IoInfo.ReadOperationCount; /// The number of bytes read. public ulong ReadTransferCount => job.CheckThenGet().IoInfo.ReadTransferCount; /// /// /// The total amount of kernel-mode execution time for all active processes associated with the job (as well as all terminated /// processes no longer associated with the job) since the last call that set a per-job kernel-mode time limit, in 100-nanosecond ticks. /// /// This member is set to zero on creation of the job, and each time a per-job kernel-mode time limit is established. /// public TimeSpan ThisPeriodTotalKernelTime => job.CheckThenGet().ThisPeriodTotalKernelTime; /// /// /// The total amount of user-mode execution time for all active processes associated with the job (as well as all terminated /// processes no longer associated with the job) since the last call that set a per-job user-mode time limit, in 100-nanosecond ticks. /// /// This member is set to 0 on creation of the job, and each time a per-job user-mode time limit is established. /// public TimeSpan ThisPeriodTotalUserTime => job.CheckThenGet().ThisPeriodTotalUserTime; /// /// The total amount of kernel-mode execution time for all active processes associated with the job, as well as all terminated /// processes no longer associated with the job, in 100-nanosecond ticks. /// public TimeSpan TotalKernelTime => job.CheckThenGet().TotalKernelTime; /// /// The total number of page faults encountered by all active processes associated with the job, as well as all terminated processes /// no longer associated with the job. /// public uint TotalPageFaultCount => job.CheckThenGet().TotalPageFaultCount; /// /// The total number of processes associated with the job during its lifetime, including those that have terminated. For example, /// when a process is associated with a job, but the association fails because of a limit violation, this value is incremented. /// public uint TotalProcesses => job.CheckThenGet().TotalProcesses; /// The total number of processes terminated because of a limit violation. public uint TotalTerminatedProcesses => job.CheckThenGet().TotalTerminatedProcesses; /// /// The total amount of user-mode execution time for all active processes associated with the job, as well as all terminated /// processes no longer associated with the job, in 100-nanosecond ticks. /// public TimeSpan TotalUserTime => job.CheckThenGet().TotalUserTime; /// The number of write operations performed. public ulong WriteOperationCount => job.CheckThenGet().IoInfo.WriteOperationCount; /// The number of bytes written. public ulong WriteTransferCount => job.CheckThenGet().IoInfo.WriteTransferCount; } }