Figure 1   StartRestrictedProcess

void StartRestrictedProcess() {
    // Create a job kernel object
    HANDLE hjob = CreateJobObject(NULL, NULL);

    // Place some restrictions on processes in the job

    // First, set some basic restrictions
    JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };

    // The process always runs in the idle priority class
    jobli.PriorityClass = IDLE_PRIORITY_CLASS;

    // The job cannot use more than 1 second of CPU time
    // 1 second in 100-ns intervals
    jobli.PerJobUserTimeLimit.QuadPart = 10000000;    

    // These are the only 2 restrictions I want placed on the job (process)
    jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS |              
                       JOB_OBJECT_LIMIT_JOB_TIME;
    SetInformationJobObject(hjob, JobObjectBasicLimitInformation, &jobli, 
                            sizeof(jobli));


    // Second, set some UI restrictions
    JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
    jobuir.UIRestrictionsClass  = JOB_OBJECT_UILIM3wIT_NONE;    // A fancy 0

    // The process can't logoff the system
    jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;

    // The process can't access USER object (like other windows) in the system
    jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES

    SetInformationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir, 
                            sizeof(jobuir));


    // Spawn the process that is to be in the job
    // Note: You must first spawn the process and then place the process in the 
    // job.
    // This means that the process's thread must be initially suspended so that 
    // it can't execute any code outside of the job's restrictions.
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    CreateProcess(NULL, "CMD", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, 
                  &si, &pi);

    // Place the process in the job.
    // Note: if this process spawns any children, the children are 
    //        automatically associated with the same job
    AssignProcessToJobObject(hjob, pi.hProcess);

    // Now, we can allow the child process's thread to execute code.
    ResumeThread(pi.hThread);
    CloseHandle(pi.hThread);

    // Wait for the process to terminate or for all the job's allotted CPU time 	    
    // to be used
    HANDLE h[2];
    h[0] = pi.hProcess;
    h[1] = hjob;
    DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
    switch (dw - WAIT_OBJECT_0) {
        case 0:
            // The process has terminated…
            break;
        case 1:
            // All of the job's allotted CPU time was used…
            break;
    }

    // Clean-up properly
    CloseHandle(pi.hProcess);
    CloseHandle(hjob);
}

Figure 2   Setting Restrictions

To Set
Value for Second Parameter
Structure Used for Third Parameter
Basic Limits
JobObjectBasicLimitInformation
JOBOBJECT_BASIC_LIMIT_INFORMATION
Extended Basic Limits
JobObjectExtendedLimitInformation
JOBOBJECT_EXTENDED_LIMIT_INFORMATION
Basic UI Restrictions
JobObjectBasicUIRestrictions
JOBOBJECT_BASIC_UI_RESTRICTIONS
Security Limits
JobObjectSecurityLimitInformation
JOBOBJECT_SECURITY_LIMIT_INFORMATION


Figure 3   JOBOBJECT_BASIC_LIMIT_INFORMATION
Member Descriptions

Member
Description
Notes
PerProcessUserTimeLimit
Specifies the maximum user mode time allotted to each process (in 100 nanosecond intervals).
The system automatically terminates any process that uses more than its allotted time.
PerJobUserTimeLimit
Specifies how much more user mode time can be used by processes in this job (in 100 Nanosecond intervals).
By default, the system automatically terminates all processes when this time limit is reached. You can change this value periodically as the job runs.
LimitFlags
Flags indicating which restrictions you want to apply to the job.
Explained in the article text.
MinimumWorkingSetSize/
MaximumWorkingSetSize
Specifies the minimum and maximum working set size for each process (not for all processes within the job).
Normally, a process’s working set can grow above its maximum, but setting the MaximumWorkingSetSize forces a hard limit; once the process’s working set reaches this limit, the process pages against itself. Calls to SetProcessWorkingSetSize by an individual process are ignored unless the process is just trying to empty its working set.
ActiveProcessLimit
Specifies the maximum number of processes that can run concurrently in the job.
Attempting to go over this limit causes the new process to be terminated with a “not enough quota” error.
Affinity
Specifies the subset of CPUs that can run the processes.
Individual processes can limit this even further.
PriorityClass
Specifies the priority class used by all processes.
If a process calls SetPriorityClass, the call will return success even though it actually fails. If the process calls GetPriorityClass, it returns what the process has set the priority class to, although this may not be the process’s actual priority class. In addition, SetThreadPriority fails to raise threads above normal priority, but may be used to lower a thread’s priority.
SchedulingClass
Specifies a relative time quantum difference assigned to threads in the job.
Value can be from 0 to 9, inclusive. 5 is the default.


