Just Enough Administration (JEA) step by step
Build JEA endpoints that give operators exactly the commands they need: role capability files, session configurations, virtual accounts, gMSAs and auditing.
The PowerShell security pillar showed a five-line JEA example. That is enough to see the idea, but not enough to run it in production. This guide goes further: how to design roles that can't be escaped, how to choose the run-as identity, how to deploy endpoints to many servers and how to prove they do what you think.
Just Enough Administration is a PowerShell remoting endpoint with three properties. The connecting user can only run an allowlisted set of commands, the commands run under a temporary privileged identity rather than the user's own account, and everything is transcribed. Done well, it lets a helpdesk restart a service or clear a print queue without anyone holding local admin rights on the server. See the JEA glossary entry for the short definition.
How the pieces fit
| File | Extension | Defines | Where it lives |
|---|---|---|---|
| Role capability | .psrc | What a role can do: visible cmdlets, functions, external commands, providers | RoleCapabilities folder inside a module on $env:PSModulePath |
| Session configuration | .pssc | Who gets which role, the run-as identity, transcripts, language mode | Any path; registered with Register-PSSessionConfiguration |
Role capabilities are loaded when a session starts, so editing a .psrc affects new sessions immediately. Changes to a .pssc only apply after you register it again.
What breaks
JEA sessions are deliberately uncomfortable. Plan for:
- No variables or script blocks.
RestrictedRemoteServersessions default toNoLanguagemode. Users run commands; they can't write$x = Get-ServiceorForEach-Object { }. Put logic in functions inside your module. - No double hop. A virtual account is local to the server. Anything that must reach a file share, SQL or AD from inside the session needs a gMSA instead.
- No file copy by default.
Copy-Item -ToSessiononly works with a user drive (MountUserDrive), and only into that drive. - GUI tools don't use it. Server Manager, MMC snap-ins and RDP ignore JEA endpoints. Operators need scripts or implicit remoting.
Step 1: inventory the tasks
Start from tickets, not from cmdlets. For each recurring task, write down the exact command and the parameter values it needs:
- Restart the print spooler on print servers.
- Clear stuck jobs from a named queue.
- Read the last 50 System events.
If a task needs arbitrary input (any service, any path), that is a warning sign. Every free-form parameter is a potential escape route.
Step 2: build the role capability module
Create a small module to hold the role. The module needs a manifest, even if the .psm1 is empty:
$mod = "C:\Program Files\WindowsPowerShell\Modules\CorpJEA"
New-Item -ItemType Directory -Path "$mod\RoleCapabilities" -Force | Out-Null
New-Item -ItemType File -Path "$mod\CorpJEA.psm1" -Force | Out-Null
New-ModuleManifest -Path "$mod\CorpJEA.psd1" -RootModule 'CorpJEA.psm1'Now the role. Constrain parameters with ValidateSet or ValidatePattern wherever you can:
New-PSRoleCapabilityFile -Path "$mod\RoleCapabilities\PrintOperator.psrc" `
-VisibleCmdlets @(
@{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'Spooler' } },
@{ Name = 'Get-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'Spooler' } },
@{ Name = 'Get-PrintJob'; Parameters = @{ Name = 'PrinterName'; ValidatePattern = '^PRN-[A-Z0-9-]+$' } },
@{ Name = 'Remove-PrintJob'; Parameters = @(
@{ Name = 'PrinterName'; ValidatePattern = '^PRN-[A-Z0-9-]+$' },
@{ Name = 'ID' }) }
) `
-VisibleFunctions 'Get-CorpSystemEvents'Get-CorpSystemEvents is a function you write in CorpJEA.psm1, for example a wrapper around Get-WinEvent -LogName System -MaxEvents 50. Functions in your module are the right place for anything that needs variables or loops: users can call them, but can't see or change their bodies.
Leave these out of every role unless you understand exactly why they are there, because each one turns the endpoint back into full admin:
Invoke-Expression,Invoke-Command,Start-Process,New-Service,Register-ScheduledTask,Add-Type.- Anything that changes group membership (
Add-LocalGroupMember,Add-ADGroupMember). - File and registry providers via
VisibleProviders, andSet-Content,Copy-ItemorNew-Itemwithout path constraints. VisibleExternalCommandspointing tocmd.exe,powershell.exeor any interpreter.
A cmdlet with an unconstrained -ScriptBlock, -FilePath or -ComputerName parameter deserves the same suspicion.
Step 3: write the session configuration
New-PSSessionConfigurationFile -Path .\PrintOps.pssc `
-SessionType RestrictedRemoteServer `
-RunAsVirtualAccount `
-RunAsVirtualAccountGroups 'JEA-PrintSvc' `
-TranscriptDirectory 'C:\ProgramData\JEA\Transcripts' `
-RoleDefinitions @{ 'CORP\JEA-PrintOps' = @{ RoleCapabilities = 'PrintOperator' } } `
-RequiredGroups @{ And = 'CORP\JEA-PrintOps', 'CORP\SmartCardUsers' }
Test-PSSessionConfigurationFile -Path .\PrintOps.psscJEA-PrintSvc is a local group on the server that you grant only the rights the role needs, for example start and stop rights on the Spooler service (through the service's security descriptor) and Manage documents on the print queues. Building that group takes more effort than accepting local admin, and it is the effort that makes the endpoint safe.
The run-as identity is the most important decision here:
| Option | Parameter | Rights | Use when |
|---|---|---|---|
| Virtual account, default | -RunAsVirtualAccount | Member of local Administrators; on a domain controller, member of Domain Admins | Almost never on DCs; acceptable on member servers only if the role is tightly constrained |
| Virtual account, scoped | -RunAsVirtualAccount -RunAsVirtualAccountGroups | Only the listed local groups, not Administrators | The default choice for member servers |
| Group managed service account | -GroupManagedServiceAccount 'CORP\gmsa-jea-print$' | Whatever you grant the gMSA, including network resources | The session must reach other machines |
The Domain Admins default on domain controllers catches people out. A JEA endpoint on a DC with an unscoped virtual account and one escapable command is a Domain Admin shell for whoever can connect. Always scope it, or use a gMSA with specific delegated rights.
RequiredGroups adds a condition that must be true regardless of role, for example that the user also logged on with a smart card (through an authentication mechanism assurance group). It is evaluated at connection time.
Step 4: register the endpoint
Register-PSSessionConfiguration -Name PrintOps -Path .\PrintOps.pssc -ForceRegistration restarts the WinRM service, which drops existing remote sessions. Do it in a maintenance window on busy servers.
Then check the default endpoints. Get-PSSessionConfiguration lists every endpoint and who may use it:
Get-PSSessionConfiguration | Select-Object Name, PermissionThe built-in microsoft.powershell endpoints allow Administrators and Remote Management Users. JEA users must be in neither group, or they can skip your endpoint entirely. Clean up group membership as part of removing local admin rights.
Step 5: deploy at scale
Hand-registering endpoints doesn't scale past a few servers. Two common patterns:
- Software distribution plus a script. Package the module folder and the
.pssc, copy the module toC:\Program Files\WindowsPowerShell\Modules, then runRegister-PSSessionConfiguration. Use the module version in the manifest as your detection rule. - Desired State Configuration. The community
JeaDscresource module declares endpoints and role capabilities as configuration, which also corrects drift. Check the module's current maintenance status before relying on it.
Keep the .psrc and .pssc files in source control with code review. They are privileged configuration, just like a GPO that grants admin rights.
Verify it
First, ask the server what a given user would get, without connecting:
Get-PSSessionCapability -ConfigurationName PrintOps -Username 'CORP\alice'The output lists the exact commands available to that user. Anything unexpected there is a design error.
Then connect as a test operator:
Enter-PSSession -ComputerName prn01.corp.example -ConfigurationName PrintOps
Get-Command # only the allowed commands, plus the defaults
Restart-Service -Name Spooler # works
Restart-Service -Name WinRM # fails parameter validation
$env:USERNAME # fails: no languageCheck the transcript in C:\ProgramData\JEA\Transcripts. The header shows both the connected user and the run-as account, which is what makes JEA auditable. Forward those transcripts, and the PowerShell 4104 events generated inside the session, as described in detecting malicious PowerShell.
Finally, try to break out on purpose. Run each visible command with odd arguments, pipe outputs into other visible commands, and look for anything that writes files, starts processes or changes groups. A short internal review by someone who didn't write the role finds most escapes.
Operating JEA
- Protect the endpoint transport. JEA runs over WinRM, so it inherits WinRM's configuration. Apply WinRM hardening: Kerberos, no Basic, no CredSSP, and firewall scoping to management hosts.
- Map roles to tiers. A JEA endpoint on a Tier 0 server is a Tier 0 asset, and the group that grants the role is a Tier 0 group. Follow your admin tiering model.
- Review quarterly. Compare role capabilities with actual usage in transcripts. Remove commands nobody runs.
- Alert on endpoint changes. A new or modified session configuration shows up as a change under
WSMan:\localhost\Plugin; monitor that and the module folder with your file integrity or configuration management tooling.
More background on JEA and the other PowerShell controls lives on the PowerShell security topic page.
Checklist
- Recurring operational tasks listed with exact commands and parameter values
- Role capability module with constrained parameters; no interpreters, process start, group changes or unconstrained file access
- Session configuration uses
RestrictedRemoteServerand a scoped virtual account or gMSA - No unscoped virtual account on any domain controller
-
RequiredGroupsused for sensitive roles (for example smart card or MFA assurance) - JEA users removed from Administrators and Remote Management Users
-
Get-PSSessionCapabilityoutput reviewed for every role - Transcripts and 4104 events forwarded and retained
- Role and session files in source control and deployed automatically
Frequently asked questions
Do JEA users need to be local administrators?
No, and they should not be. The point of JEA is that the connecting user has no rights on the server; the session runs as a virtual account or gMSA that holds the rights. If operators are also local administrators or members of Remote Management Users, they can bypass JEA by connecting to the default, unrestricted endpoint.
Can I use JEA with PowerShell 7?
Windows PowerShell 5.1 is the reference host for JEA and is present on every supported Windows version. PowerShell 7 can also host JEA endpoints on Windows once its own remoting endpoints are enabled. Behaviour and supported modules can differ between the two hosts, so check the current Microsoft docs and test the exact endpoint you plan to deploy.
Is JEA a replacement for a privileged access management product?
Not entirely. JEA restricts what a remote PowerShell session can do and records it, which covers many repetitive operational tasks. It does not vault credentials, approve requests or record GUI sessions. Many teams combine JEA for scripted tasks with admin tiering and a PAM or just-in-time process for the remaining full-admin work.
Just Enough Administration (JEA) step by step