Table of Contents

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

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:

  1. WinRM Service: Must be running on target computers
  2. Firewall Configuration: Appropriate firewall rules must be in place
  3. Authentication: Proper credentials and authentication methods
  4. 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"

References