Introduction
Managing 5 Windows servers by RDP-ing into each is inefficient. PowerShell Remoting (based on WinRM, Windows Remote Management) lets you:
- Run a command on 10 servers in parallel
- Retrieve info from a centralized script
- Install updates across a whole fleet
- Audit configurations
It's the SSH equivalent of the Windows world: the foundation of modern Windows sysadmin.
Prerequisites
- 2+ Windows Server 2019/2022/2025 (or Windows 10/11 Pro)
- A user with admin privileges on all servers
- Network connectivity between machines (TCP 5985 HTTP or 5986 HTTPS)
Step 1: Enable WinRM on target servers
On each target server (PowerShell as admin):
Enable-PSRemoting -Force
This command:
- Starts the WinRM service
- Configures firewall for TCP 5985 (HTTP)
- Creates a WinRM listener
- Enables delegation for admins
Verify:
Get-Service WinRM
Get-PSSessionConfiguration
winrm enumerate winrm/config/listener
Step 2: Test connection from the management server
From your admin workstation (PowerShell):
Test-WSMan -ComputerName srv-app-01
Expected output:
wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/...
ProductVendor : Microsoft Corporation
ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 3.0
If error, see troubleshooting section.
Step 3: Configure TrustedHosts (for non-domain)
If your servers are not in an AD domain, you must add targets to client TrustedHosts:
# Add a specific server
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "srv-app-01" -Force
# Add multiple
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "srv-app-01,srv-db-01,srv-web-01" -Force
# Allow all (DEV only)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force
# Check
Get-Item WSMan:\localhost\Client\TrustedHosts
Step 4: Run a remote command
One command, one server
Invoke-Command -ComputerName srv-app-01 -ScriptBlock {
Get-ComputerInfo | Select-Object CsName, OsName, OsVersion
} -Credential (Get-Credential)
A window asks for credentials. To automate:
$cred = Get-Credential
Invoke-Command -ComputerName srv-app-01 -ScriptBlock { hostname } -Credential $cred
One command across multiple servers in parallel
$servers = @("srv-app-01", "srv-app-02", "srv-db-01")
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-Service | Where-Object Status -eq 'Running' | Measure-Object | Select-Object Count
} -Credential $cred
Output: Get-Service runs in parallel on all 3 servers.
Step 5: Persistent sessions (PSSession)
To run multiple commands without re-authenticating each time:
$session = New-PSSession -ComputerName srv-app-01 -Credential $cred
Invoke-Command -Session $session -ScriptBlock { Get-Service WinRM }
Invoke-Command -Session $session -ScriptBlock { Get-Process | Select -First 5 }
# When done
Remove-PSSession $session
Interactive session (SSH-like)
Enter-PSSession -ComputerName srv-app-01 -Credential $cred
Prompt changes: [srv-app-01]: PS C:\Users\admin\>. Type Windows commands as if local. To exit:
Exit-PSSession
Step 6: Copy files via WinRM
$session = New-PSSession -ComputerName srv-app-01 -Credential $cred
# Push (local → remote)
Copy-Item -Path "C:\scripts\install.ps1" -Destination "C:\temp\install.ps1" -ToSession $session
# Pull (remote → local)
Copy-Item -Path "C:\logs\app.log" -Destination "C:\local-logs\srv-app-01_app.log" -FromSession $session
Remove-PSSession $session
Step 7: Common admin scripts
Complete fleet inventory
$servers = @("srv-app-01", "srv-app-02", "srv-db-01")
$cred = Get-Credential
$inventory = Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
[PSCustomObject]@{
Hostname = $env:COMPUTERNAME
OS = (Get-CimInstance Win32_OperatingSystem).Caption
Version = (Get-CimInstance Win32_OperatingSystem).Version
CPU = (Get-CimInstance Win32_Processor).Name
Cores = (Get-CimInstance Win32_Processor).NumberOfCores
RAM_GB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
Disk_C_Free = [math]::Round((Get-PSDrive C).Free / 1GB, 2)
Uptime = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
LastUpdate = (Get-HotFix | Sort-Object InstalledOn -Descending | Select -First 1).InstalledOn
}
}
$inventory | Format-Table -AutoSize
$inventory | Export-Csv "C:\fleet-inventory.csv" -NoTypeInformation
Install Windows updates everywhere
Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
Install-PackageProvider -Name NuGet -Force | Out-Null
Install-Module PSWindowsUpdate -Force | Out-Null
Import-Module PSWindowsUpdate
Get-WindowsUpdate -AcceptAll -Install -AutoReboot
}
Service status across the fleet
Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
Get-Service -Name "Spooler","WinRM","DNS" |
Select-Object @{N='Server';E={$env:COMPUTERNAME}}, Name, Status
} | Format-Table -AutoSize
List active users
Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
query user
}
Restart multiple servers
Restart-Computer -ComputerName $servers -Credential $cred -Force -Wait -For PowerShell -Timeout 600
Step 8: Secure with HTTPS (WinRM over SSL)
By default WinRM uses unencrypted HTTP (port 5985). In production, encrypt with HTTPS (port 5986).
Generate a self-signed cert (test) or use a CA
$cert = New-SelfSignedCertificate -DnsName "srv-app-01" -CertStoreLocation "Cert:\LocalMachine\My"
Create the HTTPS listener
$thumbprint = $cert.Thumbprint
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbprint $thumbprint -Force
Open firewall
New-NetFirewallRule -DisplayName "WinRM HTTPS" -Protocol TCP -LocalPort 5986 -Action Allow
Disable HTTP
Get-ChildItem WSMan:\localhost\Listener | Where-Object { $_.Keys -match "HTTP$" } | Remove-Item -Recurse
Disable-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)"
Connect over HTTPS
Enter-PSSession -ComputerName srv-app-01 -UseSSL -Credential $cred
For a self-signed cert, add -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck).
Step 9: JEA (Just Enough Administration)
To give a user the right to run certain commands without making them admin:
# Create a session config
New-PSSessionConfigurationFile -Path C:\Config\WebAdmin.pssc -SessionType RestrictedRemoteServer -RoleDefinitions @{'DOMAIN\WebOps' = @{ RoleCapabilities = 'WebOpsRole' }}
# Create role capability
New-PSRoleCapabilityFile -Path C:\Config\WebOpsRole.psrc -VisibleCmdlets @('Restart-Service', 'Get-Service', 'Get-IISSite')
# Register
Register-PSSessionConfiguration -Path C:\Config\WebAdmin.pssc -Name WebAdmin
User DOMAIN\WebOps can now connect to this restricted session and only use allowed cmdlets.
Step 10: Group Policy to enable WinRM at scale
To enable WinRM on all servers in an AD domain:
- GPMC.msc → create/edit a GPO
- Computer Configuration → Policies → Administrative Templates → Windows Components → Windows Remote Management (WinRM)
- Enable:
- WinRM Service: Allow remote server management through WinRM = Enabled
- Windows Firewall: open ports 5985/5986
- Link GPO to servers OU
gpupdate /force on servers to apply immediately.
Troubleshooting
"WinRM cannot complete the operation"
WinRM service isn't started on the target:
Get-Service WinRM
Start-Service WinRM
Set-Service WinRM -StartupType Automatic
"Access is denied"
User not in Remote Management Users group or not admin. Add:
Add-LocalGroupMember -Group "Remote Management Users" -Member "DOMAIN\username"
"The WinRM client cannot process the request"
Add target to TrustedHosts (step 3) or use HTTPS.
Firewall blocks
Get-NetFirewallRule | Where-Object DisplayName -like "*Windows Remote Management*" | Enable-NetFirewallRule
Timeout on long commands
$opt = New-PSSessionOption -OperationTimeout 600000 # 10 min
Invoke-Command -ComputerName srv -ScriptBlock {...} -SessionOption $opt
Useful commands
# List active sessions
Get-PSSession
# Kill all sessions
Get-PSSession | Remove-PSSession
# View WinRM config
winrm get winrm/config
winrm get winrm/config/listener
# Test from CLI
winrm identify -r:http://srv-app-01:5985
# Full WinRM reset
winrm quickconfig -force
winrm invoke restore winrm/config
# List listeners
winrm enumerate winrm/config/listener
# Get WinRM log
Get-WinEvent -LogName "Microsoft-Windows-WinRM/Operational" -MaxEvents 50
Conclusion
With PowerShell Remoting mastered:
- You manage 1 or 100 servers with the same effort
- Scripts run in parallel automatically
- You avoid RDP sessions (faster, audit-friendly)
Going further:
- Combine with DSC (Desired State Configuration) for infra-as-code
- Use Azure Arc to manage on-prem Windows servers from the cloud
- Migrate to Ansible or Puppet for multi-OS orchestration
Resources
- Official documentation: https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands
- Microsoft PowerShell Gallery: https://www.powershellgallery.com
- VeryCloud guide — Active Directory:
/docs/article/active-directory - VeryCloud guide — Task Scheduler:
/docs/article/task-scheduler

















