Adding Trace Information to a PowerShell Ping Tool
In a previous article, I started us down a path of discovery and exploration. We have a task to create a ping sweep tool in PowerShell. My hope is that along the way you will learn a few new things. As I said last time, the journey is its own reward. If you missed the previous article, take a moment to get caught up. When we left off, we had a rudimentary PowerShell script that would ping a range of IP addresses.
PowerShell Ping Sweep Tool Article Series
- Building a Ping Sweep Tool with PowerShell
- PowerShell Ping Sweep Tool: Adding Parameter Validation
- Adding Trace Information to a PowerShell Ping Tool
- Identifying a Computer Name with a PowerShell Ping Sweep Tool
- Building a PowerShell Ping Sweep Tool: Adding a Port Check
Eventually, we will want the user to be able to specify the variable values as parameters, so let's look at these next. Although I have defined a subnet variable, I'm really not using it in the working code, where the $subnet variable is a placeholder for me. I could ask the user to enter a value like 172.16.30 and change the working code to this:
I think that's a bit awkward. Creating the IP address is okay, but asking the user for a network value of 172.16.30 is a bit odd, as we would normally consider it to be 172.16.30.0. Let's assume that is the type of value we expect. This is a good practice by the way: don't force the user to have to conform to your scripting needs. You should write scripts that fall within normal expectations. If $subnet is 172.16.30.0, how do I get rid of the last .0? There are a few different ways to achieve this, but let's try this.
I'm going to split the string into an array using the period as the delimiter. All we need are the first three elements of the array.
Finally, I can rejoin them using a period as the separator.
Great. This becomes a new variable.
And I can revise the code in my loop accordingly.
The next item of business is to make the output more meaningful. Right now my code is writing true or false to the pipeline. Yes, I'm using Write-Host so I can see what IP address is being tested, but otherwise I have no context for the results. Because Test-Connection with –Quiet is writing a Boolean value to pipeline, I can use an IF statement.
If Test-Connection is true, then PowerShell will display the IP address, otherwise I get nothing. Here's the current state of my script:
That's not too bad. If I want to test a different subnet or range of addresses, I have to modify the script. However, I can easily turn them into parameters because I have variables already defined.
I left in values as defaults if the user doesn't specify something else. The $base variable is not a parameter because it is derived from the $subnet parameter. Now I can test the script with different values. I prefer to test in the PowerShell console and not the ISE because of potential scope errors. The command used the default subnet value but different values for start and end. While there's nothing wrong with using this as a script, I think it gets a bit cumbersome when using it as part of a larger PowerShell expression. With that said, let's turn this into a function. We declare the function before the parameters:
And at the end of the script insert a closing }. Boom. You just made a basic PowerShell function. Before we go on, a quick word about naming. I believe it is a scripting best practice to use the standard verb-noun naming convention we have for all other PowerShell commands. The verb should be something you see when you run Get-Verb. I thought Test seemed the most appropriate. The noun should be the "thing" you doing something with. In our case, I am interpreting a ping sweep as testing a subnet. Therefore, I chose Subnet for the noun. If you think your noun is common enough that it might conflict with another cmdlet, then use a prefix with the noun. Most people use either their initials or company abbreviation. For example, if your function is doing some sort of test with a process, you might want to avoid using Test-Process and instead go with Test-JHProcess or Test-PetriProcess. Let's wrap up this stage of our journey with my function, such as it is now.
There are a few things I want to point out. First, I have assigned a type to all of the parameter values. This ensures that PowerShell will treat each value appropriately. This is another best practice. You'll also notice that my parameter names are meaningful and not cryptic like A and Z. While it doesn't apply in this scenario, if your function needs a parameter for something that is similar to other cmdlets, use the more commonly used parameter name. If your function needs a parameter to indicate the name of a computer, use Computername because that is used in many cmdlets. Don't go off on your own and create a parameter called Sys or Machine. It makes your command less discoverable and less intuitive. If in doubt, use Get-Command to see where your proposed parameter is being used.
To use the function in the console, you need to dot source the script. Then you can run it like any other command.
The last change I made was to include the [cmdletbinding()] attribute. By doing so, PowerShell will treat the function like a cmdlet which means among other things built-in support for -Verbose. I didn't like having Write-Host always cluttering the screen, so I changed that line to use Write-Verbose. Now when I run Test-Subnet and include –Verbose, I will see the verbose output. This is a great way of adding trace information to your function which can be invaluable when it comes time to troubleshoot or debug. We've come a long way today but I know there is more you can learn from this project, so I hope you'll watch for the next article.