PowerShell Remoting is one of the most powerful features in the Windows administration toolkit. It lets you execute commands, run scripts, and manage configurations on remote machines — one at a time or across hundreds simultaneously. At its core, PowerShell Remoting relies on Windows Remote Management (WinRM), Microsoft’s implementation of the WS-Management protocol. In this guide, you will learn how to enable and configure WinRM, use the key remoting cmdlets, manage credentials securely, handle cross-domain scenarios, and leverage SSH-based remoting for cross-platform management.
Prerequisites
Before you begin configuring PowerShell Remoting, make sure you have the following in place:
- Windows Server 2016 or later (or Windows 10/11 Pro/Enterprise for client machines)
- PowerShell 5.1 or later installed (PowerShell 7+ recommended for SSH-based remoting)
- Administrative privileges on both the client and remote machines
- Network connectivity on port 5985 (HTTP) or 5986 (HTTPS) between client and server
- Active Directory domain (recommended but not required — workgroup setups are covered below)
- Basic familiarity with PowerShell cmdlets and Windows firewall configuration
Enabling and Configuring WinRM
WinRM is installed by default on modern Windows systems, but the service is typically not running and the firewall rules are not enabled on client operating systems. On Windows Server editions starting with 2012, WinRM is enabled by default, but you should still verify the configuration.
Enable PSRemoting
The fastest way to enable everything at once is the Enable-PSRemoting cmdlet:
# Run in an elevated PowerShell session on the remote machine
Enable-PSRemoting -Force
This single command performs several actions: it starts the WinRM service, sets it to start automatically, creates HTTP listener on port 5985, adds Windows Firewall exceptions, and registers the default PowerShell session configurations.
Verify WinRM is Listening
After enabling, confirm the service is running and listeners are active:
# Check service status
Get-Service WinRM
# List active listeners
Get-WSManInstance -ResourceURI winrm/config/listener -Enumerate
# Quick connectivity test from the client
Test-WSMan -ComputerName SERVER01
Configure TrustedHosts
In a domain environment, Kerberos handles authentication transparently. In workgroup or cross-domain scenarios, you need to configure TrustedHosts on the client machine:
# Add a single host
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "SERVER01"
# Add multiple hosts (comma-separated)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "SERVER01,SERVER02,192.168.1.50"
# Trust all hosts (NOT recommended for production)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*"
# View current TrustedHosts
Get-Item WSMan:\localhost\Client\TrustedHosts
Configure HTTPS Transport
For production environments, especially across untrusted networks, HTTPS transport encrypts the entire WinRM session:
# On the remote server: create a self-signed certificate
$cert = New-SelfSignedCertificate -DnsName "SERVER01.contoso.com" `
-CertStoreLocation Cert:\LocalMachine\My -NotAfter (Get-Date).AddYears(5)
# Create an HTTPS listener using the certificate thumbprint
New-WSManInstance -ResourceURI winrm/config/listener `
-SelectorSet @{Address="*"; Transport="HTTPS"} `
-ValueSet @{CertificateThumbprint=$cert.Thumbprint}
# Add firewall rule for HTTPS
New-NetFirewallRule -DisplayName "WinRM HTTPS" -Direction Inbound `
-LocalPort 5986 -Protocol TCP -Action Allow
Configure via Group Policy
For domain-wide deployment, use Group Policy to enable WinRM at scale:
- Navigate to Computer Configuration → Administrative Templates → Windows Components → Windows Remote Management → WinRM Service
- Enable Allow remote server management through WinRM
- Set the IPv4/IPv6 filter to
*or specific subnets - Configure Windows Remote Shell → Allow Remote Shell Access to Enabled
Using Invoke-Command and Enter-PSSession
PowerShell provides two primary cmdlets for remote execution, each suited to different workflows.
Invoke-Command — Parallel Batch Execution
Invoke-Command sends a script block to one or more computers and runs them in parallel by default (up to 32 concurrent connections):
# Run a command on a single remote host
Invoke-Command -ComputerName SERVER01 -ScriptBlock {
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
}
# Run against multiple servers simultaneously
$servers = @("SERVER01", "SERVER02", "SERVER03", "SERVER04")
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-Service -Name "W3SVC" | Select-Object Status, MachineName
}
# Throttle concurrency for large environments
Invoke-Command -ComputerName $servers -ScriptBlock {
Restart-Service -Name "Spooler" -Force
} -ThrottleLimit 10
# Run a local script file on remote machines
Invoke-Command -ComputerName $servers -FilePath "C:\Scripts\Audit-Security.ps1"
Enter-PSSession — Interactive Remoting
Enter-PSSession is ideal for ad-hoc troubleshooting where you need an interactive shell:
# Start an interactive session
Enter-PSSession -ComputerName SERVER01 -Credential (Get-Credential)
# You're now on SERVER01 — run commands as if local
[SERVER01]: PS C:\> Get-EventLog -LogName System -Newest 20
[SERVER01]: PS C:\> Get-Disk | Format-Table
[SERVER01]: PS C:\> Exit-PSSession
Persistent Sessions (PSSessions)
For repeated connections, create a persistent session to avoid the overhead of establishing a new connection each time:
# Create persistent sessions
$sessions = New-PSSession -ComputerName SERVER01, SERVER02, SERVER03
# Reuse the session across multiple commands
Invoke-Command -Session $sessions -ScriptBlock { Get-HotFix | Select-Object -Last 5 }
Invoke-Command -Session $sessions -ScriptBlock { Get-WindowsFeature | Where-Object Installed }
# Copy files through a session
Copy-Item -Path "C:\Deploy\app.zip" -Destination "C:\Install\" -ToSession $sessions[0]
# Clean up when done
Remove-PSSession $sessions
Credential Management and Security
Proper credential handling is crucial for secure remoting at scale.
Using Get-Credential and PSCredential
# Interactive prompt
$cred = Get-Credential -UserName "CONTOSO\admin" -Message "Enter remote admin password"
# Programmatic credential (for automation — protect the password source)
$securePass = ConvertTo-SecureString "P@ssw0rd!" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential("CONTOSO\admin", $securePass)
Storing Credentials Securely
Never hardcode passwords in scripts. Use one of these approaches:
# Export encrypted credential to file (user+machine specific)
Get-Credential | Export-Clixml -Path "$HOME\cred.xml"
# Import in another session (same user, same machine only)
$cred = Import-Clixml -Path "$HOME\cred.xml"
# Use Windows Credential Manager via CredentialManager module
Install-Module -Name CredentialManager -Force
New-StoredCredential -Target "WinRM-Admin" -UserName "CONTOSO\admin" `
-Password "P@ssw0rd!" -Persist LocalMachine
$cred = Get-StoredCredential -Target "WinRM-Admin"
CredSSP for Double-Hop Scenarios
When a remote command needs to access a second resource (the “double-hop” problem), configure CredSSP delegation:
# On the client: enable CredSSP client role
Enable-WSManCredSSP -Role Client -DelegateComputer "SERVER01.contoso.com"
# On the server: enable CredSSP server role
Enable-WSManCredSSP -Role Server
# Connect using CredSSP authentication
Invoke-Command -ComputerName SERVER01 -Credential $cred `
-Authentication CredSSP -ScriptBlock {
# This can now access network resources like file shares
Get-ChildItem "\\FILESERVER\Share$"
}
Warning: CredSSP sends credentials to the remote server. Only use it with machines you trust and preferably over HTTPS.
Just Enough Administration (JEA)
For least-privilege remoting, configure JEA endpoints that restrict what users can do:
# Create a role capability file
New-PSRoleCapabilityFile -Path "C:\JEA\HelpDeskRole.psrc" `
-VisibleCmdlets @("Restart-Service", "Get-Service", "Get-Process") `
-VisibleFunctions @("Get-HotFix")
# Create session configuration
New-PSSessionConfigurationFile -Path "C:\JEA\HelpDesk.pssc" `
-SessionType RestrictedRemoteServer `
-RunAsVirtualAccount `
-RoleDefinitions @{ "CONTOSO\HelpDesk" = @{ RoleCapabilities = "HelpDeskRole" } }
# Register the endpoint
Register-PSSessionConfiguration -Name "HelpDesk" -Path "C:\JEA\HelpDesk.pssc"
SSH-Based Remoting for Cross-Platform
Starting with PowerShell 7, you can use SSH as the transport layer instead of WinRM. This is especially useful for managing Linux and macOS machines alongside Windows servers.
Install and Configure OpenSSH
# On Windows: install the OpenSSH server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service sshd -StartupType Automatic
# Configure the SSH subsystem for PowerShell in sshd_config
# Add this line to C:\ProgramData\ssh\sshd_config:
# Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo -NoProfile
Restart-Service sshd
On Linux, add the PowerShell subsystem to /etc/ssh/sshd_config:
# Install PowerShell on Ubuntu
sudo apt-get update && sudo apt-get install -y powershell
# Add subsystem to sshd_config
echo "Subsystem powershell /usr/bin/pwsh -sshs -NoLogo -NoProfile" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart sshd
Using SSH-Based Remoting
# Interactive session over SSH
Enter-PSSession -HostName linux-server01 -UserName admin -SSHTransport
# Run commands on Linux from Windows
Invoke-Command -HostName linux-server01 -UserName admin -ScriptBlock {
uname -a
df -h
systemctl status nginx
}
# Mixed-OS targeting
$windowsSession = New-PSSession -ComputerName WIN-SRV01 -Credential $cred
$linuxSession = New-PSSession -HostName LINUX-SRV01 -UserName admin -SSHTransport
Invoke-Command -Session $windowsSession, $linuxSession -ScriptBlock {
if ($IsLinux) { cat /etc/os-release } else { Get-ComputerInfo | Select-Object OsName }
}
Comparison: WinRM vs SSH-Based vs OpenSSH on Windows
| Feature | WinRM (HTTP/HTTPS) | SSH-Based Remoting (PS7) | OpenSSH on Windows |
|---|---|---|---|
| Protocol | WS-Management | SSH | SSH |
| Default ports | 5985 / 5986 | 22 | 22 |
| Authentication | Kerberos, NTLM, CredSSP | SSH keys, password | SSH keys, password |
| Cross-platform | Windows only | Windows, Linux, macOS | Windows only (server) |
| PowerShell version | 5.1+ | 7.0+ required | Any (just SSH shell) |
| Encryption | HTTPS or message-level | Always encrypted | Always encrypted |
| Double-hop | CredSSP or Kerberos delegation | SSH agent forwarding | SSH agent forwarding |
| Domain integration | Native AD/Kerberos | Manual key management | Manual key management |
| GPO management | Full support | Limited | Limited |
| Best for | Windows-only environments | Mixed-OS environments | Linux admins managing Windows |
Real-World Scenario
Imagine you are a systems administrator managing 50+ Windows servers across three environments — Development, Staging, and Production — plus a handful of Linux machines running Nginx reverse proxies. Here is how you would structure your PowerShell Remoting workflow:
# Define server groups
$devServers = Get-Content "C:\ServerLists\dev.txt"
$stagingServers = Get-Content "C:\ServerLists\staging.txt"
$prodServers = Get-Content "C:\ServerLists\prod.txt"
# Create persistent sessions with stored credentials
$cred = Import-Clixml "$HOME\admin-cred.xml"
$devSessions = New-PSSession -ComputerName $devServers -Credential $cred
$stagingSessions = New-PSSession -ComputerName $stagingServers -Credential $cred
# Morning health check across all dev servers
$healthReport = Invoke-Command -Session $devSessions -ScriptBlock {
[PSCustomObject]@{
Server = $env:COMPUTERNAME
Uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
CPUPercent = (Get-CimInstance Win32_Processor).LoadPercentage
FreeGB = [math]::Round((Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'").FreeSpace / 1GB, 2)
Services = (Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" }).Count
}
}
$healthReport | Format-Table Server, Uptime, CPUPercent, FreeGB, Services -AutoSize
# Deploy a hotfix to staging with throttled concurrency
Invoke-Command -Session $stagingSessions -ThrottleLimit 5 -ScriptBlock {
Start-Process msiexec.exe -ArgumentList "/i C:\Deploy\hotfix-kb123.msi /quiet" -Wait
Write-Output "$env:COMPUTERNAME — Hotfix installed"
}
# Check Linux proxies over SSH
$linuxSessions = New-PSSession -HostName "proxy01","proxy02" -UserName admin -SSHTransport
Invoke-Command -Session $linuxSessions -ScriptBlock {
systemctl is-active nginx
curl -s -o /dev/null -w "%{http_code}" http://localhost
}
This pattern keeps sessions open for the duration of your work, minimizes authentication overhead, and lets you target specific environment groups with throttled execution.
Gotchas and Edge Cases
- Firewall blocking: WinRM uses port 5985 (HTTP) and 5986 (HTTPS). If remoting fails silently, check both Windows Firewall and any network firewalls between client and server.
- TrustedHosts resets: Setting TrustedHosts with
Set-Itemreplaces the entire list. Use-Concatenateto append:Set-Item WSMan:\localhost\Client\TrustedHosts -Value "NEWSERVER" -Concatenate. - MaxMemoryPerShellMB: The default limit (1024 MB) can cause sessions to drop during memory-intensive operations. Increase with
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048. - Double-hop failure: Accessing network resources from a remote session fails by default. Use CredSSP, Kerberos constrained delegation, or resource-based constrained delegation to solve this.
- Session limits: WinRM defaults to 25 concurrent shells per user. In large environments, increase
MaxShellsPerUserin the WinRM configuration. - Time zone mismatch: Timestamps returned from remote sessions use the remote server’s time zone. Use
[DateTime]::UtcNowfor consistency in multi-region environments. - SSH subsystem path: On Windows, the subsystem path in
sshd_configmust use the 8.3 short path format (progra~1) or the full path without spaces. - Serialization limits: Objects returned from remote sessions are deserialized — they lose their methods. What comes back is a snapshot, not a live object. Plan your script blocks to extract the data you need on the remote side.
- PowerShell version mismatch: If the remote machine runs PowerShell 5.1 and the client runs PowerShell 7, not all cmdlets will be available. Test with
-ConfigurationName PowerShell.7for PS7 endpoints.
Summary
- Enable-PSRemoting is the one-stop command for configuring WinRM on any Windows machine.
- TrustedHosts must be configured on the client for workgroup or cross-domain connections.
- HTTPS transport should be used in production to encrypt remoting traffic end to end.
- Invoke-Command is the workhorse for parallel execution across multiple servers; Enter-PSSession is best for interactive troubleshooting.
- Credential management should use Export-Clixml, Credential Manager, or a secrets vault — never hardcoded passwords.
- CredSSP or Kerberos constrained delegation solves the double-hop problem.
- SSH-based remoting in PowerShell 7+ enables cross-platform management of Linux and macOS from the same scripts.
- JEA endpoints provide least-privilege remoting for delegated administration.
- Watch for session limits, serialization behavior, and firewall rules as your environment scales.