A NetAdapter Statistics Solution
In this issue:
- Get-NetAdapter
- Get-NetAdapterStatistics
- Write-Progress Children
- Proof-Of-Concept
- Capture Test
- Creating a Function
- Show-NetAdapterStatistics
- Summary
Last month I gave you what was most likely one of my more complicated scripting challenges. The idea is to use native PowerShell commands for presentation. This is a theme that has held my interest of late. How can I use PowerShell to present information in a visually engaging manner?
For this project, I wanted you to display network adapter statistics using Write-Progress.
The primary challenge is to display statistics using
Write-Progressfor the primary adapter.o You can't display everything, but at least show the total number of bytes sent and received. Anything else you want to display is up to you. You should also show the computer name and the name of the network adapter. Create a command that will display the progress bar for a specified number of seconds. I suggest adding support for a refresh interval of at least one second.
Get-NetAdapter
The first step is to identify the network adapter you want to monitor. On Windows platforms we can use the Get-NetAdapter cmdlet.

You might want to find the primary network adapter which should be running and be a physical device.
Get-NetAdapter | where { $_.status -eq 'up' -and (-not $_.virtual) }

This is indeed the primary network adapter on my laptop. I need this information so that I can get network statistics.
Get-NetAdapterStatistics
The Get-NetAdapterStatistics command is very similar to Get-NetAdapter. I can pipe an adapter object to it.
PS C:\> Get-NetAdapter | where { $_.status -eq 'up' -and (-not $_.virtual) } | Get-NetAdapterStatistics
Name ReceivedBytes ReceivedUnicastPackets SentBytes
---- ------------- ---------------------- ---------
Wi-Fi 1404008633 550315 632599708
You can also specify an adapter by name or interface descripti
PS C:\> Get-NetAdapterStatistics -Name "wi-fi"
Name ReceivedBytes ReceivedUnicastPackets SentBytes
---- ------------- ---------------------- ---------
Wi-Fi 1404564284 550989 633183781
There are a variety of statistics to choose from. For the challenge, we only need sent and received bytes.
PS C:\> Get-NetAdapterStatistics -Name "wi-fi" | Select SystemName,SentBytes,ReceivedBytes
SystemName SentBytes ReceivedBytes
---------- --------- -------------
Cadenza 634464167 1404733531
This is the information I want to display. You could get by with a bare bones implementation simply by clearing the host.
do { Clear-Host ; Get-NetAdapterStatistics -Name "wi-fi" | Format-Table SystemName,SentBytes,ReceivedBytes ; Start-Sleep -seconds 5 } while ($True)
But we want something much nicer.
Write-Progress Children
The reason I suggested using Write-Progress is that you can use the progress bar as a graphing feature. And you can have a hierarchy of progress bars by assigning an ID value.
1..5 | Foreach-Object -begin {$a= 24;$b=35} -process {
Write-Progress -Activity "Main Activity" -id 1 -Status "Status"
Write-Progress -Activity "Child1 Activity" -ParentId 1 -id 2 -Status Foo -PercentComplete ($a+=3)
Write-Progress -Activity "Child2 Activity" -ParentId 1 -id 3 -Status Bar -PercentComplete ($b+=7)
Start-sleep -Seconds 2
}
If you don't loop and pause Write-Progress will run so fast that it won't display anything. You should see something like this:

