WinRM & Remote Shell Cheatsheet
WinRM & Remote Shell Cheatsheet
WinRM is Microsoft’s implementation of WS-Management, and PowerShell Remoting rides on top of it. This covers enabling it, connecting interactively and in bulk, adding an HTTPS listener, surviving the double-hop problem, reaching Windows from Linux, and the OpenSSH alternative — then the command and error reference.
Choose a transport
| Method | Port | Best for |
|---|---|---|
| PowerShell Remoting (WinRM) | 5985 / 5986 | The default for Windows-to-Windows administration. Objects, not text. |
| PowerShell Remoting over SSH | 22 | Cross-platform, key-based, no domain needed. PowerShell 7. |
| OpenSSH server | 22 | A plain shell, scp/sftp, familiar key management. |
| RDP | 3389 | A graphical desktop, not scripting. Never expose it to the internet. |
WinRM returns objects Unlike SSH, PowerShell Remoting serializes real objects back to your session, so Get-Service | Where-Object Status -eq Running works remotely exactly as it does locally. That is the reason to prefer it on Windows.
Enable WinRM on the target
One cmdlet starts the service, sets it to automatic, creates an HTTP listener, and opens the firewall.
Enable-PSRemoting -Force # If a NIC is on the Public profile, the firewall rule is skipped. # Prefer fixing the profile; use this only on a trusted network. Enable-PSRemoting -Force -SkipNetworkProfileCheck # Confirm the listener and service Get-Service WinRM winrm enumerate winrm/config/listener
Server versus client Windows Server usually has WinRM enabled already. Windows 11 and 10 do not, and their default network profile is often Public, which is why Enable-PSRemoting appears to succeed yet nothing can connect.
# Local Administrators can connect by default. # Grant non-admins access via this built-in group instead: Add-LocalGroupMember -Group "Remote Management Users" -Member "DOMAIN\svc-monitor" # Review the session configuration ACL interactively Set-PSSessionConfiguration -Name Microsoft.PowerShell -ShowSecurityDescriptorUI
Remote Management Users exists precisely so you do not have to make an account a local administrator just to let it run remote queries.
Trust: domain vs workgroup
In a domain, Kerberos handles mutual authentication and you need none of this. Outside a domain there is no Kerberos, so the client must be told which servers it may send NTLM credentials to.
# Set on the CLIENT, not the target Set-Item WSMan:\localhost\Client\TrustedHosts -Value "srv01.lab.local" -Concatenate -Force Get-Item WSMan:\localhost\Client\TrustedHosts # Clear it again when finished Clear-Item WSMan:\localhost\Client\TrustedHosts -Force
Never use -Value “*” A wildcard tells your machine to hand NTLM credentials to any host that answers, which makes credential relay and spoofing trivial. List specific names, or use HTTPS with certificate validation instead.
Connect and run
$cred = Get-Credential # Interactive shell on one host; 'exit' to leave Enter-PSSession -ComputerName srv01 -Credential $cred # One command, many hosts, in parallel Invoke-Command -ComputerName srv01,srv02,srv03 -Credential $cred -ScriptBlock { Get-Service WinRM | Select-Object PSComputerName, Status } # Run a local script file remotely Invoke-Command -ComputerName srv01 -FilePath .\audit.ps1 # Reusable session: keeps state between calls $s = New-PSSession -ComputerName srv01 -Credential $cred Invoke-Command -Session $s -ScriptBlock { $x = Get-Process } Invoke-Command -Session $s -ScriptBlock { $x.Count } # $x still exists # Copy files through the session (no SMB share needed) Copy-Item -Path .\tool.zip -Destination C:\Temp\ -ToSession $s Copy-Item -Path C:\Logs\app.log -Destination .\ -FromSession $s Remove-PSSession $s
Sessions beat repeated connections Every -ComputerName call sets up and tears down a session. For a loop of commands, create one PSSession and reuse it: faster, and variables persist between calls.
HTTPS listener
HTTPS adds transport encryption and, more importantly, lets the client verify it is talking to the right server.
# Use a CA-issued cert in production. Self-signed for a lab: $c = New-SelfSignedCertificate -DnsName "srv01.lab.local" ` -CertStoreLocation Cert:\LocalMachine\My # Create the HTTPS listener on 5986 New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * ` -CertificateThumbPrint $c.Thumbprint -Force # Open the firewall New-NetFirewallRule -DisplayName "WinRM HTTPS" -Direction Inbound ` -Protocol TCP -LocalPort 5986 -Action Allow # Optional: remove the HTTP listener once HTTPS works # Get-ChildItem WSMan:\localhost\Listener | Where-Object Keys -match "HTTP\b" | Remove-Item -Recurse
# Client side Enter-PSSession -ComputerName srv01.lab.local -UseSSL -Credential $cred # Lab only: skip validation for a self-signed certificate $o = New-PSSessionOption -SkipCACheck -SkipCNCheck Enter-PSSession -ComputerName srv01 -UseSSL -SessionOption $o -Credential $cred
Skipping checks defeats the point -SkipCACheck and -SkipCNCheck disable exactly the server-identity verification you added HTTPS for. Fine while testing; in production issue a proper certificate and drop the flags.
The double-hop problem
You connect to SRV01, and from there try to reach a file share on SRV02. It fails with access denied, even though your account has rights. Your credentials authenticated to SRV01 but cannot be forwarded onward — by design, so a compromised server cannot replay them.
| Solution | Assessment |
|---|---|
| Resource-based constrained delegation | Preferred. Configured on the target resource; no credentials are forwarded and no client change is needed. |
| Kerberos constrained delegation | Solid, but configured per service on the account, so more moving parts. |
| Pass a credential object explicitly | Simple: send $cred into the script block with -ArgumentList and authenticate again on the far side. |
| CredSSP | Last resort. Delegates your plaintext-equivalent credentials to the remote host; anyone who owns that host owns your account. |
# RBCD: allow SRV01 to act on your behalf toward SRV02 (run on a DC) $srv01 = Get-ADComputer SRV01 Set-ADComputer SRV02 -PrincipalsAllowedToDelegateToAccount $srv01 # Simple alternative: carry the credential into the session Invoke-Command -ComputerName srv01 -Credential $cred -ArgumentList $cred -ScriptBlock { param($c) Invoke-Command -ComputerName srv02 -Credential $c -ScriptBlock { hostname } }
Avoid CredSSP if you possibly can It is the easiest fix and the worst one. Reach for resource-based constrained delegation first; it solves the same problem without ever handing your credentials to an intermediate machine.
From Linux
# pywinrm pip install pywinrm python3 - <<'EOF' import winrm s = winrm.Session('https://srv01:5986/wsman', auth=('[email protected]', 'password'), transport='ntlm', server_cert_validation='validate') print(s.run_ps('Get-Service WinRM | Format-List').std_out.decode()) EOF # Ansible inventory for Windows hosts # ansible_connection=winrm # ansible_port=5986 # ansible_winrm_transport=kerberos # or ntlm # ansible_winrm_server_cert_validation=validate # PowerShell 7 on Linux, remoting over SSH pwsh -c 'Enter-PSSession -HostName srv01 -UserName admin'
Kerberos from Linux With a domain, install krb5-user, kinit user@REALM, then use transport=kerberos. That gets you mutual authentication and no stored passwords, which NTLM cannot offer.
OpenSSH on Windows
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 Start-Service sshd Set-Service -Name sshd -StartupType Automatic New-NetFirewallRule -Name sshd -DisplayName "OpenSSH Server" ` -Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow # Make PowerShell the default shell instead of cmd.exe New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell ` -Value "C:\Program Files\PowerShell\7\pwsh.exe" -PropertyType String -ForceAdministrator keys are special For members of the Administrators group, OpenSSH on Windows reads C:\ProgramData\ssh\administrators_authorized_keys, not the user’s .ssh\authorized_keys. That file must be owned by Administrators/SYSTEM with inheritance disabled, or sshd silently refuses the key.
Combines object-based remoting with SSH key authentication and no domain requirement.
# In C:\ProgramData\ssh\sshd_config on the target: # Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo Restart-Service sshd # From any platform running PowerShell 7 Enter-PSSession -HostName srv01 -UserName admin New-PSSession -HostName srv01 -UserName admin -KeyFilePath ~/.ssh/id_ed25519
Use the 8.3 path The progra~1 form avoids the space in “Program Files”, which the subsystem line does not handle well.
Harden & audit
# Confirm the dangerous settings are OFF Get-Item WSMan:\localhost\Service\Auth\Basic Get-Item WSMan:\localhost\Service\AllowUnencrypted Get-Item WSMan:\localhost\Service\Auth\CredSSP # Restrict which addresses may listen / connect Set-Item WSMan:\localhost\Service\IPv4Filter -Value "10.0.0.0-10.0.0.255" # Script block + module logging feeds your SIEM (event 4104) # Computer Config > Admin Templates > Windows Components # > Windows PowerShell > Turn on PowerShell Script Block Logging Get-WinEvent -LogName Microsoft-Windows-WinRM/Operational -MaxEvents 50 Get-WinEvent -LogName Microsoft-Windows-PowerShell/Operational -MaxEvents 50
Just Enough Administration JEA gives an operator a constrained endpoint exposing only named cmdlets, running as a virtual account. Build a role capability (.psrc) and session configuration (.pssc), then Register-PSSessionConfiguration. It is the difference between granting a helpdesk “restart this service” and granting them administrator.
Cmdlets
| Command | Does |
|---|---|
| Enable-PSRemoting -Force | Enable WinRM, listener, and firewall rule. |
| Disable-PSRemoting | Block remote access to session configurations. |
| Test-WSMan -ComputerName X | Is WinRM answering? The first thing to run. |
| Enter-PSSession | Interactive remote shell. |
| Exit-PSSession | Leave it (or type exit). |
| Invoke-Command -ScriptBlock | Run a block on one or many hosts. |
| Invoke-Command -FilePath | Run a local script file remotely. |
| New-PSSession / Remove-PSSession | Create / close a persistent session. |
| Get-PSSession | List sessions. |
| Copy-Item -ToSession / -FromSession | Transfer files over the session. |
| New-PSSessionOption | Timeouts, proxy, certificate checks. |
| Register-PSSessionConfiguration | Publish a custom or JEA endpoint. |
| Get-PSSessionConfiguration | List endpoints and their ACLs. |
| Invoke-Command -AsJob | Run in the background; collect with Receive-Job. |
Useful flags -ThrottleLimit caps concurrency (default 32), -SessionOption tunes timeouts, -ConfigurationName targets a JEA endpoint, -Port and -UseSSL select the listener.
Configuration & the WSMan drive
# Browse configuration like a filesystem Get-ChildItem WSMan:\localhost Get-ChildItem WSMan:\localhost\Listener Get-ChildItem WSMan:\localhost\Service\Auth # Classic winrm.cmd equivalents winrm get winrm/config winrm enumerate winrm/config/listener winrm quickconfig winrm id -r:srv01
| Setting | Meaning |
|---|---|
| Service\AllowUnencrypted | Keep false. True sends traffic in the clear. |
| Service\Auth\Basic | Keep false. Basic sends credentials in the clear over HTTP. |
| Service\Auth\CredSSP | Keep false unless you have accepted the delegation risk. |
| Service\IPv4Filter | Restrict which addresses the listener accepts. |
| Client\TrustedHosts | Workgroup NTLM targets. Never *. |
| Shell\MaxMemoryPerShellMB | Memory ceiling per shell; raise for heavy scripts. |
| Shell\MaxShellsPerUser | Concurrent shells per user. |
| Shell\IdleTimeout | How long an idle session survives. |
Authentication methods
| Method | Mutual auth | Notes |
|---|---|---|
| Kerberos | Yes | Domain default. Best option; no TrustedHosts needed. |
| Negotiate | Kerberos, else NTLM | The practical default. |
| NTLM | No | Workgroup fallback; requires TrustedHosts. Relay-prone. |
| Certificate | Yes | Client certificate mapped to a local account. Strong, more setup. |
| CredSSP | Yes | Solves double hop by delegating credentials. Treat as a last resort. |
| Basic | No | Credentials in the clear over HTTP. Do not enable. |
| Port | Use |
|---|---|
| 5985/tcp | WinRM over HTTP; message-level encryption with Negotiate/Kerberos. |
| 5986/tcp | WinRM over HTTPS; transport encryption plus server identity. |
| 22/tcp | OpenSSH / PowerShell Remoting over SSH. |
| 3389/tcp | RDP. Keep it off the internet; front it with a gateway or VPN. |
Error index
| Message | Cause and fix |
|---|---|
| WinRM cannot complete the operation … verify the machine name is valid | Name resolution, host down, or 5985 blocked. Test with Test-NetConnection srv01 -Port 5985. |
| The WinRM client cannot process the request … default authentication | Workgroup NTLM without trust. Add the host to TrustedHosts, or use HTTPS. |
| Access is denied | Account is not in Administrators or Remote Management Users on the target. |
| Kerberos authentication failed / cannot find computer | SPN or DNS mismatch. Connect by FQDN, and check clock skew (over five minutes breaks Kerberos). |
| The SSL certificate is signed by an unknown authority | Self-signed or untrusted cert. Install the CA, or use -SkipCACheck in a lab only. |
| The SSL certificate contains a common name that does not match | Connect using the exact name in the certificate subject. |
| Access denied on a share from inside a session | The double-hop problem. See Phase 5. |
| Enable-PSRemoting … network connection type is Public | Set the adapter to Private, or use -SkipNetworkProfileCheck on a trusted network. |
| The maximum number of concurrent shells has been reached | Sessions leaked. Get-PSSession | Remove-PSSession, and raise MaxShellsPerUser if genuinely needed. |
| Not enough memory / quota violation | Raise MaxMemoryPerShellMB; the default is modest for large data sets. |
| SSH key ignored for an admin account | Use administrators_authorized_keys with correct ACLs, not the user’s .ssh folder. |
Optimize & secure
| Area | Lever |
|---|---|
| Never enable | Basic auth, AllowUnencrypted, and TrustedHosts = *. Each one hands credentials away. |
| Prefer Kerberos | Mutual authentication with no stored passwords and no TrustedHosts list. |
| Least privilege | Remote Management Users instead of local admin; JEA endpoints for task-specific access. |
| Double hop | Resource-based constrained delegation, not CredSSP. |
| Exposure | Never publish 5985/5986/3389 to the internet. Reach them over VPN or a bastion. |
| Throughput | Reuse PSSession objects; tune -ThrottleLimit for large fan-out; use -AsJob for long runs. |
| Data volume | Filter and select on the remote side; you pay serialization cost for everything returned. |
| Audit | Script block logging (4104) plus the WinRM operational log, shipped to your SIEM. |
References
| Resource | Use | Link |
|---|---|---|
| PowerShell Remoting | Official remoting guide | learn.microsoft.com |
| WinRM reference | Service configuration | Windows Remote Management |
| JEA | Constrained endpoints | Just Enough Administration |
| OpenSSH for Windows | Install and configure | OpenSSH install |

0 comments