Introduction to Just-In-Time Administration

Just-In-Time (JIT) Administration is a privileged access management practice where elevated permissions are granted only for the duration required to complete a specific task—typically minutes or hours—after which they are automatically revoked. This eliminates standing privileges: administrators no longer have persistent Domain Admin or local admin rights that an attacker can abuse 24/7. On Windows Server 2019, JIT administration is implemented using Microsoft Identity Manager (MIM) Privileged Access Management (PAM), Active Directory Privileged Access Groups with Time-Based Membership, or Azure AD Privileged Identity Management (Azure AD PIM) for hybrid environments.

Option 1: AD Privileged Access Management with Time-Based Membership

Windows Server 2016+ introduced Time-Based Group Membership as a native AD feature. This allows you to grant a user membership in a privileged group for a specified time period using the Kerberos temporal credential feature—the user’s Kerberos ticket is only valid for the duration of the membership window.


# Requires Windows Server 2016+ forest functional level with PAM feature enabled
Get-ADForest | Select-Object ForestMode
# Must be Windows2016Forest or higher

# Enable the PAM optional feature in the forest
Enable-ADOptionalFeature -Identity 'Privileged Access Management Feature' `
    -Scope ForestOrConfigurationSet `
    -Target (Get-ADForest).RootDomain

# Verify PAM feature is enabled
Get-ADOptionalFeature -Filter * | Where-Object { $_.Name -like '*Privileged*' } |
    Select-Object Name, EnabledScopes

# Create a privileged group to be used for JIT access
New-ADGroup -Name 'JIT-DomainAdmins' `
    -SamAccountName 'JIT-DomainAdmins' `
    -GroupScope Universal `
    -GroupCategory Security `
    -Path 'OU=Groups,OU=Tier 0,OU=Admin,DC=corp,DC=local' `
    -Description 'JIT elevation group - time-limited Domain Admin equivalent'

# Add the JIT group to Domain Admins as a nested group
Add-ADGroupMember -Identity 'Domain Admins' -Members 'JIT-DomainAdmins'

Granting Time-Limited Group Membership


# Grant a user time-limited membership in the JIT group
# This uses the msDS-MembersForTimePeriod / PAM API

# Grant 2 hours of Domain Admin via JIT group membership
$user = Get-ADUser -Identity 'adm-t0-jsmith'
$group = Get-ADGroup -Identity 'JIT-DomainAdmins'
$ttl = New-TimeSpan -Hours 2

Add-ADGroupMember -Identity $group `
    -Members $user `
    -MemberTimeToLive $ttl

# Verify the time-limited membership
Get-ADGroup -Identity 'JIT-DomainAdmins' -Properties member |
    Select-Object -ExpandProperty member

# Check the TTL on the membership (AD attribute)
Get-ADObject -Identity $group.DistinguishedName `
    -Partition (Get-ADForest).PartitionsContainer `
    -Properties 'msDS-MembersWithTTLForTimePeriod'

Building a JIT Request and Approval Workflow in PowerShell

A practical JIT system needs a request-approval workflow so that elevation does not happen without oversight. The following script implements a simple workflow using an approval mailbox and scheduled task:


# JIT-RequestElevation.ps1
# Run by the admin who needs temporary elevation

[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [string]$Justification,
    [ValidateRange(1,8)]
    [int]$HoursRequested = 2
)

$requester    = $env:USERNAME
$timestamp    = Get-Date -Format 'yyyy-MM-dd HH:mm'
$requestId    = [guid]::NewGuid().ToString('N').Substring(0,8).ToUpper()

# Write request to shared approval queue (network share or SharePoint)
$requestRecord = [PSCustomObject]@{
    RequestId   = $requestId
    Requester   = $requester
    Timestamp   = $timestamp
    Hours       = $HoursRequested
    Justification = $Justification
    Status      = 'Pending'
}

$requestFile = "\MGMT01JITRequests$requestId.json"
$requestRecord | ConvertTo-Json | Out-File $requestFile

# Notify approvers
Send-MailMessage -From '[email protected]' `
    -To '[email protected]' `
    -Subject "JIT Elevation Request [$requestId] from $requester" `
    -Body @"
Requester:    $requester
Request ID:   $requestId
Hours:        $HoursRequested
Justification: $Justification
Time:         $timestamp

To approve: Approve-JITRequest.ps1 -RequestId $requestId
To deny:    Deny-JITRequest.ps1 -RequestId $requestId
"@ `
    -SmtpServer 'smtp.corp.local'

Write-Output "JIT Request submitted. Request ID: $requestId"
Write-Output "You will be notified when your request is approved."

Approval Script


# Approve-JITRequest.ps1
# Run by an approver (Security team member)

[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [string]$RequestId
)