There is a -CurrentOperation parameter you can use but it isn't displayed in PowerShell 7. Although you could always configure the $PSStyle settings.
PS C:\> $PSStyle.Progress
Style : `e[38;5;10m
MaxWidth : 120
View : Minimal
UseOSCIndicator : False
You can set $PSStyle.Progress.View to Classic to use the Windows Powershell progress style. I'll stick to the minimal view for now.
Proof-Of-Concept
Now that I have an idea of the syntax, I can mock-up a preview.
1..10 | foreach {
Write-Progress -Activity $env:computername -Id 1 -Status 'WIFI' -CurrentOperation (Get-Date)
$s = (Get-Random -min 100MB -Maximum 1GB) / 1mb
#scale the value which must be less than 100
$sPer = $s / 30
Write-Progress -Activity 'Sent Bytes' -ParentId 1 -Id 2 -Status "$s MB" -PercentComplete $sPer #-CurrentOperation "Sent"
$r = (Get-Random -min 100MB -Maximum 1GB) / 1mb
$rPer = $r / 30
Write-Progress -Activity 'Received Bytes' -ParentId 1 -Id 3 -Status "$r MB" -PercentComplete $rPer
#-CurrentOperation "Received Bytes"
Start-Sleep -Seconds 5
}
One thing to keep in mind is that the percentage value must not be greater to 100. This means I can't simply display the raw values. I'm scaling them with an arbitrary value of 30 which seemed to create a nice display.

The mock up is using placeholder values for the send and receive values.
Capture Test
The next step is to revise the code to use actual data.
$adapter = Get-NetAdapter | where { $_.status -eq 'up' -and (-not $_.virtual) }
1..20 | foreach -Begin { Clear-Host } -Process {
$stat = $adapter | Get-NetAdapterStatistics
#use description from status
$status = '[{0}] {1}' -f (Get-Date), $stat.Description
Write-Progress -Activity $adapter.SystemName -Id 1 -Status $status
[int]$sent = $stat.SentBytes /1mb
$sentPer = $sent / 20
Write-Progress -Activity 'Sent Bytes' -ParentId 1 -Id 2 -Status "$sent MB" -PercentComplete $sentPer
[int]$rcv = $stat.ReceivedBytes /1MB
$rcvPer = $rcv / 20
Write-Progress -Activity 'Received Bytes' -ParentId 1 -Id 3 -Status "$rcv MB" -PercentComplete $rcvPer
Start-Sleep -Seconds 5
}
I hope you recognize the value in the process I have been going through. Don't feel you have to start at the end.

Creating a Function
Now that I have the core code worked out, I can begin wrapping it up in a function. I need the user to be able to specify these parameters:
- the network adapter name
- the refresh interval between statistics gathering
- how long to display the information
- the computer name
Get-NetworkAdapterStatistics has a -CimSession parameter. The parameter works with only a computer name. I'm not building a production-level tool, so allowing the user to specify a remote computer by name is sufficient. For our purposes I'm not dealing with credentials on the remote machine.
I also want the user to able to indicate if they would like to clear the screen before displaying the progress report. From a design perspective, this is a toss-up. There's no reason the user can't run Clear-Host and then my function. Although, if I include the feature as part of my function, it becomes part of the command.
Using a StopWatch
In my test code I looped through using ForEach-Object. That won't work in the function since I'm giving the user duration parameter. In other words, I want the user to run the Get-NetAdapterStatistics and Write-Progress code for X number of seconds.
Do {
#code
} until (<$duration is exceeded>)
The best way to keep track of time is with a stopwatch.
$sw = [System.Diagnostics.Stopwatch]::new()
When the stopwatch is running, I can compare the ElapsedMilliseconds property to the duration value and exit the Do loop when the duration is met. This means I need to make sure that I convert the parameter value, which will be in seconds, to milliseconds.
$max = $Duration * 1000
I also wanted to give the user an option to run the monitor continuously. Instead of dealing with another parameter set, I'm allowing the user to use a value of -1 for the duration.
[Parameter(HelpMessage = 'Specify the duration in seconds. A value of -1 means run continuously. Use Ctrl+C to stop.')]
[ValidateScript({ ($_ -eq -1) -or ($_ -ge 10) }, ErrorMessage = 'Specify a value greater than 10 or -1 to run continuously.')]
[int]$Duration = 60
I'm already using the duration parameter to determine how long to run. Rather than add logic, I'm going to set the internal $max variable to a very large value.
if ($Duration -eq -1) {
#Technically this will time out after 11.13:46:40
$max = 1000000000
}
From a practical perspective, I'll consider this "continuous".
Show-NetAdapterStatistics
Here's my finished PowerShell function.
function Show-NetAdapterStatistics {
[cmdletbinding()]
[OutputType('none')]
[alias('snas')]
param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipelineByPropertyName,
HelpMessage = 'Specify the network adapter name'
)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[Parameter(HelpMessage = 'Specify the refresh interval in seconds')]
[ValidateRange(1, 300)]
[int]$Refresh = 10,
[Parameter(HelpMessage = 'Specify the duration in seconds. A value of -1 means run continuously. Use Ctrl+C to stop.')]
[ValidateScript({ ($_ -eq -1) -or ($_ -ge 10) }, ErrorMessage = 'Specify a value greater than 10 or -1 to run continuously.')]
[int]$Duration = 60,
[Parameter(
ValueFromPipelineByPropertyName,
HelpMessage = "Specify the name of a remote computer"
)]
[Alias("SystemName")]
[ValidateNotNullOrEmpty()]
[string]$Computername = $env:Computername,
[Parameter(HelpMessage = "Clear the screen before displaying the monitor. You will lose any displayed Verbose messages")]
[switch]$ClearHost
)
begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
#create a stop watch
$sw = [System.Diagnostics.Stopwatch]::new()
} #begin
process {
Write-Information $PSBoundParameters -tags runtime
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Analyzing network adapter $Name every $refresh seconds."
if ($Duration -eq -1) {
$msg = "[$((Get-Date).TimeOfDay) PROCESS] Running the monitor continuously. Use Ctr+C to stop"
#Technically this will time out after 11.13:46:40
$max = 1000000000
}
else {
$msg = "[$((Get-Date).TimeOfDay) PROCESS] Running the monitor for $(New-TimeSpan -Seconds $Duration)"
#this will be the comparison value for the stopwatch's elapsedMillisecond property
$max = $Duration * 1000
}
Write-Verbose $msg
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Starting the clock"
$sw.Start()
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Getting adapter statistics for $Name from $($Computername.ToUpper())"
$ClearHost ? (Clear-Host) : $null
do {
Try {
$stat = Get-NetAdapterStatistics -name $Name -CimSession $Computername -ErrorAction Stop
}
Catch {
write-Warning "Cannot find an adapter called $name on $Computername"
Break
}
#save each set of statistics to the information stream
#add a time stamp to each
$stat | Add-Member -MemberType NoteProperty -Name Date -Value (Get-Date) -PassThru | Write-Information -Tags data
#use description from status
$status = '[{0}] {1}' -f (Get-Date), $stat.Description
Write-Progress -Activity $stat.SystemName.ToUpper() -Id 1 -Status $status
$sent = $stat.SentBytes
$sentPer = $sent / 1mb / 20
Write-Progress -Activity 'Sent Bytes' -ParentId 1 -Id 2 -Status "$($sent/1mb -as [int]) MB" -PercentComplete $sentPer
$rcv = $stat.ReceivedBytes
$rcvPer = $rcv / 1mb / 20
Write-Progress -Activity 'Received Bytes' -ParentId 1 -Id 3 -Status "$($rcv/1mb -as [int]) MB" -PercentComplete $rcvPer
Start-Sleep -Seconds $Refresh
} until ($sw.ElapsedMilliseconds -ge $max)
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Stopping the clock"
$sw.Stop()
} #process
end {
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
} #end
}
The function includes my typical verbose messaging. I am also using the information stream to save runtime information and data. When the progress bar finishes, it is cleared from the screen. I thought it would be nice to save the statistics which I'm doing using Write-Information. One extra step is that I am adding a property with a timestamp value.
$stat | Add-Member -MemberType NoteProperty -Name Date -Value (Get-Date) -PassThru | Write-Information -Tags data
The user can pass adapter and computer names, or pipe output from Get-NetAdapter.
Get-NetAdapter -CimSession cadenza | where { $_.status -eq 'up' -and (-not $_.virtual)} | Show-NetAdapterStatistics -duration 300 -verbose -InformationVariable z

Information Statistics
I like using the information stream as a secondary source of information. I can use it for troubleshooting.
PS C:\> $z[0].MessageData
Key Value
--- -----
Duration 300
Verbose True
InformationVariable z
Computername Cadenza
Name Wi-Fi
I saved the statistics to the information variable so I can work with the data, even after the progress display is gone.
PS C:\> $z.MessageData | select -Skip 1 | select date,@{Name="SentMB";Expression={$_.SentBytes/1mb}},@{Name="RecvMB";Expression={$_.ReceivedBytes/1MB}}
Date SentMB RecvMB
---- ------ ------
5/22/2026 2:59:15 PM 642.21 1520.93
5/22/2026 2:59:25 PM 642.21 1520.93
5/22/2026 2:59:35 PM 642.21 1520.93
5/22/2026 2:59:45 PM 642.22 1520.94
5/22/2026 2:59:56 PM 642.26 1520.94
5/22/2026 3:00:06 PM 642.27 1520.96
...
PS C:\> $z.MessageData | Select -Skip 1 | Measure -Average -Property SentBytes,ReceivedBytes | Select Count,Property,@{Name="AvgMb";Expression={$_.average/1mb}}
Count Property AvgMb
----- -------- -----
30 SentBytes 642.93
30 ReceivedBytes 1521.25
Summary
How did you do? I hope you'll take the time to pick apart my solution and understand how it works. I gave you a secondary challenge using the pwshSpectreConsole module, but I'll save that for another article. In the mean time, keep an eye out for the next scripting challenge.
Add a comment: