I hope you've been trying your hand at the scripting challenges being posted on the Iron Scripter website. The challenges are designed for individuals to do on their own to build up their PowerShell scripting skills. A few weeks ago, a challenge was posted to create a network monitoring tool using PowerShell and the Write-Progress cmdlet. I thought I'd share my notes on the challenge and some of the code I came up with.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Intermediate Challenge
The first step was to identify the necessary performance counters for Get-Counter.
$counters = "\Network interface(*ethernet*)\Bytes sent/sec",
"\Network interface(*ethernet*)\Bytes received/sec",
"\Network interface(*ethernet*)\Bytes total/sec"
With these, I could use Get-Counter to retrieve the data.
Get-Counter -Counter $counters -MaxSamples 10 -SampleInterval 1 -ov d |
Select-object -ExpandProperty CounterSamples | Select-Object -Property Timestamp,
@{Name="Computername";Expression = {[regex]::Match($_.path, "(?<=\\\\)\w+").Value.toUpper() }},
@{Name = "Counter"; Expression = { ($_.path -split "\\")[-1]}},
@{Name = "Network"; Expression = {$_.instancename}},
@{Name = "Count"; Expression = {$_.cookedValue}}
I'm parsing out values from the counter sample property.
If I wanted to take this a step further I could define the output as a custom object and create a format ps1xml file to clean up the display.
Advanced
The advanced challenge was to create a visual tool using Write-Progress. The cmdlet lets you have multiple progress bars in the same window. You can reference them by an ID number. This meant I had to divide up the counters.
Get-Counter -Counter $counters -MaxSamples 30 -SampleInterval 1 |
ForEach-Object {
$network = $_.countersamples[0].InstanceName
$computername = [regex]::Match($_.CounterSamples[0].path, "(?<=\\\\)\w+").Value.toUpper()
$time = $_.timestamp
$total = $_.countersamples.where( {$_.path -match "total"})
$totalKbps = $total.cookedValue*0.008
if ($totalKbps -gt 100) {
$totalPct = 100
}
else {
$totalPct = $totalKbps
}
$sent = $_.countersamples.where( {$_.path -match "sent"})
$sentKbps = $sent.cookedValue*0.008
if ($sentKbps -gt 100) {
$sentPct = 100
}
else {
$sentPct = $sentKbps
}
$rcvd = $_.countersamples.where( {$_.path -match "received"})
$rcvdKbps = $rcvd.cookedValue*0.008
if ($rcvdKbps -gt 100) {
$rcvdPct = 100
}
else {
$rcvdPct = $rcvdKbps
}
Write-Progress -Activity "[$time] $computername : $network" -status "Total Kbps $totalKbps" -id 1 -PercentComplete $totalpct
Write-Progress -Activity " " -status "Send Kbps $sentKbps" -id 2 -PercentComplete $sentpct
Write-Progress -Activity " " -status "Received Kbs $rcvdKbps" -id 3 -PercentComplete $rcvdpct
}
Another bonus element was to format the data as Kbps. As far as I know, if I multiply the byte value by 0.008 I will get what I want. I also had to take into account the length of the progress bar since it obviously can't go above 100. Thus the display is relative and not a true graph.
In testing this, I found that Write-Progress in Windows PowerShell seems to have a display bug that doesn't produce exactly what I have in mind. But it works just fine in PowerShell 7.
Get-NetworkPerformance
I put all of this together into a PowerShell function.
Function Get-NetworkPerformance {
[cmdletbinding(DefaultParameterSetName = "sample")]
[alias("gnp")]
Param(
[Parameter(HelpMessage = "Specify a computer to connect to.")]
[string]$Computername = $env:computername,
[Parameter(HelpMessage = "Use the network description from Get-NetAdapter")]
[string]$NetworkInterface = "*ethernet*",
[Parameter(ParameterSetName = "continuous")]
[switch]$Continuous,
[Parameter(Mandatory, ParameterSetName = "sample")]
[int64]$MaxSamples,
[Parameter(Mandatory, ParameterSetName = "sample")]
[Int32]$SampleInterval,
[switch]$Passthru
)
Write-Verbose "Starting $($MyInvocation.MyCommand)"
Write-Verbose "Detected parameter set $($pscmdlet.ParameterSetName)"
Write-Verbose "Validating network interface"
#updated 12 Oct 2021 to make sure only a single network adapter is selected
$c = (Get-NetAdapter -InterfaceDescription $NetworkInterface -CimSession $Computername | Where-Object { $_.status -eq 'up' }).count
if ($c -gt 1) {
Throw "You must specify a single network interface."
}
Write-Verbose "Verified a single network interface"
#save the current progressbar setting
$bg = $host.PrivateData.progressBackgroundColor
#the networking counters
$counters = "\Network interface($NetworkInterface)\Bytes sent/sec",
"\Network interface($NetworkInterface)\Bytes received/sec",
"\Network interface($NetworkInterface)\Bytes total/sec"
$PSBoundParameters.Add("counter", $counters)
if ($PSBoundParameters.ContainsKey("Passthru")) {
[void]( $PSBoundParameters.Remove("passthru"))
}
if ($PSBoundParameters.ContainsKey("NetworkInterface")) {
[void]( $PSBoundParameters.Remove("NetworkInterface"))
}
Write-Verbose "Passing these parameters to Get-Counter:`n $($PSBoundParameters | Out-String)"
Try {
Get-Counter @PSBoundParameters -OutVariable pass -ErrorAction Stop |
ForEach-Object {
$network = $_.countersamples[0].InstanceName
$computername = [regex]::Match($_.CounterSamples[0].path, "(?<=\\\\)\w+").Value.toUpper()
$time = $_.timestamp
$total = $_.countersamples.where( { $_.path -match "total" })
$totalKbps = $total.cookedValue * 0.008
#adjust the progressbar color
if ($totalKbps -ge 150) {
$host.PrivateData.progressBackgroundColor = "Red"
}
else {
$host.PrivateData.progressBackgroundColor = $bg
}
if ($totalKbps -gt 100) {
$totalPct = 100
}
else {
$totalPct = $totalKbps
}
$sent = $_.countersamples.where( { $_.path -match "sent" })
$sentKbps = $sent.cookedValue * 0.008
if ($sentKbps -gt 100) {
$sentPct = 100
}
else {
$sentPct = $sentKbps
}
$rcvd = $_.countersamples.where( { $_.path -match "received" })
$rcvdKbps = $rcvd.cookedValue * 0.008
if ($rcvdKbps -gt 100) {
$rcvdPct = 100
}
else {
$rcvdPct = $rcvdKbps
}
Write-Progress -Activity "[$time] $computername : $network" -Status "Total Kbps $totalKbps" -Id 1 -PercentComplete $totalpct
Write-Progress -Activity " " -Status "Send Kbps $sentKbps" -Id 2 -PercentComplete $sentpct
Write-Progress -Activity " " -Status "Received Kbs $rcvdKbps" -Id 3 -PercentComplete $rcvdpct
}
} #Try
Catch {
Write-Warning "Failed to get performance counters. $($_.Exception.message)"
}
if ($passthru -AND $pass) {
Write-Verbose "Passing results to the pipeline"
$pass
}
#restore the progressbar value
$host.PrivateData.progressBackgroundColor = $bg
Write-Verbose "Ending $($MyInvocation.MyCommand)"
}
The function offers flexibility and ease of use.
The function has room for improvement. For one, there's no error handling. Get-Counter should really be in a Try/Catch block. This version doesn't handle alternate credentials for remote computers. Nor does it dynamically change the progress background color. I should be able to do that by dynamically changing the value of $host.PrivateData.ProgressBackgroundColor.
I even learned a few new things in solving this challenge, which is really the whole point. I hope you'll try your hand at the challenges. There are tests for every skill level. Good luck.
Nice function ! I would consider adding it in my PS Toolbox that I’m currently building 🙂
I really appreciate the different techniques and new approaches in each post of the blog ! Thanks !