Introduction to PowerShell Remoting

PowerShell Remoting enables administrators to run commands and scripts on remote Windows Server 2019 machines as if they were running locally. Based on the WS-Management protocol (WinRM) and the Web Services for Management (WSMan) standard, PowerShell Remoting provides encrypted, authenticated remote management over HTTP (port 5985) or HTTPS (port 5986). Unlike older remote management approaches such as DCOM-based WMI or PSExec, PowerShell Remoting uses a standardized, firewall-friendly protocol with support for Kerberos and NTLM authentication, SSL/TLS encryption, and constrained endpoint configurations for delegated administration. Windows Server 2019 comes with WinRM enabled by default in domain environments.

Enabling and Configuring PowerShell Remoting

On Windows Server 2019 in a domain environment, WinRM is typically already configured. Enable PowerShell Remoting explicitly using the Enable-PSRemoting cmdlet:

Enable-PSRemoting -Force -SkipNetworkProfileCheck

This command starts the WinRM service, sets it to automatic startup, creates a WinRM listener on port 5985 for HTTP, configures the Windows Firewall rule, and registers the default PowerShell session configuration endpoints. Verify WinRM is running and configured:

Get-Service WinRM | Select-Object Status, StartType
winrm get winrm/config
winrm enumerate winrm/config/Listener
Get-WSManInstance WinRM/Config

Check which endpoint configurations (session configurations) are available:

Get-PSSessionConfiguration | Select-Object Name, PSVersion, Permission, RunAsUser | Format-Table -AutoSize

Configuring HTTPS for Secure Remoting

By default, PowerShell Remoting uses HTTP which encrypts the payload using Kerberos or NTLM message encryption, but does not use TLS for transport encryption. For cross-domain scenarios, workgroup environments, or compliance requirements, configure HTTPS. First obtain an SSL certificate with the server’s FQDN as the Subject:

$cert = New-SelfSignedCertificate -DnsName "SERVER01.contoso.com" -CertStoreLocation Cert:LocalMachineMy -KeyUsage KeyEncipherment, DigitalSignature
Write-Host "Certificate Thumbprint: $($cert.Thumbprint)"

Create the HTTPS WinRM listener using the certificate:

$thumbprint = $cert.Thumbprint
New-Item -Path WSMan:localhostListener -Transport HTTPS -Address * -CertificateThumbprint $thumbprint -Force

Open the HTTPS port in Windows Firewall:

New-NetFirewallRule -DisplayName "WinRM HTTPS" -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Domain

Connect to the remote server over HTTPS from a client:

Enter-PSSession -ComputerName SERVER01.contoso.com -UseSSL -Credential (Get-Credential)

Interactive Remote Sessions

Enter-PSSession creates an interactive remote PowerShell session, similar to SSH for Linux. The prompt changes to indicate the remote machine:

Enter-PSSession -ComputerName SERVER01
[SERVER01]: PS C:> Get-Process
[SERVER01]: PS C:> Get-EventLog -LogName System -Newest 10
[SERVER01]: PS C:> Exit-PSSession

Use credentials when accessing non-domain machines or when the current credentials are insufficient:

$cred = Get-Credential -UserName "CONTOSOadmin" -Message "Enter admin credentials"
Enter-PSSession -ComputerName SERVER01 -Credential $cred
Enter-PSSession -ComputerName 192.168.1.50 -Credential (Get-Credential) -Authentication Negotiate

Running Commands on Multiple Remote Servers

Invoke-Command runs commands on one or more remote computers. This is the most powerful remoting command for automation. Run a command on multiple servers simultaneously:

$servers = "Server01","Server02","Server03","Server04"
Invoke-Command -ComputerName $servers -ScriptBlock {
    [PSCustomObject]@{
        Computer = $env:COMPUTERNAME
        CPU = (Get-CimInstance Win32_Processor).LoadPercentage
        FreeMemoryGB = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory/1MB, 2)
        UptimeDays = [math]::Round((Get-Date - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime).TotalDays, 1)
        PendingReboot = Test-Path "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionComponent Based ServicingRebootPending"
    }
} | Format-Table -AutoSize

Pass parameters to remote scripts using ArgumentList or $using scope modifier:

$logPath = "C:LogsAppLog.txt"
$maxLines = 50
Invoke-Command -ComputerName Server01 -ScriptBlock {
    Get-Content $using:logPath -Tail $using:maxLines
}

