PowerShell Remoting

This entry is part 10 of 12 in the series Learn PowerShell

In this episode of the Learn PowerShell series we’ll explore PowerShell remoting capabilities. We’ve seen in previous episodes how PowerShell is a powerful tool when managing a local device. Now we’ll greatly expand that capability by discovering how to manage hundreds or even thousands of devices with PowerShell.

This episode contains a lot of code examples that you may want to refer back to. You can reference them easily on the Learn PowerShell Examples GitHub page.

Video

If you prefer video format over written documentation, I discuss this topic in the following TechThoughts video:

Understanding PowerShell Remoting Requirements

This topic is a bit different from previous episodes because it requires knowledge of several concepts beyond PowerShell. Establishing a connection from one device to another engages a lot of system technologies. An understanding of basic network protocols, ports, firewalls, listeners, and server management is assumed. Remote access using PowerShell also engages various authentication technologies: Default, Basic, Digest, NTLM, Kerberos, Negotiate, and CredSSP.

To further complicate matters there are many possible connection scenarios. Each requires a slightly different remote connection configuration!

PowerShell Remoting Connection Scenarios

This post can’t cover every scenario, as that would greatly increase the length of this topic. Instead, we’ll cover some of the more common configuration scenarios. Links are provided at the bottom of this article to get you started with other configuration requirements.

Don’t feel put off by the complexity of this topic. If you are wondering why you would invest a lot of effort into learning and configuring your environment for remote PowerShell connections, remember this: PowerShell remoting is one of the most powerful capabilities that PowerShell has to offer. It can be used to audit, push changes, or query thousands of devices. This is the capability that turns a 3-day task into a 15 minute task. It will take effort to learn and implement, but PowerShell remoting is well worth investing in.

PowerShell remoting is one of the most powerful capabilities that PowerShell has to offer.

Windows PowerShell Remoting

PowerShell remote access on Windows uses the following technologies:

ComputerName

There are many native cmdlets (even more with modules) that support the ComputerName parameter. These allow you to query and interact with remote machines using PowerShell without the need for additional configuration. Per Microsoft documentation varying communication protocols are utilized to accomplish this which are typically enabled by default on Microsoft Operating systems.

# Per PowerShell documentation you can find a list of cmdlets that support ComputerName with the following:
Get-Command | Where-Object { $_.parameters.keys -contains "ComputerName" -and $_.parameters.keys -notcontains "Session"}

This means in most cases credentials and network access to the device should be sufficient to interact with it. Lets start by first creating a credential object using Get-Credential.

# this will prompt you to enter your access credentials. the creds will be securely stored in the variable
$creds = Get-Credential

With our credentials provided, we can now use cmdlets with the ComputerName previously identified to perform remote tasks:

# restart a single computer
Restart-Computer -ComputerName RemoteDevice -Credential $creds

# restart several computers
Restart-Computer -ComputerName RemoteDevice1, RemoteDevice2, RemoteDevice3 -Credential $creds

# restart an entire list of computers
$devices = Get-Content -Path C:\listOfServers.txt
Restart-Computer -ComputerName $devices -Credential $Creds -Force

WinRM

Windows remote access in PowerShell is typically accomplished by establishing a session via WinRM. WinRM must be installed and configured on both your management device as well as all devices you will be managing remotely.

For WinRM to operate two things are required:

  1. The WinRM service needs to be running
  2. A WinRM listener needs to be configured

WinRM listens on a local port that you configure. By default, WinRM uses the following ports:

  • Port 5985 for http connections (trusted network – typical for domain to domain)
  • Port 5986 for https connections (untrusted network – also required in some non-domain scenarios)

Keep in mind that multiple listeners can be configured on a single device.

Configuring WinRM

To easily configure WinRM on a device with all defaults you can simply run the following:

winrm quickconfig

This command performs the following:

  • Starts WinRM service and sets startup type to Automatic
  • Configures a default listener for HTTP for any IP address
  • Configures Windows Firewall to open WinRM ports

Remember, WinRM is configured as both client and a service. The client is the settings used when the device establishes an outgoing session to a remote device. The service settings configure the listeners that respond to potential clients trying to establish a remote connection.

You can see both the client and service settings of your device by running the following from an administrator PowerShell window:

winrm get winrm/config/client
winrm get winrm/config/service

Testing WinRM

Verify WinRM is configured and responding using Test-WSMan. This cmdlet is van verify WinRM on both a local and remote device.

#verify that WinRM is setup and configured locally
Test-WSMan

