I use PowerShell a lot. And I mean a lot. I’m a big fan of scripting and automation and anything that makes my development life easier. Where PowerShell really comes into it’s own is when you realise you have the full power of C# right there, unlike say with batch scripts. In this post I want to cover Remoting and how it has made my life easier.

First off you need to enable remoting on the target server(s). To do this, open a PowerShell console using the “Run as Administrator” option. Then run the following cmdlet

Enable-PSRemoting -Force

In a Domain controlled environment that is all you need to do. If however you want to connect two disparate Domains or your machine is part of a Workgroup, there is some additional setup related to the TrustedHosts but I won’t be covering that in this post. A good guide on how to get this setup and what to troubleshoot can be found here.

I have a standard layout for a basic remoting script that I use and over time it has been refined to the beast it is today. Let’s start with the core of script, assuming you have enabled remoting on your local machine.

1
2
3
4
5
6
7
8
[System.Management.Automation.Runspaces.PSSession]$session = New-PSSession -ComputerName $env:COMPUTERNAME

Invoke-Command -Session $session -ScriptBlock {
     Write-Host ("Remoted onto machine {0}..." -f $env:COMPUTERNAME)
}

Remove-PSSession -Session $session
$session = $null;

And that is the basics of PowerShell remoting! Let’s break it down:

  1. On line 1 we create a new session into the local machine. We don’t ask for credentals, so the account that is currently running this PowerShell console is used.
  2. On line 3 we use the Invoke-Command cmdlet with this session, using the -ScriptBlock parameter to inline what statements we would like to execute on the remote machine.
  3. On lines 7 and 8 we clean up the session into this machine as the WinRM service can only keep a limited number of sessions open (default is 5 per user).

If you run the above script on your local machine in an elevated PowerShell console, you should get some output like this:

Remoted onto machine JONATHANC-PC...

The next step is to get the correct credentials with which the session will be created. In my case, these scripts are run by Jenkins, so the credentials will be passed in as parameters. However for testing I find it convenient for the script to prompt me as needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Script parameters: $MachineName, $AskForCreds, $Username, $Password

[System.Management.Automation.PSCredential]$Credentials = $null;

if ($AskForCreds -eq $true) {
    $Credentials = Get-Credential -Message "Please enter your credentials to execute this script".
}
else {
    if ([String]::IsNullOrEmpty($Username) -or [String]::IsNullOrEmpty($Password)) {
        throw "Username or password cannot be null or empty."
    }

    $securePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force;

    $Credentials = New-Object System.Management.Automation.PSCredential ($Username, $securePassword);
}

if ($Credentials -eq $null) {
    throw "Credentials are invalid."
}

[System.Management.Automation.Runspaces.PSSession]$session = $null

if ($Credentials -ne $null) {
    $session = New-PSSession -ComputerName $MachineName -Credential $Credentials
}

Here we are prompting the user for the username and password if required, else an error is thrown if the credentials are missing. On line 6 the built in dialog will be displayed for the user to capture the credentials.

PowerShell Capture Credentials

The System.Management.Automation.PSCredential requires a secure string so on line 13 we create one from the plain text password. Then when the session is created on line 25 we can use the newly created Credentials object.

We can now enhance our initial core example with an expanded ScriptBlock, and more comprehensive error handling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$scriptBlock = {
    Write-Host ("Remoted onto machine {0}..." -f $env:COMPUTERNAME)

    Set-ExecutionPolicy Bypass -Scope Process

    # Perform a task on the remote machine...
}

try {
    Invoke-Command -Session $session -ScriptBlock $scriptBlock -ErrorAction Stop
    $global:LASTEXITCODE = Invoke-Command -ScriptBlock { $lastexitcode } -Session $session
}
Catch {
    Write-Error -Message ("Exception occurred running script block")
    $global:LASTEXITCODE = 1;
}
Finally {
    Remove-PSSession -Session $session
    $session = $null;
}

Something to note on line 10 is due to the way the Invoke-Command cmdlet works the output is immediately returned and is piped to the host. If you capture the results into a variable it will not show up in the console window immediately as the commands are executed, but you might still want to return some data or a status at the end of the script block. So in order to check if the set of commands in the script block was successful we can explicitly check the $lastexitcode variable on the remote server and return it to a local variable, as shown on line 11.

Another thing to note is on line 4 we set the Execution Policy to Bypass, which is useful if you plan on executing scripts sourced from files on the remote machine.

And there you have it, PowerShell Remoting in a nutshell! Next time I’ll try cover some more ideas in depth.