Pinging with PowerShell

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.

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 use Add-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.