Skip to content

Remote Management

PowerShell Remoting lets you run commands on one or more remote computers over WinRM (Windows Remote Management) — or SSH on non-Windows systems.


Enabling Remoting

On Windows (WinRM)

# On the target machine (requires elevation)
Enable-PSRemoting -Force

# Verify WinRM is running
Get-Service WinRM

# Test from the client
Test-WSMan -ComputerName server01

On Linux/macOS (SSH remoting, PS 7+)

No special server setup is needed — use standard OpenSSH. Enable SSH on the target, then:

Enter-PSSession -HostName ubuntu01 -UserName alice -SSHTransport

Running Commands Remotely: Invoke-Command

Invoke-Command is the primary tool for one-shot remote execution:

# Run on one computer
Invoke-Command -ComputerName server01 -ScriptBlock { Get-Process }

# Run on multiple computers simultaneously
Invoke-Command -ComputerName server01, server02, server03 -ScriptBlock {
    [PSCustomObject]@{
        Computer = $env:COMPUTERNAME
        Uptime   = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
    }
}

# Pass variables into the remote script block
$svcName = "Spooler"
Invoke-Command -ComputerName server01 -ScriptBlock {
    param ($name)
    Get-Service -Name $name
} -ArgumentList $svcName

# Use $using: to reference local variables (PS 3+)
$svcName = "Spooler"
Invoke-Command -ComputerName server01 -ScriptBlock {
    Get-Service -Name $using:svcName
}

# Run as a background job
$job = Invoke-Command -ComputerName server01 -ScriptBlock { Start-Sleep 10; "Done" } -AsJob
Receive-Job $job -Wait

Interactive Remote Sessions: Enter-PSSession

For an interactive shell on a remote computer:

# Connect
Enter-PSSession -ComputerName server01

# You are now in a remote session:
# [server01]: PS C:\Users\alice\Documents>

# Exit the session
Exit-PSSession

Persistent Sessions: New-PSSession

Persistent sessions avoid the overhead of creating a new connection for each command:

# Create sessions
$s1 = New-PSSession -ComputerName server01
$s2 = New-PSSession -ComputerName server02

# Use a session
Invoke-Command -Session $s1 -ScriptBlock { Get-Process }

# Interactive session
Enter-PSSession -Session $s1

# Import a module from a remote session
Import-PSSession $s1 -Module ActiveDirectory

# See open sessions
Get-PSSession

# Close a session
Remove-PSSession $s1

# Close all sessions
Get-PSSession | Remove-PSSession

Running Scripts Remotely

# Copy a script and run it
Copy-Item .\deploy.ps1 \\server01\c$\Scripts\

Invoke-Command -ComputerName server01 -FilePath .\deploy.ps1

# Or inline the script content
$script = Get-Content .\deploy.ps1 -Raw
Invoke-Command -ComputerName server01 -ScriptBlock ([scriptblock]::Create($script))

SSH Remoting (PS 7+)

# Connect interactively via SSH
Enter-PSSession -HostName linuxbox -UserName alice -SSHTransport

# Run a command
Invoke-Command -HostName linuxbox -UserName alice -ScriptBlock { uname -a }

# Key-based authentication (recommended)
Invoke-Command -HostName linuxbox -KeyFilePath ~/.ssh/id_rsa -UserName alice -ScriptBlock { hostname }

# Connect to multiple Linux hosts
Invoke-Command -HostName 'web01','web02','web03' -UserName deploy -ScriptBlock {
    systemctl status nginx
}

Working with Credentials

$cred = Get-Credential

# Use credentials for remoting
New-PSSession -ComputerName server01 -Credential $cred
Invoke-Command -ComputerName server01 -Credential $cred -ScriptBlock { whoami }

# Use different credentials per host
$creds = @{
    'server01' = Get-Credential -Message "Credentials for server01"
    'server02' = Get-Credential -Message "Credentials for server02"
}

foreach ($srv in $creds.Keys) {
    Invoke-Command -ComputerName $srv -Credential $creds[$srv] -ScriptBlock { hostname }
}

Practical Recipes

Parallel health check across servers

$servers = 'web01','web02','db01','db02','app01'

$results = Invoke-Command -ComputerName $servers -ScriptBlock {
    $os  = Get-CimInstance Win32_OperatingSystem
    $cpu = Get-CimInstance Win32_Processor
    [PSCustomObject]@{
        Computer = $env:COMPUTERNAME
        OS       = $os.Caption
        Uptime   = [math]::Round(((Get-Date) - $os.LastBootUpTime).TotalHours, 1)
        CPU      = $cpu.Name
        FreeRAM  = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
        DiskFree = [math]::Round((Get-Volume C).SizeRemaining / 1GB, 1)
    }
} -ErrorAction SilentlyContinue

$results | Sort-Object Computer | Format-Table -AutoSize

Deploy a configuration file to many servers

$servers = Get-Content .\servers.txt
$localFile = ".\config\app.conf"

foreach ($srv in $servers) {
    try {
        Copy-Item $localFile "\\$srv\c$\App\app.conf" -Force
        Invoke-Command -ComputerName $srv -ScriptBlock { Restart-Service AppService }
        Write-Host "[OK] $srv updated" -ForegroundColor Green
    } catch {
        Write-Warning "[FAIL] $srv failed: $_"
    }
}

Collect event log errors from multiple servers

$servers = 'web01','web02','app01'

Invoke-Command -ComputerName $servers -ScriptBlock {
    Get-WinEvent -FilterHashtable @{
        LogName   = 'Application'
        Level     = 2
        StartTime = (Get-Date).AddHours(-24)
    } -MaxEvents 5 -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, Id, Message,
        @{Name='Server'; Expression={ $env:COMPUTERNAME }}
} | Sort-Object TimeCreated -Descending | Format-Table Server, TimeCreated, Id, Message -Wrap

Remoting Tips

Firewall and port

WinRM uses port 5985 (HTTP) and 5986 (HTTPS). Ensure these are open on firewalls between the client and server.

CredSSP for double-hop scenarios

If the remote session needs to connect to a third server (e.g., a file share), you may need CredSSP or Kerberos delegation to pass credentials through.

JEA — Just Enough Administration

For production environments, use JEA to constrain what commands remote users can run, which roles they run as, and log all activity. See New-PSSessionConfigurationFile.