How to Set Up and Use PowerShell Remoting on Windows Server 2025
PowerShell Remoting transforms individual server management into scalable fleet operations. Instead of logging into each server to run a command, PowerShell Remoting lets you execute scripts on dozens or hundreds of Windows Server 2025 hosts simultaneously from a single administrative workstation. Built on top of WS-Management (WinRM) by default — and optionally on SSH — it provides encrypted, authenticated remote command execution that integrates naturally with the rest of the PowerShell ecosystem. Whether you need an interactive shell on a remote server for troubleshooting, want to collect inventory data across your environment in parallel, or need to import modules from a remote server without installing them locally, PowerShell Remoting has a solution.
Prerequisites
- Windows Server 2025 on both source (management) and target (remote) machines
- Administrator privileges on all servers involved
- Network connectivity between management host and target servers (TCP 5985 for HTTP, 5986 for HTTPS)
- Windows Defender Firewall rules allowing WinRM (configured automatically by
Enable-PSRemoting) - For SSH remoting: OpenSSH Server feature installed on target servers
Step 1: Enabling PowerShell Remoting
Enable-PSRemoting is the single command that configures WinRM, creates the necessary firewall rules, and registers the default PowerShell session configuration endpoint. On Windows Server 2025, it is often already enabled by default when the server is domain-joined, but should be explicitly verified.
# Run this on EACH server you want to manage remotely (as Administrator)
Enable-PSRemoting -Force
# Verify WinRM is running and configured
Get-Service WinRM | Select-Object Name, Status, StartType
winrm enumerate winrm/config/listener
# Check which session configurations are registered
Get-PSSessionConfiguration | Select-Object Name, Enabled, Permission
# On workgroup (non-domain) machines, you must add the management host to TrustedHosts
# WARNING: TrustedHosts bypasses mutual authentication — use HTTPS or Kerberos instead when possible
Set-Item WSMan:localhostClientTrustedHosts -Value "192.168.1.50,192.168.1.51" -Force
Get-Item WSMan:localhostClientTrustedHosts
On domain-joined servers in an Active Directory environment, Kerberos handles mutual authentication automatically — no TrustedHosts configuration is needed. WinRM connections between domain members are authenticated and encrypted using Kerberos negotiate.
Step 2: Interactive Remote Sessions with Enter-PSSession
Enter-PSSession opens an interactive PowerShell prompt on a remote server, similar to SSH. Your local prompt changes to show the remote computer name, and every command you type executes on the remote host. This is ideal for one-off troubleshooting tasks where you need to explore the remote system interactively.
# Connect interactively to a remote server (domain environment — Kerberos auth)
Enter-PSSession -ComputerName "WS2025-SVR01"
# Connect with explicit credentials (workgroup or cross-domain)
$Cred = Get-Credential -Message "Enter admin credentials for WS2025-SVR01"
Enter-PSSession -ComputerName "WS2025-SVR01" -Credential $Cred
# Once connected, your prompt changes:
# [WS2025-SVR01]: PS C:UsersAdministratorDocuments>
# Run any command remotely — this executes ON the remote server
Get-EventLog -LogName System -Newest 20 | Where-Object { $_.EntryType -eq "Error" }
# Check local disk space on the remote server
Get-PSDrive -PSProvider FileSystem | Select-Object Name, Used, Free
# Exit the remote session and return to local prompt
Exit-PSSession
Note that Enter-PSSession is single-threaded and synchronous — only one server at a time. For multi-server operations, use Invoke-Command instead.
Step 3: Running Commands in Parallel with Invoke-Command
Invoke-Command is the powerhouse of PowerShell Remoting. By passing an array of computer names, it fans out execution across all targets concurrently (up to 32 simultaneous connections by default, configurable via -ThrottleLimit), collects results, and returns them as local objects annotated with the originating computer name.
# Run a command on multiple servers simultaneously
$Servers = @("WS2025-SVR01", "WS2025-SVR02", "WS2025-SVR03", "WS2025-SVR04")
Invoke-Command -ComputerName $Servers -ScriptBlock {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
OSVersion = (Get-CimInstance Win32_OperatingSystem).Version
Uptime = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
FreeMemoryGB = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)
}
} | Sort-Object ComputerName | Format-Table -AutoSize
# Increase parallelism for larger server counts
Invoke-Command -ComputerName $Servers -ThrottleLimit 50 -ScriptBlock {
Get-Service -Name "wuauserv" | Select-Object Name, Status
}
# With explicit credentials
Invoke-Command -ComputerName $Servers -Credential $Cred -ScriptBlock {
Restart-Service -Name "Spooler" -Force
"Spooler restarted on $env:COMPUTERNAME"
}
Step 4: Passing Local Variables with the $using: Scope
Script blocks passed to Invoke-Command run in a separate process on the remote server and cannot directly access local variables. The $using: scope modifier solves this cleanly — prefix any local variable name with $using: inside the script block to serialize and transmit its value to the remote session.
# Local variables that need to be passed to remote sessions
$ServiceName = "Spooler"
$ThresholdMB = 500
$LogPath = "C:LogsServiceCheck.log"
$Servers = @("WS2025-SVR01", "WS2025-SVR02")
Invoke-Command -ComputerName $Servers -ScriptBlock {
# Reference local variables using $using: prefix
$svc = Get-Service -Name $using:ServiceName -ErrorAction SilentlyContinue
if ($svc.Status -ne "Running") {
Start-Service -Name $using:ServiceName
"Started $using:ServiceName on $env:COMPUTERNAME" | Out-File $using:LogPath -Append
}
# $using: works with all serializable types: strings, numbers, arrays, hashtables
$proc = Get-Process -Name $using:ServiceName -ErrorAction SilentlyContinue
if ($proc -and ($proc.WorkingSet64 / 1MB) -gt $using:ThresholdMB) {
"WARNING: $using:ServiceName using $([math]::Round($proc.WorkingSet64/1MB,1)) MB on $env:COMPUTERNAME"
}
}
Step 5: Persistent Sessions with New-PSSession
For repeated operations against the same servers, creating a persistent PSSession object avoids the overhead of re-authenticating and re-establishing connections on every command. Sessions also preserve remote state — variables and functions defined in a session persist for its lifetime.
# Create persistent sessions to multiple servers
$Sessions = New-PSSession -ComputerName $Servers -Credential $Cred
# Run multiple commands against the same sessions (no reconnect overhead)
Invoke-Command -Session $Sessions -ScriptBlock { $RemoteVar = "SharedState" }
Invoke-Command -Session $Sessions -ScriptBlock { Write-Output "Variable is: $RemoteVar" }
# Copy files TO remote servers using a persistent session
$Sessions | ForEach-Object {
Copy-Item -Path "C:ScriptsDeployScript.ps1" -Destination "C:Scripts" -ToSession $_
}
# Copy files FROM remote servers
Copy-Item -Path "C:Logsapp.log" -Destination "C:Collected$($Sessions[0].ComputerName)_app.log" `
-FromSession $Sessions[0]
# Configure session options for high-latency or unreliable networks
$SessionOpts = New-PSSessionOption `
-OperationTimeout 120000 `
-OpenTimeout 30000 `
-IdleTimeout 3600000 `
-MaximumConnectionRedirectionCount 2
$ReliableSession = New-PSSession -ComputerName "WS2025-SVR01" -SessionOption $SessionOpts
# Always clean up sessions when done
$Sessions | Remove-PSSession
Step 6: Implicit Remoting with Import-PSSession
Implicit remoting allows you to use commands from a remote server as if they were installed locally. This is especially useful for managing server roles — such as Active Directory or Exchange — where the management module is installed on the server but not on your workstation.
# Connect to a server that has the Active Directory module installed
$ADSession = New-PSSession -ComputerName "WS2025-DC01" -Credential $Cred
# Import the AD module from the remote server into your local session
Import-PSSession -Session $ADSession -Module ActiveDirectory -Prefix Remote
# Now you can run AD commands locally — they transparently execute on WS2025-DC01
Get-RemoteADUser -Identity "jdoe"
Get-RemoteADGroupMember -Identity "Domain Admins"
# Clean up when finished
Remove-PSSession $ADSession
Step 7: PowerShell SSH Remoting
Windows Server 2025 supports PowerShell remoting over SSH as an alternative transport to WinRM. SSH remoting is useful for cross-platform environments (connecting from Linux or macOS to Windows), when WinRM is blocked by network policy, or when you prefer certificate-based authentication over Kerberos/NTLM.
# Install OpenSSH Server on Windows Server 2025 (if not already installed)
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
# Verify the PowerShell SSH subsystem is configured in sshd_config
# Add this line to C:ProgramDatasshsshd_config:
# Subsystem powershell C:/Program Files/PowerShell/7/pwsh.exe -sshs -NoLogo
# Restart SSH service after config change
Restart-Service sshd
# Connect using SSH transport (from PowerShell 7+)
Enter-PSSession -HostName "WS2025-SVR01" -UserName "Administrator" -SSHTransport
# Invoke-Command over SSH
Invoke-Command -HostName "WS2025-SVR01" -UserName "Administrator" -SSHTransport -ScriptBlock {
$PSVersionTable
}
# Using SSH key-based authentication (no password prompt)
Invoke-Command -HostName "WS2025-SVR01" -UserName "Administrator" `
-KeyFilePath "$env:USERPROFILE.sshid_ed25519" -SSHTransport -ScriptBlock {
Get-Date
}
Step 8: Troubleshooting Connectivity Issues
When remoting connections fail, a systematic approach to diagnosis saves considerable time. Most failures fall into one of four categories: WinRM not enabled on the target, firewall blocking the port, authentication failures, or name resolution problems.
# Test basic WinRM connectivity (checks port 5985)
Test-WSMan -ComputerName "WS2025-SVR01"
# Test with credentials if domain auth fails
Test-WSMan -ComputerName "WS2025-SVR01" -Credential $Cred -Authentication Negotiate
# Check if WinRM port is open (network-level test)
Test-NetConnection -ComputerName "WS2025-SVR01" -Port 5985
# View WinRM configuration on the TARGET server (run locally on that server)
winrm get winrm/config
winrm get winrm/config/service
# Check WinRM listener status
Get-WSManInstance -ResourceURI winrm/config/listener -SelectorSet @{Transport="HTTP";Address="*"}
# Review PowerShell remoting event log for errors
Get-WinEvent -LogName "Microsoft-Windows-WinRM/Operational" -MaxEvents 50 |
Where-Object { $_.LevelDisplayName -eq "Error" } |
Select-Object TimeCreated, Message
# Reset WinRM to defaults if misconfigured (run on target server)
winrm quickconfig -force
PowerShell Remoting on Windows Server 2025 is not merely a convenience feature — it is the foundation of scalable, repeatable Windows infrastructure management. By mastering Enter-PSSession for interactive troubleshooting, Invoke-Command for parallel fleet operations, persistent sessions for stateful workflows, and SSH remoting for cross-platform scenarios, you gain the ability to manage hundreds of servers as efficiently as you manage one. Combine this with credential management best practices — using Get-Credential, stored credentials in PSCredential objects, or certificate authentication via SSH — and you have a secure, auditable remote management architecture that scales from a handful of servers to thousands.