#verify that WinRM is setup and responding on a remote device
#you must specify the authentication type when testing a remote device.
#if you are unsure about the authentication, set it to Negotiate
$credential = Get-Credential
Test-WSMan RemoteDeviceName -Authentication Negotiate -Credential $credential

You can also leverage the following commands to verify basic network connectivity and open ports:

#verify local device is listening on WinRM port
Get-NetTCPConnection -LocalPort 5985

#verify a remote device is listening on WinRM port
Test-NetConnection -Computername 192.168.34.13 -Port 5985

Connecting via WinRM

Interactive Session

Enter-PSSession establishes an interactive remote session to the specified device. Interactive sessions are for connecting to a single remote device and running commands remotely in the console. This is a great alternative to RDP. You’ll notice that once you establish a connection that the command prompt will change.

[RemoteDeviceName]: PS C:\ >

This indicates that commands typed and run are running in the context of the remote device, and no longer you local device. You can continue to run commands in the remote context of the interactive session. When you want to drop back to your local device, simply type Exit.

#establish an interactive remote session
$credential = Get-Credential
Enter-PSSession -ComputerName RemoteDeviceName -Credential $credential

PowerShell session (PSSession)

Use New-PSSession to establish remote PowerShell sessions to one, or many remote devices.

#basic session opened to remote device
$session = New-PSSession -ComputerName RemoteDeviceName -Credential domain\user

#session opened to device over SSL
$credential = Get-Credential
$sessionHTTPS = New-PSSession -ComputerName RemoteDeviceName -Credential $credential -UseSSL

#establish sessions to multiple devices
$credential = Get-Credential
$multiSession = New-PSSession -ComputerName RemoteDeviceName1,RemoteDeviceName2, RemoteDeviceName3 -Credential $credential

#establish session to an entire list of devices
$devices = Get-Content -Path C:\listOfServers.txt
$credential = Get-Credential
$multiSession = New-PSSession -ComputerName $devices -Credential $credential

#session opened with advanced session options configured
$sessionOptions = New-PSSessionOption -SkipCNCheck -SkipCACheck -SkipRevocationCheck
$advancedSession = New-PSSession -ComputerName 10.0.3.27 -Credential user -UseSSL -SessionOption $so

In and of itself this session doesn’t accomplish anything other than opening a connection to the remote device(s). Use Invoke-Command to actually send commands to those established sessions.

This is hugely empowering as it enables you remotely perform tasks on devices throughout your environment. Take note of the ScriptBlock parameter. Provide code here to audit, query, or make changes to your entire fleet.

