Remote Execution
PowerShell remote execution (PowerShell Remoting) allows you to run commands and scripts on remote computers across the network. It's built on the Windows Remote Management (WinRM) service and Web Services for Management (WS-Management) protocol, providing secure and efficient remote administration capabilities.
Table of Contents
- Overview
- Prerequisites
- Basic Remote Execution
- PowerShell Sessions
- Advanced Remote Execution
- Security Considerations
- Troubleshooting
- Best Practices
- Cross-Platform Remoting
Overview
PowerShell Remoting enables administrators to:
- Execute commands on remote computers
- Manage multiple computers simultaneously
- Transfer files and data between systems
- Run background jobs on remote machines
- Establish persistent connections for ongoing administration
Key Components
- WinRM (Windows Remote Management): The underlying service that handles remote connections
- PSSession: Persistent connections to remote computers
- PowerShell Direct: Direct connection to Hyper-V virtual machines
- SSH-based Remoting: Cross-platform remoting using SSH protocol
Prerequisites
Windows Prerequisites
Before using PowerShell Remoting, ensure the following requirements are met:
- WinRM Service: Must be running on target computers
- Firewall Configuration: Appropriate firewall rules must be in place
- Authentication: Proper credentials and authentication methods
- Network Connectivity: Target computers must be reachable
Enable PowerShell Remoting
# Enable PowerShell Remoting on local computer (Run as Administrator)
Enable-PSRemoting -Force
# Enable remoting with public network profile (if needed)
Enable-PSRemoting -Force -SkipNetworkProfileCheck
# Quick configuration for domain environments
winrm quickconfig
Configure Trusted Hosts (for Workgroup environments)
# Add specific computers to trusted hosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "Computer1,Computer2" -Force
# Add all computers in domain (use with caution)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*.contoso.com" -Force
# Add computers by IP range
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.1.*" -Force
# View current trusted hosts
Get-Item WSMan:\localhost\Client\TrustedHosts
Basic Remote Execution
Invoke-Command
The Invoke-Command
cmdlet is the primary method for executing commands on remote computers.
# Execute a simple command on a remote computer
Invoke-Command -ComputerName "Server01" -ScriptBlock { Get-Process }
# Execute command with credentials
$Credential = Get-Credential
Invoke-Command -ComputerName "Server01" -Credential $Credential -ScriptBlock {
Get-Service | Where-Object { $_.Status -eq "Running" }
}
# Execute on multiple computers
$Servers = "Server01", "Server02", "Server03"
Invoke-Command -ComputerName $Servers -ScriptBlock {
Get-WmiObject -Class Win32_OperatingSystem | Select-Object PSComputerName, Caption, Version
}
# Execute a local script on remote computers
Invoke-Command -ComputerName $Servers -FilePath "C:\Scripts\SystemInfo.ps1"
Passing Parameters to Remote Scripts
# Using -ArgumentList
Invoke-Command -ComputerName "Server01" -ScriptBlock {
param($ServiceName, $Action)
if ($Action -eq "Start")
{
Start-Service -Name $ServiceName
}
elseif ($Action -eq "Stop")
{
Stop-Service -Name $ServiceName
}
Get-Service -Name $ServiceName
} -ArgumentList "Spooler", "Start"
# Using variables in script block (PowerShell 3.0+)
$ServiceName = "Spooler"
$Action = "Restart"
Invoke-Command -ComputerName "Server01" -ScriptBlock {
Restart-Service -Name $using:ServiceName -PassThru
}
Background Jobs with Remote Execution
# Start background jobs on remote computers
$Job = Invoke-Command -ComputerName $Servers -ScriptBlock {
Get-EventLog -LogName System -Newest 100
} -AsJob -JobName "SystemLogRetrieval"
# Check job status
Get-Job -Name "SystemLogRetrieval"
# Receive job results
$Results = Receive-Job -Job $Job
$Results | Group-Object PSComputerName
PowerShell Sessions
Creating and Managing Sessions
PowerShell Sessions (PSSessions) provide persistent connections to remote computers, offering better performance for multiple operations.
# Create a single session
$Session = New-PSSession -ComputerName "Server01"
# Create multiple sessions
$Servers = "Server01", "Server02", "Server03"
$Sessions = New-PSSession -ComputerName $Servers
# Create session with specific configuration
$SessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck
$Session = New-PSSession -ComputerName "Server01" -SessionOption $SessionOption
# Create session with different credentials
$Credential = Get-Credential
$Session = New-PSSession -ComputerName "Server01" -Credential $Credential
Using Sessions
# Execute commands in existing sessions
Invoke-Command -Session $Session -ScriptBlock { Get-ComputerInfo }
# Execute multiple commands in the same session context
Invoke-Command -Session $Session -ScriptBlock {
$ErrorActionPreference = "Stop"
Import-Module ActiveDirectory
$Users = Get-ADUser -Filter * -Properties LastLogonDate
$Users | Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-30) }
}
# Copy files to/from sessions
Copy-Item -Path "C:\LocalFile.txt" -Destination "C:\RemoteFile.txt" -ToSession $Session
Copy-Item -Path "C:\RemoteFile.txt" -Destination "C:\LocalCopy.txt" -FromSession $Session
Session Management
# View all sessions
Get-PSSession
# View session information
$Session | Format-List *
# Test session connectivity
Test-WSMan -ComputerName $Session.ComputerName
# Disconnect sessions (keep alive on remote computer)
Disconnect-PSSession -Session $Session
# Reconnect to disconnected sessions
$DisconnectedSessions = Get-PSSession -ComputerName "Server01" -State Disconnected
Connect-PSSession -Session $DisconnectedSessions
# Remove sessions when done
Remove-PSSession -Session $Sessions
Advanced Remote Execution
Interactive Remote Sessions
# Enter interactive session (similar to RDP for PowerShell)
Enter-PSSession -ComputerName "Server01"
# Inside the remote session
[Server01]: PS C:\> Get-Location
[Server01]: PS C:\> Get-Process notepad
[Server01]: PS C:\> Exit-PSSession
# Enter session using existing PSSession
Enter-PSSession -Session $Session
Remote Session Configuration
# Create custom session configuration
Register-PSSessionConfiguration -Name "CustomConfig" -StartupScript "C:\Scripts\Startup.ps1"
# Connect using custom configuration
$Session = New-PSSession -ComputerName "Server01" -ConfigurationName "CustomConfig"
# View available session configurations
Get-PSSessionConfiguration
# Remove custom configuration
Unregister-PSSessionConfiguration -Name "CustomConfig"
Fan-Out Remoting
# Execute commands on multiple computers simultaneously
$Computers = Get-Content "C:\ServerList.txt"
$Results = Invoke-Command -ComputerName $Computers -ScriptBlock {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
FreeSpace = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='C:'" |
Select-Object -ExpandProperty FreeSpace
LastBootTime = (Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime
Uptime = (Get-Date) - [Management.ManagementDateTimeConverter]::ToDateTime(
(Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime)
}
} -ThrottleLimit 50
$Results | Export-Csv "C:\SystemReport.csv" -NoTypeInformation
Workflow-Based Remoting
# PowerShell Workflow for parallel execution
workflow Get-SystemInformation
{
param([string[]]$Computers)
foreach -parallel ($Computer in $Computers)
{
InlineScript
{
$ComputerInfo = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $using:Computer
$OSInfo = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $using:Computer
[PSCustomObject]@{
ComputerName = $using:Computer
Model = $ComputerInfo.Model
TotalMemory = [math]::Round($ComputerInfo.TotalPhysicalMemory / 1GB, 2)
OSVersion = $OSInfo.Caption
LastBootTime = $OSInfo.LastBootUpTime
}
}
}
}
# Execute workflow
$ServerList = "Server01", "Server02", "Server03", "Server04", "Server05"
$WorkflowJob = Get-SystemInformation -Computers $ServerList -AsJob
$Results = Receive-Job -Job $WorkflowJob -Wait
Security Considerations
Authentication Methods
# Kerberos authentication (default in domain)
$Session = New-PSSession -ComputerName "Server01" -Authentication Kerberos
# NTLM authentication
$Session = New-PSSession -ComputerName "Server01" -Authentication Negotiate
# Certificate-based authentication
$Session = New-PSSession -ComputerName "Server01" -CertificateThumbprint "CERT_THUMBPRINT"
# Explicit credentials
$SecurePassword = Read-Host "Enter Password" -AsSecureString
$Credential = New-Object System.Management.Automation.PSCredential("Domain\Username", $SecurePassword)
$Session = New-PSSession -ComputerName "Server01" -Credential $Credential
Secure Configuration
# Configure HTTPS for WinRM
winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname="server01.contoso.com"; CertificateThumbprint="CERT_THUMBPRINT"}
# Use HTTPS in PowerShell remoting
$SessionOption = New-PSSessionOption -UseSSL
$Session = New-PSSession -ComputerName "Server01" -SessionOption $SessionOption
# Configure authentication requirements
Set-WSManInstance -ResourceURI winrm/config/service -ValueSet @{Auth_Certificate=$true}
Constrained Endpoints
# Create constrained endpoint configuration
$ConfigurationPath = "C:\PSConfigurations\LimitedEndpoint.pssc"
New-PSSessionConfigurationFile -Path $ConfigurationPath -SessionType RestrictedRemoteServer -ExecutionPolicy Restricted -LanguageMode ConstrainedLanguage -VisibleCmdlets "Get-*", "Measure-*" -VisibleFunctions "Test-Connection"
# Register the configuration
Register-PSSessionConfiguration -Name "LimitedEndpoint" -Path $ConfigurationPath -Force
# Connect to constrained endpoint
$Session = New-PSSession -ComputerName "Server01" -ConfigurationName "LimitedEndpoint"
Troubleshooting
Common Issues and Solutions
# Test WinRM connectivity
Test-WSMan -ComputerName "Server01"
# Test PowerShell remoting connectivity
Test-NetConnection -ComputerName "Server01" -Port 5985 # HTTP
Test-NetConnection -ComputerName "Server01" -Port 5986 # HTTPS
# Check WinRM configuration
winrm get winrm/config
# View WinRM listeners
winrm enumerate winrm/config/listener
# Check PowerShell execution policy on remote computer
Invoke-Command -ComputerName "Server01" -ScriptBlock { Get-ExecutionPolicy }
# Verify remote computer is in trusted hosts (for workgroup)
Get-Item WSMan:\localhost\Client\TrustedHosts
Diagnostic Commands
# Enable WinRM logging
wevtutil set-log Microsoft-Windows-WinRM/Operational /enabled:true
# View WinRM logs
Get-WinEvent -LogName "Microsoft-Windows-WinRM/Operational" -MaxEvents 50
# Test session configuration
Test-PSSessionConfigurationFile -Path $ConfigurationPath
# Get detailed error information
$Error[0] | Format-List * -Force
# Network troubleshooting
function Test-RemotingConnectivity
{
param([string]$ComputerName)
$Results = @{}
# Test basic connectivity
$Results.PingTest = Test-Connection -ComputerName $ComputerName -Count 1 -Quiet
# Test WinRM ports
$Results.HTTPPort = Test-NetConnection -ComputerName $ComputerName -Port 5985 -InformationLevel Quiet
$Results.HTTPSPort = Test-NetConnection -ComputerName $ComputerName -Port 5986 -InformationLevel Quiet
# Test WinRM service
try
{
$Results.WSManTest = Test-WSMan -ComputerName $ComputerName -ErrorAction Stop
$Results.WSManStatus = "Success"
}
catch
{
$Results.WSManStatus = $_.Exception.Message
}
# Test PowerShell remoting
try
{
$TestResult = Invoke-Command -ComputerName $ComputerName -ScriptBlock { $env:COMPUTERNAME } -ErrorAction Stop
$Results.PSRemotingTest = "Success - Connected to $TestResult"
}
catch
{
$Results.PSRemotingTest = $_.Exception.Message
}
return [PSCustomObject]$Results
}
# Use diagnostic function
Test-RemotingConnectivity -ComputerName "Server01"
Best Practices
Performance Optimization
# Use sessions for multiple operations
$Session = New-PSSession -ComputerName "Server01"
# Batch multiple commands in single Invoke-Command
Invoke-Command -Session $Session -ScriptBlock {
$OS = Get-WmiObject -Class Win32_OperatingSystem
$CS = Get-WmiObject -Class Win32_ComputerSystem
$Disk = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='C:'"
[PSCustomObject]@{
OSVersion = $OS.Caption
Memory = [math]::Round($CS.TotalPhysicalMemory / 1GB, 2)
DiskFree = [math]::Round($Disk.FreeSpace / 1GB, 2)
}
}
# Use appropriate ThrottleLimit for fan-out operations
Invoke-Command -ComputerName $LargeServerList -ScriptBlock { Get-Service } -ThrottleLimit 32
Error Handling
# Robust error handling for remote operations
function Invoke-RemoteCommandSafely
{
param(
[string[]]$ComputerName,
[scriptblock]$ScriptBlock,
[pscredential]$Credential
)
$Results = @()
foreach ($Computer in $ComputerName)
{
try
{
$Result = Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -Credential $Credential -ErrorAction Stop
$Results += [PSCustomObject]@{
ComputerName = $Computer
Status = "Success"
Result = $Result
Error = $null
}
}
catch
{
$Results += [PSCustomObject]@{
ComputerName = $Computer
Status = "Failed"
Result = $null
Error = $_.Exception.Message
}
}
}
return $Results
}
Resource Management
# Always clean up sessions
try
{
$Sessions = New-PSSession -ComputerName $Servers
# Perform remote operations
$Results = Invoke-Command -Session $Sessions -ScriptBlock {
# Remote commands here
}
}
finally
{
# Ensure sessions are removed
if ($Sessions)
{
Remove-PSSession -Session $Sessions
}
}
# Use session timeout to prevent orphaned sessions
$SessionOption = New-PSSessionOption -IdleTimeout 3600000 # 1 hour
$Session = New-PSSession -ComputerName "Server01" -SessionOption $SessionOption
Cross-Platform Remoting
SSH-Based Remoting
PowerShell Core supports SSH-based remoting for cross-platform scenarios.
# Install PowerShell remoting over SSH (Windows)
# First install OpenSSH client feature
# Create SSH-based session to Linux
$Session = New-PSSession -HostName "ubuntu-server" -UserName "admin"
# Connect to Linux system
Invoke-Command -Session $Session -ScriptBlock {
Get-Process | Where-Object { $_.CPU -gt 10 }
}
# SSH with key-based authentication
$Session = New-PSSession -HostName "ubuntu-server" -UserName "admin" -KeyFilePath "C:\Users\Admin\.ssh\id_rsa"
Linux PowerShell Remoting
# Install PowerShell on Linux (Ubuntu/Debian example)
wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell
# Configure SSH for PowerShell remoting
sudo nano /etc/ssh/sshd_config
# Add: Subsystem powershell /usr/bin/pwsh -sshs -NoLogo -NoProfile
# Restart SSH service
sudo systemctl restart sshd
Practical Examples
System Administration Tasks
# Deploy software across multiple servers
$Servers = Get-Content "C:\ServerList.txt"
$SoftwarePath = "\\FileServer\Software\Application.msi"
$InstallResults = Invoke-Command -ComputerName $Servers -ScriptBlock {
param($MSIPath)
try
{
$Process = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$MSIPath`" /quiet /norestart" -Wait -PassThru
if ($Process.ExitCode -eq 0)
{
"Installation successful"
}
else
{
"Installation failed with exit code: $($Process.ExitCode)"
}
}
catch
{
"Installation error: $($_.Exception.Message)"
}
} -ArgumentList $SoftwarePath
$InstallResults | Export-Csv "C:\InstallationResults.csv" -NoTypeInformation
Health Monitoring
# Comprehensive health check across servers
function Get-ServerHealthReport
{
param([string[]]$ComputerName)
$HealthData = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
# CPU Usage
$CPU = Get-WmiObject -Class Win32_Processor | Measure-Object -Property LoadPercentage -Average
# Memory Usage
$Memory = Get-WmiObject -Class Win32_OperatingSystem
$MemoryUsage = [math]::Round((($Memory.TotalVisibleMemorySize - $Memory.FreePhysicalMemory) / $Memory.TotalVisibleMemorySize) * 100, 2)
# Disk Usage
$Disks = Get-WmiObject -Class Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
[PSCustomObject]@{
Drive = $_.DeviceID
UsedPercent = [math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 2)
FreeGB = [math]::Round($_.FreeSpace / 1GB, 2)
}
}
# Service Status
$CriticalServices = "Spooler", "BITS", "WinRM", "EventLog"
$ServiceStatus = Get-Service -Name $CriticalServices | Select-Object Name, Status
# Return consolidated data
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
CPUUsage = $CPU.Average
MemoryUsage = $MemoryUsage
DiskInfo = $Disks
ServiceStatus = $ServiceStatus
LastBootTime = (Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime
Timestamp = Get-Date
}
}
return $HealthData
}
# Generate health report
$Servers = "Server01", "Server02", "Server03"
$HealthReport = Get-ServerHealthReport -ComputerName $Servers
$HealthReport | ConvertTo-Json -Depth 3 | Out-File "C:\HealthReport.json"