How to Configure Just Enough Administration (JEA) and Constrained PowerShell Remoting Endpoints in Windows 11

Granting helpdesk technicians, developers, or junior system operators broad Local Administrator rights on Windows 11 endpoints introduces severe cybersecurity vulnerabilities. If an operator’s credentials are compromised via phishing or pass-the-hash attacks, adversaries instantly inherit unfettered access across the system. Traditional role-based access control (RBAC) in Windows frequently forces an all-or-nothing dichotomy: users are either standard unprivileged users or members of the built-in Administrators group.

Just Enough Administration (JEA) resolves this operational risk by establishing constrained, role-based PowerShell Remoting endpoints over WinRM. With JEA, operators connect to a hardened PowerShell session where they can execute only specific, pre-approved cmdlets and parameters. Behind the scenes, JEA executes these whitelisted commands under a temporary, disposable Virtual Account with elevated administrator privileges, entirely removing the need to hand out administrative passwords. This guide details how to architect, configure, and audit JEA endpoints on Windows 11.


Architectural Mechanics: How JEA Enforces Least Privilege

JEA functions at the session configuration layer of Windows Remote Management (WinRM). When a remote user connects to a designated JEA endpoint, Windows negotiates the session through two declarative configuration files:

Configuration Component File Extension Architectural Role Typical Storage Path
Role Capability File .psrc Defines what an authorized user is permitted to do: visible cmdlets, functions, parameter filters, and external executables. $env:ProgramFiles\WindowsPowerShell\Modules\<ModuleName>\RoleCapabilities\
Session Configuration File .pssc Defines who can connect, maps groups to Role Capabilities, enables Virtual Accounts, and enforces transcript logging. $env:ProgramData\<CustomDirectory>\ or central repository.
Virtual Account In-Memory Identity A transient, machine-generated local administrator account created dynamically upon session creation and purged immediately upon disconnection. Dynamic Security Identifier (SID); zero persistent credential footprint.

By executing through Virtual Accounts, the operator never knows the local administrator password, nor can an adversary extract re-usable password hashes from the machine’s LSASS memory space.


Step 1: Establishing the PowerShell Module Directory Structure

PowerShell requires Role Capability files to reside inside a dedicated RoleCapabilities subdirectory within a valid PowerShell module directory:

# Execute in an elevated Windows PowerShell console on Windows 11:
$modulePath = "$env:ProgramFiles\WindowsPowerShell\Modules\SupportJEA"
$roleCapPath = "$modulePath\RoleCapabilities"

# Create required folder hierarchy
New-Item -ItemType Directory -Path $roleCapPath -Force