#get the number of CPUs for each remote device
Invoke-Command -Session $sessions -ScriptBlock {(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors}

#get the amount of RAM for each remote device
Invoke-Command -Session $sessions -ScriptBlock {Get-CimInstance Win32_OperatingSystem | Measure-Object -Property TotalVisibleMemorySize -Sum | ForEach-Object {[Math]::Round($_.sum/1024/1024)}}

#get the amount of free space on the C: drive for each remote device
Invoke-Command -Session $sessions -ScriptBlock {
    $driveData = Get-PSDrive C | Select-Object Used,Free
    $total = $driveData.Used + $driveData.Free
    $calc = [Math]::Round($driveData.Free / $total,2)
    $perFree = $calc * 100
    return $perFree
}

#stop the BITS service on all remote devices
Invoke-Command -Session $sessions -ScriptBlock {Stop-Service BITS -Force}

Invoke-Command runs commands in parallel, so keep that in mind. If you send a command to 500 servers, data that comes back will not be in the same order. For example, if server 25 in the list is the first to process the scriptblock, it will be the first to return results.

#alternative to running Invoke-Command in parallel
#foreach forces sequential connection and return for each server in the list
#establish session to an entire list of devices
$devices = Get-Content -Path C:\listOfServers.txt
$credential = Get-Credential
foreach ($server in $devices) {
    Invoke-Command -ComputerName $server -ScriptBlock {$env:COMPUTERNAME} -Credential $credential
}

PowerShell Remoting on Linux

Linux and MacOS leverage SSH for PowerShell remote access. Each distro will require slightly different specific steps.

  • Install OpenSSH
  • Configure ssh_config for password authentication and PowerShell subsystem
  • Restart sshd service

Reference PowerShell remoting over SSH for other step-by-step instructions for SSH configuration. I have included a basic example below from that link:

#install openssh
sudo apt install openssh-client
sudo apt install openssh-server

#Edit the sshd_config file at location /etc/ssh
#Make sure password authentication is enabled:
PasswordAuthentication yes

#Add a PowerShell subsystem entry:
Subsystem powershell /usr/bin/pwsh -sshs -NoLogo -NoProfile

#Optionally, enable key authentication:
PubkeyAuthentication yes

#Restart the sshd service.
sudo service sshd restart

Once configured you can establish sessions and execute remote commands. The SSHTransport parameter indicates that the remote connection will use SSH.

#establish an interactive session to a remote Linux device
$session = New-PSSession -HostName RemoteDevice -UserName user -SSHTransport
Enter-PSSession $session

#execute commmands on a remote Linux device
$session = New-PSSession -HostName RemoteDevice -UserName user -SSHTransport
Invoke-Command -Session $session -ScriptBlock {Get-Process}

PowerShell Remoting: Advanced / Other

As shown in the graphic above there are many different connection scenarios. Most of these require additional configuration steps beyond what has been covered. Some only support SSL, which will require you to setup certificates. Others require adding remote devices to your TrustedHosts. Consult the Additional Reading section at the bottom of this post for more information.

Linux to Windows and Windows to Linux

It is definitely possible to configure remote PowerShell access from Windows to Linux and vice-versa. Unfortunately, the complexities of that configuration are beyond the scope of this course. There are several published articles covering various methods, but I have not found one today that covers every scenario well.

Closing Example

Use the closing example to audit remote devices for:

  • ComputerName
  • Number of CPUs
  • Amount of Memory
  • Free space on C:

The $servers variable could easily be replaced with a different source of devices in your environment. Note that this does not run Invoke-Command in a loop. This means commands will be run in parallel against all remote devices. As a result, information will come back in an unpredictable order, which is why we are also capturing the computer name.

ScriptBlock contains the logic that will be executed on the remote device. In this case we will be gathering some basic information about the device and storing it in a custom PowerShell object for return. There are a wide variety of reasons that this might encounter errors during execution. Remote devices might be:

  • Down for maintenance
  • Not configured for remote access
  • Credentials not permitted for remote access
  • You can not reach the device due to network configuration

To handle this in a large audit sweep like this closing example, we will send PSRemotingTransportException errors to a custom error variable, connectErrors. At the end of execution, you will have two final variables:

  • $remoteResults: Will contain the results of all remote executions
  • $remoteFailures: Will contain a list of all devices that failed to establish a remote connection to
#declare servers we will connect to remotely
$servers = 'Server1','Server2','Server3','Server4'
#capture credentials used for remote access
$creds = Get-Credential

#declare array to hold remote command results
$remoteResults = @()

#declare a splat for our Invoke-Command parameters
$invokeSplat = @{
    ComputerName  = $servers
    Credential    = $creds
    ErrorVariable = 'connectErrors'
    ErrorAction   = 'SilentlyContinue'
}

#execute remote command with splatted parameters.
#store results in variable
#errors will be stored in connectErrors
$remoteResults = Invoke-Command @invokeSplat -ScriptBlock {
    $obj = [PSCustomObject]@{
        Name      = $env:COMPUTERNAME
        CPUs      = "-------"
        Memory    = "-------"
        FreeSpace = "-------"
    }
    $obj.CPUs = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
    $obj.Memory = Get-CimInstance Win32_OperatingSystem `
        | Measure-Object -Property TotalVisibleMemorySize -Sum `
        | ForEach-Object { [Math]::Round($_.sum / 1024 / 1024) }
    $driveData = Get-PSDrive C | Select-Object Used, Free
    $total = $driveData.Used + $driveData.Free
    $calc = [Math]::Round($driveData.Free / $total, 2)
    $obj.FreeSpace = $calc * 100
    return $obj
}

#capture any connection errors
$remoteFailures = $connectErrors.CategoryInfo `
    | Where-Object {$_.Reason -eq 'PSRemotingTransportException'} `
    | Select-Object TargetName,@{n = 'ErrorInfo'; E = {$_.Reason} }

Additional Reading

Series Navigation<< PowerShell Errors and Exceptions HandlingPowerShell Scripts >>

2 Responses

  1. Olivier says:

    HI
    Im’ just discover and reading this post. Very instructive : simple and didactic. A ref for the beginners.
    Just a small remark : don’t use backtick in your code. The best practices are : forget backtick and put the pipe at the end of the line. PS will know that the command continu on the next line.
    Regards and Merry Christmas
    Olivier

  2. Manas Dash says:

    I had go through all the steps as described above. But unable to take remote PowerShell from remote system on my local PowerShell.
    But I can able to take command console by using Microsoft PSEXEC tool. Kindly help me.

Leave a Reply

Your email address will not be published. Required fields are marked *