Recently, I had the problem of monitoring the latency of my internet connection to provide these information to my ISP for troubleshooting. As the standard ping class in .net and Test-NetConnection was too inflexible and had no timestamps, I wanted to create a PowerShell Script to provide these functionality. Classes were something I haven’t used in a while so I chose to create a PowerShell class called AdvancedPing.

Table of Contents

  1. Introduction
  2. Class variables
  3. Constructor
    1. _AddProperty method
    2. _SetTarget method
  4. Ping method
    1. _SendPing method
  5. Examples
    1. Example 1
    2. Example 2

Introduction

For this post I assume that the basics of PowerShell Classes are known.
Some good resources to get familiar with PowerShell Classes are

Lets start with what we want to achieve. We want to create a ping class that is capable of adjusting the following settings for ICMP packets:

  • packets to be send
  • duration of ping
  • giving hostname or ip address
  • size of ICMP packets in bytes
  • ttl
  • timeout for packets
  • interval of packets (every x ms)
  • dontFragment option
  • show timestamp

The logic should be that a user provides a packet and a duration variable which are then used to determine the end of the ping process. If max packets is reached before the time specified in duration is over, the ping process will end as well as when the duration is over, but the max packets value has not been reached.

Class variables

So we start with the class body and define the class variables we will need:

