Tuesday, May 5, 2015

PowerShell: Error Handling When Accessing Remote Systems


Let me just get this out of the way: I love PowerShell.

Today I’d like to cover issues when accessing remote systems. Server unreachable. Firewall port not open. PowerShell Remoting not enabled. These are all issues that can throw wrenches into a script.

Server Unreachable

To check if a server is reachable, you can ping it. Or you can use the PowerShell cmdlet Test-Connection. In these examples, $Hostname contains the DNS name of the system.

Test-Connection -ComputerName $Hostname -Count 1 -Quiet

I recommend using full parameter names in a script to decrease any ambiguity and to increase the chances that the script will be compatible with later versions of PowerShell. Here we are testing the connection to system $Hostname, sending a single ping (-Count 1), and suppressing output to just either $true or $false (-Quiet). This allows for evaluation in an if-else block.

if (Test-Connection -ComputerName $Hostname -Quiet -Count 1) {
   Write-Output "$Hostname reachable"
}
else {
   Write-Output "$Hostname unreachable"
}

Firewall Port Not Open or PowerShell Remoting Not Enabled

What if the system is reachable and pinging, but the script is still throwing errors when trying to connect? Could be a firewall port issue. Or possibly PowerShell remoting is disabled. Regardless of why the script is throwing errors when connecting to the system, accounting for the problem will make life easier when it does happen.

This one is going to be a bit more complex.

First, the cmdlet: Invoke-Command. While it may sound like something out of a text-based role-playing game, Invoke-Command in reality allows remote execution of code and programs. You can run things on one system from another system. Again, $Hostname is simply the DNS name of the system. $MyBlockOfCode is whatever code that needs to be executed on the remote machine.

Invoke-Command -ComputerName $Hostname -ScriptBlock $MyBlockOfCode

But after executing the cmdlet, PowerShell throws an error about being unable to connect to the destination. You know that the server is online from using Test-Connection, so Test-Connection wasn’t able to catch this error. What you need is a try-catch block. You “try” this or that command, and if it works, great! If not, it will “catch” the error for you.

try {
   Invoke-Command -ComputerName $Hostname `
       -ScriptBlock $MyBlockOfCode -ErrorAction Stop
   Write-Output "$Hostname connection successful"
}
catch {
   Write-Output "$Hostname connection unsuccessful"
}

-ErrorAction Stop is so that any errors with Invoke-Command are treated as terminating errors rather than non-terminating errors. You don’t really need to know the why. Just that try-catch only will catch terminating errors.

Altogether we have the following code:
if (Test-Connection -ComputerName $Hostname -Quiet -Count 1) {
   try {
       Invoke-Command -ComputerName $Hostname `
           -ScriptBlock $MyBlockOfCode -ErrorAction Stop
       Write-Output "$Hostname connection successful"
   }
   catch {
       Write-Output "$Hostname connection unsuccessful"
   }
}
else {
   Write-Output "$Hostname unreachable"
}

I took out the line Write-Output "$Hostname connection successful" since it is redundant. If it doesn’t say unreachable, it is reachable and you’ll get “connection successful” or “connection unsuccessful” anyway.

And there you have some basic error handling for when you run into issues accessing remote systems.