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.