Create Advanced Ping Class in PowerShell
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
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:
1 | Class AdvancedPing |
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
.
16 | AdvancedPing([string]$target, [string]$packets, [string]$duration) |
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.
119 | hidden _AddProperty([string]$propName) |
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.
89 | hidden _SetTarget([string]$target) |
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.
66 | hidden _SendPing([object]$pinger, [object]$pingOptions, [object]$buffer) |
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 😉