PowerShell ships with Test-Connection
to ping computers but this command lacks important parameters such as a timeout. Let’s create a better ping command with a timeout parameter.
Why Timeout Matters
Testing online status via ping (ICMP) is a standard procedure, however the part that costs most time is when computers are not responding to ICMP requests, either because they are offline or block ICMP.
It just takes too long for most commands to run into a timeout. In most networks, if there is no response within a few seconds, it is evident that the system isn’t there (or willing to answer).
That’s why a ping command needs to have a configurable timeout. Let’s create our own Test-PSOnePing
command!
Creating a Better Ping Command
Test-PSOnePing
takes one or more computer names (or IP addresses) plus optionally a timeout. It the sequentially pings the computers and returns the online status. This line pings three computers and waits a maximum of 2000 milliseconds for a response:
Test-PSOnePing -ComputerName 127.0.0.1, microsoft.com, powershell.one -Timeout 2000
You can also pipe computer names:
'127.0.0.1', 'microsoft.com', 'powershell.one' | Test-PSOnePing -Timeout 2000
Get-ADComputer -Filter * | Select-Object -ExpandProperty Name | Test-PSOnePing -Timeout 2000
Implementing Test-PSOnePing
Here is the implementation of Test-PSOnePing
:
function Test-PSOnePing
{
<#
.SYNOPSIS
Sends a ping (ICMP) to a computer
.DESCRIPTION
Sends a ping (ICMP) to a computer
.EXAMPLE
Test-PSOnePing -ComputerName 127.0.0.1, microsoft.com, powershell.one -Timeout 2000
Pings three computers with a maximum timeout of 2000 milliseconds
.EXAMPLE
'127.0.0.1', 'microsoft.com', 'powershell.one' | Test-PSOnePing -Timeout 2000
Pings three computers with a maximum timeout of 2000 milliseconds
.EXAMPLE
Get-ADComputer -Filter * | Select-Object -ExpandProperty Name | Test-PSOnePing -Timeout 2000
Pings all computers received from Get-ADComputer with a maximum timeout of 2000 milliseconds
Module "ActiveDirectory" required for Get-ADComputer
.LINK
https://powershell.one/tricks/network/ping
#>
param
(
# Computername or IP address to ping
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]
$ComputerName,
# Timeout in milliseconds
[int]
[ValidateRange(100,50000)]
$Timeout = 2000
)
begin
{
$Online = @{
Name = 'Online'
Expression = { $_.Status -eq 'Success' }
}
$obj = New-Object System.Net.NetworkInformation.Ping
}
process
{
$ComputerName |
ForEach-Object {
$obj.Send($_, $timeout) |
Select-Object -Property $Online, Status, Address |
Add-Member -MemberType NoteProperty -Name Name -Value $_ -PassThru
}
}
}
Learning Points
The code illustrates many important PowerShell techniques:
- Making a parameter pipeline-aware
- Allowing multiple values for a parameter
- Polishing the results and changing or appending properties
- Returning values from a PowerShell function
Let’s take a detailed look at each of them:
Pipeline-Awareness
Test-PSOnePing
is pipeline-aware so you can pipe computer names directly into the command:
'127.0.0.1', 'microsoft.com', 'powershell.one' | Test-PSOnePing -Timeout 2000
- The parameter ComputerName is decorated with the attribute value ValueFromPipeline so any pipeline input goes to this parameter.
- The code to be executed for each incoming pipeline object is placed inside a process{} block.
- For performance reasons, any code that needs to execute only once can be placed inside a begin{} block. Since the System.Net.NetworkInformation.Ping object is reusable, it is created in the begin{} block. The same goes for the property description in $online that also is reusable (see below what it does).
Allowing Multiple Computer Names
Test-PSOnePing
also supports computer names to be a string array. This way, you can submit a comma-separated list of computer names to -ComputerName:
Test-PSOnePing -ComputerName 127.0.0.1, microsoft.com, powershell.one -Timeout 2000
- Parameter ComputerName uses the type [String[]] instead of [String], allowing multiple strings.
- $ComputerName is piped to Foreach-Object inside the process{}-block to process each submitted computer name individually.
Appending Output Object
The Send() method returns a rich object with the ping results. Select-Object picks only the relevant properties to return to the user: Status, Address.
In addition, I wanted two more properties to be emitted:
-
Online should be $true when there was a response from the computer, else $false. This information can be taken from the original information by checking whether Status equals “Success”. That’s why I implemented it via a Calculated Property based on a hashtable definition.
-
Name should contain the name of the tested computer because
Test-PSOnePing
can test multiple computers, so the results should be attributable to the computer being tested. The original information returned by Send() does not contain this information so I decided to useAdd-Member
to add a completely new property with information taken from other variables.
Adding Calculated Properties
A calculated property is defined by a hashtable with the keys Name (name of the property) and Expression (value of the property) and works with Select-Object
. The calculated property Online is defined like this:
$Online = @{
Name = 'Online'
Expression = { $_.Status -eq 'Success' }
}
$_ is a special variable referring to the original object sent to Select-Object
, in our case the object emitted from Send(). So the expression is checking whether the original Status property contains the value Success or not.
Adding Properties via Add-Member
Since Test-PSOnePing
can work on multiple computers, it is important that the output contains information about the computer that was tested. The original output from Send() is missing this information.
Add-Member
can add new properties to an object, so the code is adding the new property Name and assigning a value of $**. **$ in this context happens to be the computer name being tested. Generally, Add-Member
can assign any value to your new property and is not limited to special variables such as $_.
Add-Member -MemberType NoteProperty -Name Name -Value $_ -PassThru
By default, Add-Member
silently adds new properties to objects. Since we want to return the object, -PassTru tells Add-Member
to emit the extended object.
Return Value
The return value of a PowerShell function is always any information that isn’t either assigned to a variable or piped to another command.
So the return value of Test-PSOnePing
is the result of this line (since it is neither assigned to a variable or piped to another consuming command):
$obj.Send($_, $timeout) |
Select-Object -Property $Online, Status, Address |
Add-Member -MemberType NoteProperty -Name Name -Value $_ -PassThru
The return value of a PowerShell function is everything that is “left behind” (not assigned to a variable or consumed by a command). Since the line above is embedded in a loop, and the loop runs for every computer the user has specified, Test-PSOnePing
can produce any number of results - one per computer.
When there is more than one result, PowerShell automatically wraps the results in an array.
Get Source Code
Don’t bother copy&pasting the source code. Test-PSOnePing
is part of the PowerShell module PSOneTools. Simply install it from the PowerShellGallery to immediately start using Test-PSOnePing
in your PowerShell:
Install-Module -Name PSOneTools -Scope CurrentUser -Force
If you’d like to take a look at the source code, or feel intreagued to file a bug report or help improve the code, simply visit the PSOneTools GitHub repository.