Persistent Sessions with New-PSSession

New-PSSession creates a persistent connection that can be reused for multiple commands, which is more efficient than creating a new connection for each command when working extensively with a remote server:

$session = New-PSSession -ComputerName Server01 -Credential (Get-Credential)
Invoke-Command -Session $session -ScriptBlock {Get-Service | Where-Object Status -eq Running}
Invoke-Command -Session $session -ScriptBlock {Get-HotFix | Sort-Object InstalledOn | Select-Object -Last 5}
Enter-PSSession -Session $session
# [Server01]: PS> ...interactive work...
# [Server01]: PS> Exit-PSSession
Remove-PSSession $session

Manage multiple persistent sessions efficiently:

$sessions = New-PSSession -ComputerName Server01,Server02,Server03
Get-PSSession | Format-Table Id, ComputerName, State, Availability
Invoke-Command -Session $sessions -ScriptBlock {Install-WindowsFeature RSAT-DNS-Server}
$sessions | Remove-PSSession

Copying Files Over PowerShell Remoting

Copy-Item with the -ToSession or -FromSession parameter copies files over an existing PSSession without requiring shared network paths:

$session = New-PSSession -ComputerName Server01
Copy-Item -Path "C:ScriptsDeployScript.ps1" -ToSession $session -Destination "C:Scripts"
Copy-Item -Path "C:Reports" -ToSession $session -Destination "C:RemoteReports" -Recurse -Force
Copy-Item -Path "C:LogsErrorLog.txt" -FromSession $session -Destination "C:LocalLogs"
Remove-PSSession $session

Configuring WinRM for Workgroup Environments

In workgroup environments where Kerberos is not available, configure WinRM to use HTTPS and certificate-based authentication or add the remote server to the TrustedHosts list. The TrustedHosts setting should be used carefully as it bypasses mutual authentication:

Set-Item WSMan:localhostClientTrustedHosts -Value "192.168.1.50,192.168.1.51,webserver01" -Force
Get-Item WSMan:localhostClientTrustedHosts
# For all workgroup machines (less secure, use only in isolated test environments)
Set-Item WSMan:localhostClientTrustedHosts -Value "*" -Force

Connect to a workgroup machine:

$cred = New-Object PSCredential(".Administrator", (ConvertTo-SecureString "LocalAdminPass!" -AsPlainText -Force))
Enter-PSSession -ComputerName 192.168.1.50 -Credential $cred -Authentication Basic

Configuring WinRM via Group Policy

Deploy WinRM configuration enterprise-wide via Group Policy to ensure consistent remoting settings across all Windows Server 2019 machines. Key GPO settings under Computer Configuration > Administrative Templates > Windows Components > Windows Remote Management (WinRM):

Allow remote server management through WinRM: Enabled with IPv4 filter * (or restrict to management subnet). Turn On Compatibility HTTP Listener: Enabled. Allow Basic authentication: Disabled (use Kerberos instead). Allow unencrypted traffic: Disabled.

gpupdate /force
winrm get winrm/config/service
Test-WSMan -ComputerName Server01 -Credential (Get-Credential)

Security Best Practices for PowerShell Remoting

Harden PowerShell Remoting to prevent misuse. Enable PowerShell Script Block Logging and Module Logging to capture all remote commands executed. Restrict WinRM to management subnets via firewall rules. Use HTTPS for cross-domain remoting. Constrain remoting endpoints to limit which commands can be run remotely:

Set-PSSessionConfiguration -Name Microsoft.PowerShell -SecurityDescriptorSddl "O:NSG:BAD:P(A;;GA;;;BA)(A;;GA;;;RM)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)"
Set-Item WSMan:localhostServiceAuthBasic -Value $false
Set-Item WSMan:localhostServiceAuthKerberos -Value $true
Set-Item WSMan:localhostServiceAllowUnencrypted -Value $false
Restart-Service WinRM

Audit remoting connections by enabling the Microsoft-Windows-WinRM/Operational event log and reviewing event IDs 6 (WSMan session create), 8 (WSMan activity), and 11 (WSMan session close):

Get-WinEvent -LogName "Microsoft-Windows-WinRM/Operational" -MaxEvents 20 | Select-Object TimeCreated, Id, Message | Format-List