Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Archives
May 26, 2026

A NetAdapter Statistics Solution

In this issue:

  • Get-NetAdapter
  • Get-NetAdapterStatistics
  • Write-Progress Children
  • Proof-Of-Concept
  • Capture Test
  • Creating a Function
    • Using a StopWatch
  • Show-NetAdapterStatistics
    • Information Statistics
  • 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-Progress for 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.

Get-NetAdapter
figure 1

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) }
My primary network adapter
figure 2

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:

Demo Write-Progress children
figure 3

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.

Proof-of-concept
figure 4

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.

Adpater Statistics Reporting
figure 5

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
Show-NetworkAdapterStatistics
figure 6

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.

(c) 2022-2025 JDH Information Technology Solutions, Inc. - all rights reserved
Don't miss what's next. Subscribe to Behind the PowerShell Pipeline:

Add a comment:

You're not signed in. Posting this comment will subscribe you to this newsletter with the email address you enter below.
Share this email:
Share on Facebook Share on LinkedIn Share on Threads Share on Reddit Share via email Share on Mastodon Share on Bluesky
GitHub
Bluesky
LinkedIn
Mastodon
jdhitsolutions.github.io
Powered by Buttondown, the easiest way to start and grow your newsletter.