Try/Catch or If/Else? Try/Catch or If/Else? powershell powershell

Try/Catch or If/Else?


try..catch is for handling terminating errors. Don't abuse it for status checks by forcing a check to fail hard when it doesn't need to. If you just want to test the availability of a system run Test-Connection with the parameter -Quiet as an if condition:

if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {  ...}

If you need to cascade multiple checks you could do so in a more readable manner by inverting the checks and returning with an appropriate message:

function Test-Multiple {  ...  if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {    return "Host $_ unavailable."  }  $pw = Read-Host -AsSecureString  if (-not (Test-UserCredentials -Username testuser -Password $pw)) {    return 'Login failed for user testuser.'  }  ...}

If you want the information about ping or login failures in log files you can just append it to the respective files:

function Test-Multiple {  ...  if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {    $_ | Add-Content 'C:\path\to\unavailable.log'    return  }  $pw = Read-Host -AsSecureString  if (-not (Test-UserCredentials -Username testuser -Password $pw)) {    $_ | Add-Content 'C:\path\to\login_failure.log'    return  }  ...}


Personally, I can't stand the behavior of Test-Connection. Throwing an exception when it doesn't successfully ping isn't the behavior I want. Like, ever. I understand why they did it that way, but it's not how I ever want a ping to work. Test-Path doesn't throw an exception when the path is invalid. It just returns false. Why is Test-Connection so unfriendly?

WMI allows you to capture the actual status code, and it also allows you to easily control the timeout so it will function much more quickly.

I tend to use this:

$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000";if ($Ping.StatusCode -eq 0) {    # Success}else {    # Failure}

If I actually want to decode the ping status code:

$StatusCodes = @{    [uint32]0     = 'Success';    [uint32]11001 = 'Buffer Too Small';    [uint32]11002 = 'Destination Net Unreachable';    [uint32]11003 = 'Destination Host Unreachable';    [uint32]11004 = 'Destination Protocol Unreachable';    [uint32]11005 = 'Destination Port Unreachable';    [uint32]11006 = 'No Resources';    [uint32]11007 = 'Bad Option';    [uint32]11008 = 'Hardware Error';    [uint32]11009 = 'Packet Too Big';    [uint32]11010 = 'Request Timed Out';    [uint32]11011 = 'Bad Request';    [uint32]11012 = 'Bad Route';    [uint32]11013 = 'TimeToLive Expired Transit';    [uint32]11014 = 'TimeToLive Expired Reassembly';    [uint32]11015 = 'Parameter Problem';    [uint32]11016 = 'Source Quench';    [uint32]11017 = 'Option Too Big';    [uint32]11018 = 'Bad Destination';    [uint32]11032 = 'Negotiating IPSEC';    [uint32]11050 = 'General Failure'    };$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000"$StatusCodes[$Ping.StatusCode];


You could do it like this:

if(Test-Connection -Computername $_ -Count 2 -ErrorAction 0 -Quiet) {    if(-not (Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString))) {        $err = "Access denied (Check User Permissions)"    }} else {    $err = "Unavailable (Host Offline or Firewall)"}if($err) {    Write-Warning "$computer - $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append}

I believe Test-Connection and Test-Credentials are meant to return $true or $false rather than an exception (if properly used), so you don't really need try/catch here.