How to Configure Just Enough Administration (JEA) on Windows Server 2025
Just Enough Administration (JEA) is a PowerShell-based security feature that implements the principle of least privilege for administrative tasks. Instead of granting users full administrator rights on a server, JEA creates a constrained PowerShell remoting endpoint that limits each user to exactly the commands, scripts, and external tools they need for their specific role. When a user connects to a JEA endpoint, they run within a highly restricted session — typically as a virtual account with temporary local administrator privileges that evaporate when the session ends, or as a Group Managed Service Account (gMSA). JEA is built into Windows Server 2025 and requires no additional software, making it one of the most practical tools available for reducing administrative attack surface in your environment.
Prerequisites
- Windows Server 2025 target server (the one being administered via JEA)
- PowerShell 5.1 or later (included in Windows Server 2025)
- WinRM enabled on the target server
- Administrator account on the target server for initial configuration
- Active Directory domain (recommended) for Group Managed Service Accounts and group-based role assignment
- A clear definition of which PowerShell cmdlets each administrative role requires
Step 1: Plan Your JEA Roles
Before writing any configuration files, define which administrative roles you need and what capabilities each role requires. JEA works best when roles map to specific job functions rather than to individuals. A common pattern for a Windows Server environment:
- DNSOperator — Can manage DNS records, restart the DNS service, view DNS logs
- DiskOperator — Can view disk/volume information and run Disk Cleanup; cannot format drives
- EventLogReader — Can read event logs, export logs; cannot clear them
- ServiceOperator — Can start/stop/restart specific named services; cannot install services
# Create the JEA configuration directory structure
$jeaRoot = "C:JEA"
$roleCapDir = "$jeaRootRoleCapabilities"
$transcriptDir = "$jeaRootTranscripts"
New-Item -Path $jeaRoot -ItemType Directory -Force
New-Item -Path $roleCapDir -ItemType Directory -Force
New-Item -Path $transcriptDir -ItemType Directory -Force
# Set transcript directory permissions so only SYSTEM and Admins can read
$acl = Get-Acl $transcriptDir
$acl.SetAccessRuleProtection($true, $false)
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"BUILTINAdministrators", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"NT AUTHORITYSYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$acl.AddAccessRule($adminRule)
$acl.AddAccessRule($systemRule)
Set-Acl -Path $transcriptDir -AclObject $acl
Write-Host "JEA directory structure created at $jeaRoot"
Step 2: Create Role Capability Files (.psrc)
A Role Capability File (extension .psrc) defines what a user in a given role can do within a JEA session. It specifies visible cmdlets, functions, external commands (executables), and PowerShell providers. The file must be placed in a RoleCapabilities folder inside a PowerShell module directory. Use New-PSRoleCapabilityFile to generate the file with the correct structure:
# Create a PowerShell module to host the Role Capability Files
$moduleName = "JEARoles"
$moduleDir = "$env:ProgramFilesWindowsPowerShellModules$moduleName"
$moduleRoleCapDir = "$moduleDirRoleCapabilities"
New-Item -Path $moduleRoleCapDir -ItemType Directory -Force
# Create a minimal module manifest
New-ModuleManifest -Path "$moduleDir$moduleName.psd1" `
-RootModule "" `
-ModuleVersion "1.0.0" `
-Description "JEA Role Capability definitions for this server"
# --- DNS Operator Role Capability ---
New-PSRoleCapabilityFile -Path "$moduleRoleCapDirDNSOperator.psrc" `
-Description "Allows operators to manage DNS zones and records" `
-VisibleCmdlets @(
"Get-DnsServerZone",
"Get-DnsServerResourceRecord",
@{ Name = "Add-DnsServerResourceRecord"; Parameters = @{ Name = "ZoneName"; ValidateSet = "contoso.com","internal.contoso.com" } },
@{ Name = "Remove-DnsServerResourceRecord"; Parameters = @{ Name = "ZoneName"; ValidateSet = "contoso.com","internal.contoso.com" } },
"Get-EventLog",
"Get-WinEvent"
) `
-VisibleFunctions @("Restart-DnsService") `
-VisibleExternalCommands @("C:WindowsSystem32ipconfig.exe", "C:WindowsSystem32nslookup.exe") `
-VisibleProviders @("FileSystem") `
-FunctionDefinitions @(
@{
Name = "Restart-DnsService"
ScriptBlock = { Restart-Service -Name DNS -Confirm:$false }
}
)
Write-Host "DNSOperator role capability created."
# --- Service Operator Role Capability ---
New-PSRoleCapabilityFile -Path "$moduleRoleCapDirServiceOperator.psrc" `
-Description "Allows operators to start and stop specific services only" `
-VisibleCmdlets @(
"Get-Service",
@{
Name = "Start-Service"
Parameters = @{ Name = "Name"; ValidateSet = "W3SVC","WinRM","DNS","MSSQLSERVER" }
},
@{
Name = "Stop-Service"
Parameters = @{ Name = "Name"; ValidateSet = "W3SVC","WinRM","DNS","MSSQLSERVER" }
},
@{
Name = "Restart-Service"
Parameters = @{ Name = "Name"; ValidateSet = "W3SVC","WinRM","DNS","MSSQLSERVER" }
},
"Get-EventLog"
) `
-VisibleProviders @()
Write-Host "ServiceOperator role capability created."
# Verify both .psrc files
Get-ChildItem $moduleRoleCapDir -Filter "*.psrc" | Select-Object Name, Length
Step 3: Create the Session Configuration File (.pssc)
The Session Configuration File (extension .pssc) defines the JEA endpoint itself — how sessions run, where transcripts are stored, which identity the session runs as, and which roles are mapped to which Active Directory groups. This is the file you register with Register-PSSessionConfiguration:
# Create the JEA Session Configuration file
New-PSSessionConfigurationFile -Path "$jeaRootJEA-Endpoint.pssc" `
-SessionType RestrictedRemoteServer `
-TranscriptDirectory "C:JEATranscripts" `
-RunAsVirtualAccount `
-RoleDefinitions @{
"CONTOSODNS-Operators" = @{ RoleCapabilities = "DNSOperator" }
"CONTOSOService-Operators" = @{ RoleCapabilities = "ServiceOperator" }
"CONTOSOServer-Admins" = @{ RoleCapabilities = "DNSOperator","ServiceOperator" }
} `
-LanguageMode NoLanguage `
-ExecutionPolicy RemoteSigned `
-Description "JEA endpoint for server operator roles - WS2025"
# Validate the configuration file before registering
Test-PSSessionConfigurationFile -Path "$jeaRootJEA-Endpoint.pssc"
Key options explained:
SessionType RestrictedRemoteServer— Removes access to implicit remoting, import-module, and other features that could be used for escapeRunAsVirtualAccount— Commands run as a temporary local administrator; credentials are not stored and the account is deleted when the session endsTranscriptDirectory— All session commands and output are recorded to this directory automaticallyLanguageMode NoLanguage— Disables the PowerShell scripting language; users can only call commands from their allowed list, not write arbitrary scripts
Step 4: Register the JEA Endpoint
Registering the session configuration creates the WinRM endpoint and makes it available for connections. You can register multiple endpoints on the same server for different purposes (one per application, per environment tier, etc.):
# Register the JEA endpoint
Register-PSSessionConfiguration `
-Name "JEA-ServerOps" `
-Path "$jeaRootJEA-Endpoint.pssc" `
-Force
# Verify the endpoint is registered
Get-PSSessionConfiguration -Name "JEA-ServerOps" | Format-List
# List all registered session configurations
Get-PSSessionConfiguration | Select-Object Name, PSVersion, RunAsUser, Permission |
Format-Table -AutoSize
# Check the WinRM configuration for the new endpoint
winrm get winrm/config/plugin?action=Query`&Name=JEA-ServerOps 2>$null
# Confirm the endpoint is accessible
Test-WSMan -ComputerName localhost -Authentication Default
Step 5: Connect to the JEA Endpoint
Users connect to a JEA endpoint using standard PowerShell remoting cmdlets, but they specify the -ConfigurationName parameter to target the JEA session instead of the default endpoint. Within the session, they can only run the commands defined in their role capability file:
# Connect to the JEA endpoint interactively
Enter-PSSession -ComputerName "WS2025-SRV01" -ConfigurationName "JEA-ServerOps"
# Once connected, verify available commands
Get-Command
# Attempt a permitted action (DNS operator example)
Get-DnsServerZone
# Attempt a denied action (should fail with access denied)
# Get-Process ← This will be blocked in NoLanguage mode
# Exit the session
Exit-PSSession
# Create a persistent session for scripted automation
$jeaSession = New-PSSession `
-ComputerName "WS2025-SRV01" `
-ConfigurationName "JEA-ServerOps" `
-Credential (Get-Credential "CONTOSOdns-operator1")
# Run commands through the JEA session
Invoke-Command -Session $jeaSession -ScriptBlock { Get-DnsServerZone }
# Pass parameters safely — parameterized values only
Invoke-Command -Session $jeaSession -ScriptBlock {
param($zone)
Get-DnsServerResourceRecord -ZoneName $zone
} -ArgumentList "contoso.com"
# Clean up the session
Remove-PSSession $jeaSession
Step 6: Use a Group Managed Service Account Instead of a Virtual Account
For scenarios where the virtual account’s temporary identity causes issues (e.g., accessing network resources, database connections), you can configure JEA to run under a Group Managed Service Account (gMSA). The gMSA has a stable identity and password managed by Active Directory:
# Create a gMSA in Active Directory (run on a domain controller)
# First, ensure the KDS Root Key exists (required for gMSA)
Add-KdsRootKey -EffectiveTime (Get-Date).AddHours(-10)
# Create the gMSA
New-ADServiceAccount `
-Name "JEA-SrvOps" `
-DNSHostName "jea-srvops.contoso.com" `
-PrincipalsAllowedToRetrieveManagedPassword "WS2025-SRV01$" `
-Description "gMSA for JEA Server Operations endpoint"
# Install the gMSA on the target server (run on WS2025-SRV01)
Install-ADServiceAccount -Identity "JEA-SrvOps"
Test-ADServiceAccount -Identity "JEA-SrvOps"
# Recreate the .pssc file using the gMSA instead of virtual account
New-PSSessionConfigurationFile -Path "$jeaRootJEA-Endpoint-gMSA.pssc" `
-SessionType RestrictedRemoteServer `
-TranscriptDirectory "C:JEATranscripts" `
-GroupManagedServiceAccount "CONTOSOJEA-SrvOps$" `
-RoleDefinitions @{
"CONTOSODNS-Operators" = @{ RoleCapabilities = "DNSOperator" }
} `
-LanguageMode ConstrainedLanguage `
-ExecutionPolicy RemoteSigned
# Register the gMSA-based endpoint
Register-PSSessionConfiguration `
-Name "JEA-ServerOps-gMSA" `
-Path "$jeaRootJEA-Endpoint-gMSA.pssc" `
-Force
Step 7: Audit JEA Sessions from Transcripts
Every JEA session is automatically recorded to the transcript directory you specified. These transcripts contain the full command history, output, and session metadata — invaluable for forensic investigation and compliance reporting:
# List recent JEA transcripts
Get-ChildItem -Path "C:JEATranscripts" -Filter "*.txt" |
Sort-Object LastWriteTime -Descending |
Select-Object Name, Length, LastWriteTime -First 20
# Parse a transcript to extract commands run
$transcriptFile = Get-ChildItem "C:JEATranscripts" -Filter "*.txt" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
$content = Get-Content $transcriptFile.FullName
# Extract the connected user identity from the transcript header
$content | Select-String -Pattern "Principal:" | Select-Object -First 1
# Extract all commands issued in the session
$content | Select-String -Pattern "PS>" | ForEach-Object {
$_.Line.Trim()
} | Where-Object { $_ -ne "PS>" }
# Build a simple audit report from all transcripts
Get-ChildItem "C:JEATranscripts" -Filter "*.txt" | ForEach-Object {
$lines = Get-Content $_.FullName
$user = ($lines | Select-String "Principal:" | Select-Object -First 1).Line
$cmds = ($lines | Select-String "PS>").Count
[PSCustomObject]@{
Transcript = $_.Name
User = $user -replace ".*Principal:s*",""
Commands = $cmds
Date = $_.LastWriteTime
}
} | Format-Table -AutoSize
Conclusion
Just Enough Administration on Windows Server 2025 delivers a practical, scalable implementation of least-privilege administration without requiring expensive third-party Privileged Access Management software. By carefully designing Role Capability Files that expose only the necessary cmdlets with constrained parameter values, creating Session Configuration Files that force sessions into restricted language modes with mandatory transcript logging, and backing sessions with virtual accounts or Group Managed Service Accounts, you eliminate the need for most administrative users to ever hold permanent local administrator rights. Combined with the built-in transcript audit trail and integration with Windows Event logging (Event ID 4688 captures JEA subprocess launches), JEA gives you both the operational security benefits of least privilege and the forensic visibility required for incident response and compliance audits.