# Generate an empty module root manifest
New-ModuleManifest -Path "$modulePath\SupportJEA.psd1" `
                   -Author "Enterprise Systems Security" `
                   -Description "JEA constrained administrative modules for tier-1 support" `
                   -ModuleVersion "1.0.0"

Step 2: Creating the Role Capability File (.psrc)

The Role Capability file specifies the precise cmdlets, functions, and parameters available to operators. In this example, we permit technicians to restart specific services (such as Print Spooler and W32Time) and restart network adapters, but strictly forbid stopping security services or running arbitrary scripts.

# Generate the template Role Capability file
$rcFile = "$roleCapPath\HelpdeskOperator.psrc"
New-PSRoleCapabilityFile -Path $rcFile -Description "Allows service triage and network diagnostics"

Next, edit the generated $rcFile to declare explicit command whitelists and parameter constraints:

@{
    # Version and author metadata
    Author = 'Enterprise Security'
    CompanyName = 'Corporate IT'
    SchemaVersion = '2.0.0.0'

    # Expose only specific built-in cmdlets
    VisibleCmdlets = @(
        # Allow viewing service states universally
        'Get-Service',

        # Restrict Restart-Service exclusively to spooler and w32time
        @{
            Name = 'Restart-Service'
            Parameters = @{
                Name = 'Name'
                ValidateSet = @('Spooler', 'W32Time')
            }
        },

        # Allow basic network diagnostics
        'Get-NetIPAddress',
        'Test-NetConnection',
        'Restart-NetAdapter'
    )

    # Expose custom administrative wrapper functions
    VisibleFunctions = @(
        'Get-SystemUptime'
    )

    # Define custom internal functions injected into the session
    FunctionDefinitions = @(
        @{
            Name = 'Get-SystemUptime'
            ScriptBlock = {
                $bootTime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
                [PSCustomObject]@{
                    ComputerName = $env:COMPUTERNAME
                    LastBootTime = $bootTime
                    UptimeHours  = [math]::Round(((Get-Date) - $bootTime).TotalHours, 2)
                }
            }
        }
    )
}

Notice the ValidateSet directive applied to Restart-Service: if an operator attempts to run Restart-Service -Name WinDefend or Restart-Service -Name LanmanServer, PowerShell rejects the execution at the parser level before invoking the underlying binary.


Step 3: Creating and Registering the Session Configuration File (.pssc)

The Session Configuration links Windows user groups to the Role Capability and configures the Virtual Account.

# Define storage location for the session configuration file
$sessionDir = "$env:ProgramData\JEAConfig"
New-Item -ItemType Directory -Path $sessionDir -Force
$sessionFile = "$sessionDir\Tier1SupportEndpoint.pssc"

# Generate session configuration
New-PSSessionConfigurationFile -Path $sessionFile `
                               -SessionType RestrictedRemoteServer `
                               -RunAsVirtualAccount `
                               -TranscriptDirectory "$env:ProgramData\JEAConfig\Transcripts" `
                               -RoleDefinitions @{
                                   # Map local or domain group to the role capability
                                   'DOMAIN\HelpdeskTechnicians' = @{ RoleCapabilities = 'HelpdeskOperator' }
                                   'NT AUTHORITY\INTERACTIVE'   = @{ RoleCapabilities = 'HelpdeskOperator' }
                               }

Ensure the transcript directory has strict NTFS permissions so standard users cannot tamper with audit logs:

# Secure transcript folder
$transcriptPath = "$env:ProgramData\JEAConfig\Transcripts"
New-Item -ItemType Directory -Path $transcriptPath -Force

$acl = Get-Acl $transcriptPath
$acl.SetAccessRuleProtection($true, $false)
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl","ContainerInherit,ObjectInherit","None","Allow")
$acl.AddAccessRule($adminRule)
Set-Acl -Path $transcriptPath -AclObject $acl

Step 4: Registering and Validating the JEA Endpoint

To make the endpoint accessible via WinRM, register it using Register-PSSessionConfiguration:

# Register the endpoint under a descriptive name
Register-PSSessionConfiguration -Name 'HelpdeskMaintenance' `
                                -Path $sessionFile `
                                -Force

# Restart WinRM service to reload session configuration tables
Restart-Service WinRM

# Verify active registered endpoints
Get-PSSessionConfiguration | Select-Object Name, Permission, ResourceURI

Step 5: Testing the Constrained Session

Log in as a standard user belonging to the allowed security group and connect specifically to the named endpoint:

# Connect from a client machine to the JEA endpoint
Enter-PSSession -ComputerName "WIN11-WORKSTATION" -ConfigurationName "HelpdeskMaintenance"

# Check available commands within the constrained shell
[WIN11-WORKSTATION]: PS> Get-Command

# Expected result: Only Get-Service, Restart-Service, Get-NetIPAddress, Test-NetConnection, 
# Restart-NetAdapter, and Get-SystemUptime appear!

# Test permitted action (Restarting Spooler service)
[WIN11-WORKSTATION]: PS> Restart-Service -Name Spooler
# Success! Spooler restarts smoothly under the virtual administrator account.

# Test forbidden action (Restarting Windows Defender)
[WIN11-WORKSTATION]: PS> Restart-Service -Name WinDefend
# Fails with validation error: Cannot validate argument on parameter 'Name'.
# The argument "WinDefend" does not belong to the set "Spooler,W32Time".

# Test arbitrary command execution
[WIN11-WORKSTATION]: PS> whoami
# Fails with error: The term 'whoami' is not recognized as the name of a cmdlet.

Troubleshooting Common JEA Deployment Faults

Observed Issue Root Cause Diagnostic & Remediation Step
Connecting to remote server failed with access is denied Connecting user is not mapped in the RoleDefinitions hash table of the .pssc file Run Test-PSSessionConfigurationFile -Path $sessionFile. Confirm that the user’s Active Directory or local group SID is correctly specified in the role mappings.
Role capabilities not recognized; shell opens completely empty The .psrc file is not located in a folder named exactly RoleCapabilities inside an active module directory Confirm that the folder structure adheres to $env:ProgramFiles\WindowsPowerShell\Modules\<ModuleName>\RoleCapabilities\<Name>.psrc. Verify with Get-PSRoleCapability.
RunAsVirtualAccount fails with privilege elevation error The machine lacks local SAM database write authority to generate transient virtual accounts Ensure that the Virtual Account configuration has not been blocked by Local Security Policy. Virtual accounts require member workstation or standalone server roles.
Transcripts are not generated in destination folder NTFS permissions on the transcript path forbid the WinRM service or Virtual Account from creating files Grant BUILTIN\Administrators and NT AUTHORITY\NETWORK SERVICE write and modify rights on the transcript root path.

Operational Governance and Hardening Best Practices

  • Never Expose Scripting Interpreters: Never include Invoke-Expression, Start-Process, or unfiltered cmd.exe in a Role Capability file. Doing so allows malicious operators to break out of the constrained language mode into an unrestricted elevated command prompt.
  • Mandate Full Transcript Logging: Always specify a protected -TranscriptDirectory in the .pssc configuration. JEA automatically captures every keystroke, input parameter, and output record into cryptographic text files, providing complete non-repudiation for audit compliance.
  • Review Constrained Language Mode: JEA automatically locks the session into NoLanguage or RestrictedLanguage mode. Do not attempt to bypass this with LanguageMode = 'FullLanguage' unless every possible function is strictly encapsulated.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.