PowerShell Network Bandwidth Usage Report with WMI

Task Manager shows you a rough bandwidth total, but it cannot tell you which adapter is saturated on a multi-NIC server, or give you per-second averages over a meaningful sample window. Windows Performance Counters expose precise bytes-sent and bytes-received counters for every network interface, and PowerShell’s Get-Counter cmdlet can collect and average those counters across any number of remote servers. This guide builds a reusable bandwidth report you can run on-demand or feed into a scheduled monitoring script.
Quick Answer
Use Get-Counter with the \Network Interface(*)\Bytes Total/sec counter set, sample it multiple times, average the results, and convert to Mbps. Enumerate adapter names with Get-NetAdapter to build the correct counter paths, then export results to CSV.
Reading Network Interface Counters with Get-Counter
Performance counter paths for network interfaces follow the pattern \Network Interface(adapter name)\Bytes Total/sec. Adapter names in counter paths often differ from the friendly names shown in Device Manager — they use the description string from the driver and may include parentheses or extra characters. The safest way to get valid paths is to call Get-Counter -ListSet "Network Interface" and inspect the PathsWithInstances property.
# Discover all valid network counter paths on the local machine
$counterSet = Get-Counter -ListSet "Network Interface"
$counterSet.PathsWithInstances | Where-Object { $_ -like "*Bytes Total*" }
\\server01\Network Interface(Intel[R] Ethernet Connection I217-LM)\Bytes Total/sec
\\server01\Network Interface(Hyper-V Virtual Ethernet Adapter)\Bytes Total/sec
Those exact strings — spaces, brackets, and all — are what you pass to Get-Counter. Constructing them manually from Get-NetAdapter display names will fail for adapters with special characters in their description.
Capturing Bytes Sent and Received per Second
For directional reporting you need the Bytes Sent/sec and Bytes Received/sec counters rather than Bytes Total/sec. Collect both in a single Get-Counter call to keep the timestamps aligned.
$paths = (Get-Counter -ListSet "Network Interface").PathsWithInstances |
Where-Object { $_ -like "*Bytes Sent*" -or $_ -like "*Bytes Received*" }
# Collect 5 samples, 2 seconds apart
$samples = Get-Counter -Counter $paths -SampleInterval 2 -MaxSamples 5
Each element of $samples.CounterSamples has a CookedValue property representing bytes per second at that instant.
Averaging Over a Sample Window for Accuracy
A single sample can spike due to a burst of traffic. Averaging across a window of 5–10 samples gives a representative utilization figure. Group samples by counter path, then average the CookedValue for each group.
$report = $samples.CounterSamples |
Group-Object Path |
ForEach-Object {
$avg = ($_.Group | Measure-Object CookedValue -Average).Average
[PSCustomObject]@{
Adapter = ($_.Name -replace '.*\((.+)\)\\.*','$1')
Counter = ($_.Name -replace '.*\)\\','')
Avg_Mbps = [Math]::Round($avg * 8 / 1MB, 3)
}
}
$report | Sort-Object Adapter, Counter | Format-Table -AutoSize
Adapter Counter Avg_Mbps
------- ------- --------
Intel[R] Ethernet Connection I217-LM Bytes Received/sec 45.21
Intel[R] Ethernet Connection I217-LM Bytes Sent/sec 3.87
Hyper-V Virtual Ethernet Adapter Bytes Received/sec 0.12
Hyper-V Virtual Ethernet Adapter Bytes Sent/sec 0.09
Enumerating All Adapters with Get-NetAdapter
Get-NetAdapter provides the link speed for each adapter, which lets you calculate utilization as a percentage of available bandwidth — far more useful than raw Mbps when comparing a 1 Gbps and a 10 Gbps NIC on the same server. Join the report to adapter data on the adapter description or interface index.
Running on Multiple Remote Servers
Wrap the counter collection in Invoke-Command to gather data from several servers simultaneously. Because Get-Counter runs locally on the target, the samples reflect that machine’s actual counters without WMI overhead.
$servers = "srv-web01", "srv-db01", "srv-app01"
$allResults = Invoke-Command -ComputerName $servers -ScriptBlock {
$paths = (Get-Counter -ListSet "Network Interface").PathsWithInstances |
Where-Object { $_ -like "*Bytes Total/sec*" }
$s = Get-Counter -Counter $paths -SampleInterval 2 -MaxSamples 5
$s.CounterSamples |
Group-Object Path |
ForEach-Object {
$avg = ($_.Group | Measure-Object CookedValue -Average).Average
[PSCustomObject]@{
Server = $env:COMPUTERNAME
Adapter = ($_.Name -replace '.*\((.+)\)\\.*','$1')
Avg_Mbps = [Math]::Round($avg * 8 / 1MB, 3)
}
}
}
Exporting Bandwidth Utilization to CSV
Append a datestamp to the filename so you can build a historical archive of bandwidth reports without overwriting previous runs.
$timestamp = Get-Date -Format "yyyyMMdd-HHmm"
$allResults | Select-Object Server, Adapter, Avg_Mbps |
Export-Csv "F:\reports\bandwidth-$timestamp.csv" -NoTypeInformation
Write-Output "Saved $($allResults.Count) rows to bandwidth-$timestamp.csv"
Common Errors
- Counter path has spaces in the adapter name: Constructing counter paths manually by concatenating strings from
Get-NetAdapteroften produces paths thatGet-Countercannot resolve. Always retrieve paths from(Get-Counter -ListSet "Network Interface").PathsWithInstancesto get verbatim-correct strings. - Get-Counter returns
$nullon disabled adapters: Disabled or disconnected adapters are registered in the counter set but have no active counters. Filter by checkingGet-NetAdapter | Where-Object { $_.Status -eq "Up" }and cross-reference against discovered paths before sampling.
Related Cmdlets / See Also
Wrapping Up
Windows Performance Counters provide the most accurate, low-overhead source of network utilization data available. By combining Get-Counter with multi-sample averaging and remote execution you can build bandwidth reports across your entire server fleet in under a minute, with results precise enough to guide hardware upgrade decisions.