$requestFile = "\MGMT01JITRequests$RequestId.json"
$request = Get-Content $requestFile | ConvertFrom-Json

# Validate approver is in the approvers group
$approver = $env:USERNAME
$approvers = (Get-ADGroupMember -Identity 'JIT-Approvers').SamAccountName
if ($approver -notin $approvers) {
    Write-Error "You are not authorized to approve JIT requests."
    exit 1
}

# Grant time-limited elevation
$user  = Get-ADUser -Identity $request.Requester
$group = Get-ADGroup -Identity 'JIT-DomainAdmins'
$ttl   = New-TimeSpan -Hours $request.Hours

Add-ADGroupMember -Identity $group -Members $user -MemberTimeToLive $ttl

# Update request record
$request.Status   = 'Approved'
$request.ApprovedBy = $approver
$request.ApprovedAt = (Get-Date -Format 'yyyy-MM-dd HH:mm')
$request | ConvertTo-Json | Out-File $requestFile

# Write security event log entry
Write-EventLog -LogName Application -Source 'JIT-Admin' `
    -EventId 8001 -EntryType Information `
    -Message "JIT Elevation APPROVED: User=$($request.Requester) Hours=$($request.Hours) Approver=$approver RequestId=$RequestId"

# Notify requester
Send-MailMessage -From '[email protected]' `
    -To "$($request.Requester)@corp.local" `
    -Subject "JIT Elevation Request [$RequestId] APPROVED" `
    -Body "Your elevation request has been approved. You have $($request.Hours) hours of elevated access." `
    -SmtpServer 'smtp.corp.local'

Write-Output "JIT elevation granted to $($request.Requester) for $($request.Hours) hours."

Option 2: Azure AD Privileged Identity Management (PIM)

For hybrid environments with Azure AD, Azure AD PIM provides JIT elevation for Azure AD roles, Azure resource roles, and (via integration) on-premises AD roles. Users activate their eligible role assignments for a defined period through the Azure portal or PowerShell:


# Install Azure AD PIM module
Install-Module -Name AzureADPreview -Force
Connect-AzureAD

# Get all PIM role assignments
$context = Get-AzureADCurrentSessionInfo
$tenantId = $context.TenantId

# Using Microsoft Graph to manage PIM
Connect-MgGraph -Scopes 'RoleManagement.ReadWrite.Directory'

# List eligible role assignments
Get-MgRoleManagementDirectoryRoleEligibilitySchedule -All |
    Select-Object PrincipalId, RoleDefinitionId, StartDateTime, EndDateTime

# Activate a PIM eligible assignment (self-activation)
$globalAdminRole = Get-MgRoleManagementDirectoryRoleDefinition |
    Where-Object { $_.DisplayName -eq 'Global Administrator' }

New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter @{
    Action           = 'selfActivate'
    PrincipalId      = (Get-MgMe).Id
    RoleDefinitionId = $globalAdminRole.Id
    DirectoryScopeId = '/'
    Justification    = 'Emergency maintenance window for AAD Connect reconfiguration'
    ScheduleInfo     = @{
        StartDateTime = (Get-Date).ToUniversalTime().ToString('o')
        Expiration    = @{
            Type     = 'AfterDuration'
            Duration = 'PT2H'  # 2 hours
        }
    }
}

Auditing JIT Activations


# Audit all JIT group membership changes on-premises
# Event ID 4728 = member added to global security group
# Event ID 4756 = member added to universal security group

Get-WinEvent -ComputerName $pdc -FilterHashtable @{
    LogName   = 'Security'
    Id        = @(4728, 4756)
    StartTime = (Get-Date).AddDays(-7)
} | Where-Object {
    $_.Properties[2].Value -like 'JIT-*'
} | Select-Object TimeCreated, 
    @{N='AddedUser';E={$_.Properties[0].Value}},
    @{N='Group';E={$_.Properties[2].Value}},
    @{N='AddedBy';E={$_.Properties[4].Value}} |
    Format-Table -AutoSize

# Export JIT request log to CSV for compliance
Get-ChildItem '\MGMT01JITRequests*.json' | ForEach-Object {
    Get-Content $_.FullName | ConvertFrom-Json
} | Export-Csv 'C:ReportsJIT_Audit.csv' -NoTypeInformation

Conclusion

Just-In-Time Administration on Windows Server 2019 eliminates standing privileges that attackers can exploit during the 99% of time when they are not needed. Using AD’s native time-based group membership with PAM feature enabled, combined with a PowerShell request-approval workflow and comprehensive audit logging, organizations can enforce the principle that no one holds elevated access indefinitely. For hybrid environments, Azure AD PIM extends this model to cloud roles and provides a polished self-service activation portal with full audit trails.