Figure 4   UIRestrictionsClass Bit Flags

Flag
Description
JOB_OBJECT_UILIMIT_EXITWINDOWS
Prevents processes from logging off, shutting down, rebooting, or powering off the system via the ExitWindowsEx function
JOB_OBJECT_UILIMIT_READCLIPBOARD
Prevents processes from reading the clipboard
JOB_OBJECT_UILIMIT_WRITECLIPBOARD
Prevents processes from erasing the clipboard
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS
Prevents processes from changing system parameters via the SystemParametersInfo function
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS
Prevents processes from changing the display settings via the ChangeDisplaySettings function
JOB_OBJECT_UILIMIT_GLOBALATOMS
Gives the job its own global atom table and restricts processes in the job to accessing only the job’s table
JOB_OBJECT_UILIMIT_DESKTOP
Prevents processes from creating/switching desktops via the CreateDesktop/SwitchDesktop functions
JOB_OBJECT_UILIMIT_HANDLES
Prevents processes from using USER objects (such as HWNDs) created by processes outside the same job


Figure 6   JOBOBJECT_SECURITY_LIMIT_INFORMATION

Member
Description
SecurityLimitFlags
Flags indicating whether to disallow administrator access, disallow unrestricted token access, force a specific access token, or to disable certain SIDs/privileges
JobToken
Access token to be used by all processes in the job
SidsToDisable
Indicates which SIDs to disable for access checking
PrivilegesToDelete
Indicates which privileges to delete from the access token
RestrictedSids
Indicates a set of deny-only SIDs that should be added to the access token


Figure 7   JOBOBJECT_BASIC_ACCOUNTING_INFORMATION
Members

Member
Description
TotalUserTime
Specifies how much user mode CPU time processes in the job have used
TotalKernelTime
Specifies how much kernel mode CPU time processes in the job have used
ThisPeriodTotalUserTime
Like TotalUserTime except this value is reset to zero when SetInformationJobObject is called change to basic limit information and the JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME limit flag is not used
ThisPeriodTotalKernelTime
Like ThisPeriodTotalUserTime except it shows kernel mode time
TotalPageFaultCount
Specifies the total number of page faults processes in the job have accrued
TotalProcesses
Specifies the total number of processes that have ever been a part of the job
ActiveProcesses
Specifies the number of processes currently a part of the job
TotalTerminatedProcesses
Specifies the number of processes that have been killed due to exceeding their allotted CPU time limit


Figure 8   EnumProcessIdsInJob


void EnumProcessIdsInJob(HANDLE hjob) {

// I assume that there will never be more than 10 processes in this job
    #define MAX_PROCESS_IDS    10

// Calculate the number of bytes needed for structure & process IDs
    DWORD cb = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) +
        (MAX_PROCESS_IDS - 1) * sizeof(DWORD);

// Allocate the block of memory
    PJOBOBJECT_BASIC_PROCESS_ID_LIST pjobpil = _alloca(cb);

// Tell the function the maximum number of processes that we allocated space 
// for 
    pjobpil->NumberOfAssignedProcesses = MAX_PROCESS_IDS;

// Request the current set of process IDs
    QueryInformationJobObject(hjob, JobObjectBasicProcessIdList, pjobpil, cb, 
                              &cb);

// Enumerate the Process IDs
    for (int x = 0; x < pjobpil->NumberOfProcessIdsInList; x++) {
        // Use pjobpil->ProcessIdList[x]…
    }

// Since _alloca was used to allocate the memory, we don't need to free it here
}


Figure 11   Job Events

Event
Description
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO
Posted when no processes are running in the job.
JOB_OBJECT_MSG_END_OF_PROCESS_TIME
Posted when a process’s allotted CPU time is exceeded. The process is terminated and the process’s ID is given.
JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT
Posted when attempting to exceed the number of active processes in the job.
JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT
Posted when a process attempts to commit storage of the process’s limit. The process’s ID is given.
JOB_OBJECT_MSG_JOB_MEMORY_LIMIT
Posted when a process attempts to commit storage over the job’s limit. The process’s ID is given.
JOB_OBJECT_MSG_NEW_PROCESS
Posted when a process is added to a job. The process’s ID is given.
JOB_OBJECT_MSG_EXIT_PROCESS
Posted when a process terminates. The process’s ID is given.
JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS
Posted when a process terminates due to an unhandled exception. The process’s ID is given.
JOB_OBJECT_MSG_END_OF_JOB_TIME
Posted when the job’s allotted CPU time is exceeded. The processes are not terminated. You can allow them to continue running, set a new time limit, or call TerminateJobObject yourself.