PowerShell JEA (Just Enough Administration): How to Give Help Desk PowerShell Access Without Making Them Domain Admins

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Shared domain admin credentials for help desk work are a security failure that organizations accept because the alternative -- writing custom delegation for every administrative task -- seems prohibitively complex. JEA is the answer. It takes about an hour to build a constrained PowerShell endpoint for a specific role (password reset, service management, user account unlock), and the result is logged, auditable, least-privilege administration. The help desk engineer never sees admin credentials, never has admin rights, and every command they run is in a transcript file.
Create the Role Capability File
The role capability file (.psrc) defines what a user can do in the JEA session.
# Create the role capability file directory structure
# JEA looks for role capabilities in: <module>\RoleCapabilities\
$modulePath = 'C:\Program Files\WindowsPowerShell\Modules\JEA-HelpDesk'
New-Item -ItemType Directory -Path "$modulePath\RoleCapabilities" -Force
# Create the role capability file
New-PSRoleCapabilityFile -Path "$modulePath\RoleCapabilities\HelpDesk.psrc"
Edit the generated .psrc file to define the allowed capabilities:
# HelpDesk.psrc -- allowed operations for tier 1 help desk
@{
# PowerShell edition
SchemaVersion = '2.0.0.0'
GUID = '<generate-with-New-Guid>'
Author = 'Security Team'
Description = 'Help desk tier 1: user unlock, password reset, service restart'
# Allow specific commands (Cmdlet name + allowed parameters)
VisibleCmdlets = @(
# Unlock AD accounts
@{ Name = 'Unlock-ADAccount'; Parameters = @{ Name = 'Identity' } },
# Reset AD passwords (force change at next logon)
@{ Name = 'Set-ADAccountPassword'; Parameters = @{ Name = 'Identity'; Name = 'Reset'; Name = 'NewPassword' } },
# Get user info (read-only)
'Get-ADUser',
'Get-ADGroupMember',
# Restart specific services
@{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = @('Spooler', 'W32Time', 'BITS') } },
@{ Name = 'Get-Service'; Parameters = @{ Name = 'Name' } },
# Clear print queue
@{ Name = 'Clear-PrintJobList'; Parameters = @{ Name = 'PrinterName' } },
'Get-Printer'
)
# Allow these modules (imports their exported commands, filtered by VisibleCmdlets)
ModulesToImport = @('ActiveDirectory', 'PrintManagement')
# Visible functions (you can define custom functions here)
VisibleFunctions = @('Get-HelpDeskTicketStatus')
# Visible aliases (tab completion and shorthand)
VisibleAliases = @()
# Visible providers (filesystem paths visible in session)
# Leave empty to restrict all drive access
VisibleProviders = @()
}
Create the Session Configuration File and Register the Endpoint
The session configuration file (.pssc) ties roles to AD groups and sets the virtual account.
# Create the session configuration file
New-PSSessionConfigurationFile `
-Path 'C:\JEA\HelpDeskEndpoint.pssc' `
-SessionType RestrictedRemoteServer `
-RunAsVirtualAccount `
-RoleDefinitions @{
'DOMAIN\HelpDesk-Tier1' = @{ RoleCapabilities = 'HelpDesk' }
'DOMAIN\ServerAdmins' = @{ RoleCapabilities = 'HelpDesk', 'ServerManagement' }
} `
-TranscriptDirectory 'C:\JEA\Transcripts' `
-LanguageMode NoLanguage
# NoLanguage: blocks arbitrary PowerShell code; only pre-defined commands allowed
# Use ConstrainedLanguage for slightly more flexibility (allows some PowerShell features)
# Register the JEA endpoint on the target machine
Register-PSSessionConfiguration `
-Name 'JEA-HelpDesk' `
-Path 'C:\JEA\HelpDeskEndpoint.pssc' `
-Force
# Verify registration
Get-PSSessionConfiguration -Name 'JEA-HelpDesk' | Format-List
Set transcript directory permissions:
# Transcripts should be writable by the virtual account, not the connecting user
# Virtual accounts write as NT AUTHORITY\SYSTEM or the machine account
$acl = Get-Acl 'C:\JEA\Transcripts'
# Deny the JEA endpoint users the ability to delete or modify transcript files
# Grant only the security team / SIEM collector read access
For server fleet deployment, use DSC or Group Policy to push the module and session configuration:
# Copy the module to all target servers
$servers = @('server01', 'server02', 'fileserver01')
foreach ($server in $servers) {
Copy-Item -Path 'C:\Program Files\WindowsPowerShell\Modules\JEA-HelpDesk' `
-Destination "\\$server\C$\Program Files\WindowsPowerShell\Modules\JEA-HelpDesk" `
-Recurse -Force
Invoke-Command -ComputerName $server -ScriptBlock {
Register-PSSessionConfiguration -Name 'JEA-HelpDesk' -Path 'C:\JEA\HelpDeskEndpoint.pssc' -Force
}
}
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Connect to a JEA Endpoint and Test
Connect as a help desk user:
# Interactive session
Enter-PSSession -ComputerName 'server01' -ConfigurationName 'JEA-HelpDesk'
# Prompt shows: [server01]: PS>
# User is now in the JEA constrained session
# Try an allowed command
Unlock-ADAccount -Identity 'jsmith'
# Should succeed
# Try a non-allowed command
Get-ADDomain
# Error: The term 'Get-ADDomain' is not recognized as the name of a cmdlet...
# Try to break out of the constrained session
[System.Reflection.Assembly]::LoadWithPartialName('System')
# Error: You cannot call methods on null-valued expressions (NoLanguage mode blocks this)
# Exit the session
Exit-PSSession
Invoke a specific command without an interactive session:
# Run a single JEA command from a script or ticketing system integration
Invoke-Command -ComputerName 'server01' -ConfigurationName 'JEA-HelpDesk' -ScriptBlock {
Unlock-ADAccount -Identity $using:username
}
Verify what a connecting user can see in the JEA session:
# From within a JEA session:
Get-Command
# Returns only the allowed commands for the connecting user's role
Get-PSSessionCapability -ConfigurationName 'JEA-HelpDesk' -Username 'DOMAIN\helpdesk1'
# From outside: shows what a specific user will be able to do
Common JEA Use Cases and Role Capability Templates
Help desk account management:
# Allow: unlock account, reset password, read user info
VisibleCmdlets = @(
@{ Name = 'Unlock-ADAccount'; Parameters = @{ Name = 'Identity' } },
@{ Name = 'Set-ADAccountPassword'; Parameters = @{ Name = 'Identity'; Name = 'Reset'; Name = 'NewPassword' } },
'Get-ADUser',
@{ Name = 'Enable-ADAccount'; Parameters = @{ Name = 'Identity' } },
@{ Name = 'Disable-ADAccount'; Parameters = @{ Name = 'Identity' } }
)
Service management (on-call team):
# Allow restarting a specific list of services
VisibleCmdlets = @(
@{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = @('AppPool1', 'W3SVC', 'SQLServerAgent') } },
@{ Name = 'Start-Service'; Parameters = @{ Name = 'Name'; ValidateSet = @('AppPool1', 'W3SVC') } },
@{ Name = 'Stop-Service'; Parameters = @{ Name = 'Name'; ValidateSet = @('AppPool1', 'W3SVC') } },
'Get-Service', 'Get-EventLog'
)
DNS management (network team, not domain admins):
ModulesToImport = @('DnsServer')
VisibleCmdlets = @(
'Get-DnsServerZone', 'Get-DnsServerResourceRecord',
@{ Name = 'Add-DnsServerResourceRecord'; Parameters = @{ Name = 'ZoneName'; Name = 'A'; Name = 'Name'; Name = 'IPv4Address' } },
@{ Name = 'Remove-DnsServerResourceRecord'; Parameters = @{ Name = 'ZoneName'; Name = 'Name'; Name = 'RRType'; Name = 'Force' } }
)
Review transcripts for audit:
# Find all transcripts from a specific user
Get-ChildItem 'C:\JEA\Transcripts' | Where-Object {
(Get-Content $_.FullName -First 20) -match 'DOMAIN\\helpdesk1'
}
# Parse a transcript for specific commands
Select-String -Path 'C:\JEA\Transcripts\*.txt' -Pattern 'Set-ADAccountPassword'
The bottom line
JEA deployment order: identify the three or four most common help desk and tier 1 admin tasks that currently require shared admin credentials, write a role capability file for each, register the JEA endpoint on the target server fleet, and test with a non-admin account. The transcript directory is your audit log -- protect it from modification by the connecting users and ship it to your SIEM. NoLanguage mode is the safest setting for help desk endpoints; consider ConstrainedLanguage only for more complex automation scenarios.
Frequently asked questions
Can JEA be used to prevent users from calling .NET classes directly?
Yes. The NoLanguage session mode (the safest setting) completely prevents users from executing arbitrary PowerShell code including .NET class calls, variable assignments, loops, and conditionals. They can only run the specific cmdlets defined in the role capability file. ConstrainedLanguage mode allows some scripting constructs (variables, loops) but blocks type access and .NET reflection. For help desk delegations, NoLanguage is almost always the right choice. For automation scripts that need more PowerShell constructs, ConstrainedLanguage provides a middle ground, but NoLanguage is preferred.
What is the virtual account in JEA and what privileges does it have?
A virtual account is a temporary local account created by JEA when a user connects, used to impersonate an administrator without the connecting user having those rights. For local operations (service management, file access on the target machine), the virtual account is a member of the local Administrators group. For Active Directory operations, the virtual account uses the machine account -- which only has standard domain read rights. If your JEA endpoint needs to write to AD (reset passwords, unlock accounts), you must configure JEA to run as a Group Managed Service Account (gMSA) instead of a virtual account and grant that gMSA the specific AD permissions needed.
Can I create a JEA endpoint that allows Active Directory password resets?
Yes, but it requires a gMSA rather than a virtual account. Virtual accounts have machine-account-level AD rights which are not sufficient to reset user passwords. Configure the session with RunAsVirtualAccountGroups for local operations, or use RunAsManagedServiceAccount with a gMSA that has been granted the Reset Password extended right on the relevant OU. Grant the gMSA only the specific delegated permissions needed (Reset Password, Write pwdLastSet for force-change-at-next-logon) -- not Domain Admin rights.
How do I keep JEA configurations consistent across a large server fleet?
Three approaches work at scale: PowerShell DSC (Desired State Configuration) with a JEA DSC resource to enforce the configuration, a startup script deployed via Group Policy that checks for and re-registers the JEA endpoint if missing, or packaging the role capability module as a PowerShell module distributed via an internal PowerShell repository (PSGallery-style feed). For most environments, copying the module directory to each server and registering the endpoint as part of your server build baseline is simplest. Treat JEA endpoints as infrastructure code -- version control the .psrc and .pssc files, and deploy changes through a change management process.
How do I audit what commands a JEA user actually executed during a session?
Enable JEA session transcription by setting TranscriptDirectory in the session configuration file (.pssc). Each JEA session generates a transcript recording every command typed and its output. Store transcripts in a central, write-protected network location rather than locally (to prevent tampering by users who have local admin). Additionally, enable PowerShell Script Block Logging via Group Policy -- this captures every script block executed in the JEA session in the PowerShell Operational event log (Event ID 4104), providing a second source of record that is harder to tamper with than local transcript files. Forward these event logs to your SIEM for centralized audit retention.
What are the most common JEA deployment mistakes that reduce its security value?
The most consequential JEA mistakes: overly broad role capability files that grant visibility commands (Get-ADUser, Get-Process, Get-ItemProperty) across the entire domain rather than scoping to specific OUs or systems, creating a single 'IT admin' JEA role that grants more capability than any individual team member actually needs rather than defining per-team roles (helpdesk, server admin, network admin), and failing to use RunAsVirtualAccount or RunAsGroupManagedServiceAccount -- without a run-as account, the session runs in the connecting user's context and JEA provides no privilege separation. A JEA endpoint that grants a helpdesk account the ability to run Restart-Service on any service (including security services) on any server in the domain has weaker isolation than a simple OU-scoped delegation. Scope capabilities to the specific servers and actions each role genuinely needs.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