AdvancedPing.ps1#link
1
Class AdvancedPing
2
{
3
    hidden [string]$_packets
4
    hidden [string]$_duration
5
    hidden [string]$_hostName
6
    hidden [string]$_ip
7
    hidden [int]$_bytes = 64
8
    hidden [int]$_ttl = 57
9
    hidden [int]$_timeout = 120
10
    hidden [int]$_interval = 1000
11
    hidden [int]$_counter = 0
12
    hidden [int]$_success
13
    hidden [bool]$_dontFragment = $false
14
    hidden [bool]$_timestamp

Unfortunately, it’s not possible to use Access Modifiers like private, protected or internal in PowerShell Classes. The nearest we can get is with the hidden modifier. This will hide the class property from syntax completion and the Get-Member cmdlet, but with Get-Member -force or a direct access via ._property, it can still be accessed.
For the naming of class variables, we use the an underscore (_) like used in multiple programming languages including C# as coding style.
The following variables are initialized with default values:

  • $_bytes = 64 (bytes to send via ICMP)
  • $_ttl = 57 (Time to live/Maximum hops)
  • $_timeout = 120 (in ms)
  • $_interval = 1000 (in ms)
  • $_counter = 0 (packet counter)
  • $_dontFragment = $false (dontFragment option of Ping .NET class)

Constructor

Next we create the constructor. The constructor always has the same name as the class, so in our case it’s AdvancedPing.

AdvancedPing.ps1#link
16
   AdvancedPing([string]$target, [string]$packets, [string]$duration)
17
{
18
       $this._packets = $packets
19
       $this._duration = $duration
20
21
       # Adding getter and setter
22
23
       $properties = "timestamp", "ttl", "timeout", "bytes", "duration", "interval"
24
       foreach($property in $properties)
25
       {
26
           $this._AddProperty($property)
27
       }
28
29
       # Checking if target is DNS or IP address and resolve DNS name
30
31
       $this._SetTarget($target)
32
   }

First, we assign the values from the parameters to $this._packets and $this._duration = $duration.

Class methods and variables have to references by the $this. prefix, as long as they are not from within the same method scope.

Next, we setting getters and setters on the defined class variables in C# style. To understand what this does, we have to take a look at the _AddProperty method.

_AddProperty method

In PowerShell, the getters and setters are automatically generated for class variables. But as we don’t want the users to directly interact with the internal class variables, we made them hidden. Now to provide an intuitive way of getting and setting these variables, we create new properties on the class instance.

AdvancedPing.ps1#link
119
hidden _AddProperty([string]$propName)
120
{
121
    $property = new-object management.automation.PsScriptProperty $propName, {$propname = $propname; return $this."_$propName"}.GetNewClosure(), {param($value) $propname = $propname; $this."_$propName" = $value}.GetNewClosure()
122
    $this.psobject.properties.add($property)
123
}

What this basically does, is creating a new script property for the given parameter $propName. Then specifying $this._$propname as getter and $this."_$propName" = $value as a setter by using the variable $value, which will be provided by the user by writing e.g. $classInstance.property = 'test' where 'test' will be used as $value.
Also very important is to call the GetNewClosure() function on the scriptblocks. According to the Microsoft Docs, it “Returns a new scriptblock bound to a module. Any local variables in the callers context will be copied into the module.”.
Without GetNewClosure(), the variable $propName will just be inserted as a variable and not with the actual content that it contains.

_SetTarget method

After setting the script property for every class property that we want to expose, we also have to validation on the parameter $target.
$target could be an IP address or a DNS name. To check this, we first try to parse the content of $target and save the output in the $out variable of type System.Net.ipaddress.
If the parsing fails, we try to resolve the name by using the GetHostEntry method from the System.Net.Dns namespace.
If that fails as well, we throw an error and exit.

AdvancedPing.ps1#link
89
hidden _SetTarget([string]$target)
90
{
91
    
92
    $out = [System.Net.ipaddress]::None
93
94
    if ([System.Net.ipaddress]::TryParse($target, [ref]$out))
95
    {
96
        $this._ip = $target
97
        $this._hostname = $target
98
    }
99
    else 
100
    {
101
        try 
102
        {
103
            $resolvedName = [System.Net.Dns]::GetHostEntry($target)
104
            $this._ip = $resolvedName.AddressList[0]
105
            $this._hostname = $target
106
        }
107
        catch
108
        {
109
            $msg = $_.Exception.Message
110
            If ($msg -like "*No such host is known*") 
111
            {
112
                $msg = 'Cannot resolve DNS name {0}' -f $target
113
            }
114
            Write-Error $msg
115
        }
116
    }
117
}

If you want to provide multiple ways of instantiating your class, PowerShell classes support overloads. Just add another method also called AdvancedPing with different parameters to your class.

Ping method

The only method that should be used from a user is the ping method. This method is called without any additional parameters, as everything should be set and saved in class variables.
As I like the linux style syntax ping, we will create a $startMessage as it would appear at every start of a ping in Ubuntu for example.
After writing out that message, we have to create an instance of System.Net.NetworkInformation.Ping and System.Net.NetworkInformation.PingOptions. We need the PingOptions instance to be able to define ttl and dontFragment options.
In line 47 the $buffer variable of type byte array will be initialized with the length of $this._bytes.

To incorporate the duration variable, we create a new stopwatch instance and create a while loop to call the _SendPing method. To do this every x milliseconds as specified in the interval variable, we add a Start-Sleep -Milliseconds $this._interval at the end of the loop.

To also have linux style statistics, we just do some quick math in the formatting section and write the $statistics string to the console.

The ping process itself happens in the internal _SendPing method so let’s take a look at that one.

_SendPing method

The _SendPing method takes the already created instances in $pinger, $pingOptions and the initiated $buffer variable and uses them so send out ICMP packages.

AdvancedPing.ps1link
66
hidden _SendPing([object]$pinger, [object]$pingOptions, [object]$buffer)
67
{
68
    $reply = $pinger.send($this._ip, $this._timeout, $buffer, $pingOptions)
69
70
    if ($reply.Status -eq "Success")
71
    {
72
        $this._success += 1
73
        if ($this._timestamp)
74
        {
75
            $message = "[{0}] {1} bytes from {2}: icmp_seq={3} ttl={4} time={5} ms" -f [datetime]::Now, $this._bytes, $this._ip, $this._counter, $this._ttl, $reply.RoundtripTime
76
        }
77
        else 
78
        {
79
            $message = "{0} bytes from {1}: icmp_seq={2} ttl={3} time={4} ms" -f $this._bytes, $this._ip, $this._counter, $this._ttl, $reply.RoundtripTime
80
        }
81
        [System.Console]::WriteLine($message)
82
    }
83
    elseif ($reply.Status -eq "TimedOut") 
84
    {
85
        [System.Console]::WriteLine("Request timed out")
86
    }
87
}

All that already happens in line 68..
The following conditional statements evaluates how the “$_timestamp” switch is configured and adjusts the $message variable accordingly.
If the ICMP message times out, a simple "Request timed out" will be written to console.

Examples

See below for some example usage of the AdvancedPing class.

Example 1

In the following example, we create an instance of the AdvancedPing class and call the ping() method.

Example 2

In this example, we create an instance of the AdvancedPing class, adjusting some of the class variables and call the ping() method.

And thats everything! We have created a relatively simple class to provide some linux style ping functionality. You can extend and rewrite the class for your needs and don’t forget to check in frequently for my next post about an async PowerShell logging implementation 😉