TL;DR: Hyper-V records live throughput and dropped-packet counts for every VM network adapter in the Hyper-V Virtual Network Adapter performance counters. The script below samples those counters on every cluster node, maps each adapter to its VM, and shows which VMs are moving the most traffic. Unlike storage, Hyper-V does not track per-VM network latency — see Going forward.
Recommended action
Where to run: Steps 1–4 run in one elevated PowerShell session, in either of these:
- Directly on a cluster node (console or Remote Desktop), or
- On a management machine that can reach the nodes and has the Failover Clustering PowerShell module (RSAT) installed. Replace the first line of step 1 with
$nodes = (Get-ClusterNode -Cluster "ClusterName" | Where-Object State -eq 'Up').Name.
Do not run step 1 from inside an Enter-PSSession to a node. Step 1 opens its own remote connections to every node, and from inside a remote session that is a second hop. Windows does not pass your credentials on a second hop by default, so the nodes return access-denied errors.
Each command is labeled with where to run it and what it covers: all VMs in the cluster, one node only, or one VM.
1. Collect a sample from every node
Run on: a cluster node directly, or a management machine (see above) — not inside Enter-PSSession
Covers: all VMs in the cluster — run it once, not per node (nodes that are paused or down are skipped)
This takes about 2 seconds and stores the results in $net for the views below. Host management adapters, physical NICs, and Azure Local's internal metadata (IMDS) adapters are excluded so traffic is not counted twice.
This step produces no output. When the prompt returns, continue to step 2 to view the results.
$nodes = (Get-ClusterNode | Where-Object State -eq 'Up').Name
$net = Invoke-Command -ComputerName $nodes -ScriptBlock {
$set = '\Hyper-V Virtual Network Adapter(*)'
$samples = (Get-Counter -Counter @(
"$set\Bytes Received/sec", "$set\Bytes Sent/sec",
"$set\Dropped Packets Incoming/sec", "$set\Dropped Packets Outgoing/sec"
) -SampleInterval 2 -MaxSamples 1).CounterSamples
# Map NIC GUID -> VM network adapter
$nics = @{}
Get-VM | Get-VMNetworkAdapter | ForEach-Object {
$nics[($_.Id -split '\\')[-1].ToLower()] = $_
}
$samples |
Where-Object { $_.InstanceName -match '--[0-9a-f-]{36}$' -and $_.InstanceName -notmatch 'imds' } |
Group-Object InstanceName | ForEach-Object {
$null = $_.Name -match '--([0-9a-f-]{36})$'
$nic = $nics[$Matches[1]]
$c = @{}; $_.Group | ForEach-Object { $c[($_.Path -split '\\')[-1]] = $_.CookedValue }
[PSCustomObject]@{
Host = $env:COMPUTERNAME
VM = if ($nic) { $nic.VMName } else { $_.Name }
Adapter = if ($nic) { $nic.Name } else { '?' }
'Rx Mbps' = [math]::Round($c['bytes received/sec'] * 8 / 1e6, 2)
'Tx Mbps' = [math]::Round($c['bytes sent/sec'] * 8 / 1e6, 2)
'Drops In' = [math]::Round($c['dropped packets incoming/sec'], 1)
'Drops Out' = [math]::Round($c['dropped packets outgoing/sec'], 1)
}
}
} | Select-Object * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName2. Per-VM summary (one row per VM, busiest first)
Run on: the same PowerShell session as step 1
Covers: all VMs in the cluster, from the step 1 sample — no new data is collected; re-run step 1 for a fresh reading
$net | Group-Object VM | ForEach-Object {
$g = $_.Group
[PSCustomObject]@{
VM = $_.Name
Host = $g[0].Host
vNICs = @($g).Count
'Rx Mbps' = ($g | Measure-Object 'Rx Mbps' -Sum).Sum
'Tx Mbps' = ($g | Measure-Object 'Tx Mbps' -Sum).Sum
'Drops In' = ($g | Measure-Object 'Drops In' -Sum).Sum
'Drops Out' = ($g | Measure-Object 'Drops Out' -Sum).Sum
}
} | Sort-Object 'Rx Mbps' -Descending | Format-Table -AutoSizeExample output:
VM Host vNICs Rx Mbps Tx Mbps Drops In Drops Out
-- ---- ----- ------- ------- -------- ---------
FILE01 NODE1 1 842.15 61.40 4 0
APP-SQL01 NODE2 2 118.72 204.33 8 0
DC01 NODE1 1 0.21 0.18 4 0
MGMT01 NODE2 1 0.01 0 13 03. Per-vNIC detail
Run on: the same PowerShell session as step 1
Covers: all VMs in the cluster, one row per network adapter, from the step 1 sample
Use this for VMs with more than one network adapter, to see which adapter carries the load.
$net | Sort-Object 'Rx Mbps' -Descending |
Format-Table VM, Adapter, Host, 'Rx Mbps', 'Tx Mbps', 'Drops In', 'Drops Out' -AutoSize4. Optional: save a snapshot
Run on: the same PowerShell session as step 1
Covers: all VMs in the cluster, from the step 1 sample
The file is written to the machine you ran step 1 on — the cluster node, or your management machine. The C:\Temp folder must already exist there.
$net | Export-Csv -Path C:\Temp\VM-Network-$(Get-Date -Format yyyyMMdd-HHmm).csv -NoTypeInformationWhy
Each VM network adapter is a port on a Hyper-V virtual switch, and the host keeps per-port counters for traffic and discarded packets. Unlike storage, there is no cluster-wide service that aggregates these: the counters live only on whichever node is running the VM, so the script samples every node and merges the results. The counter instance names contain the VM's and adapter's GUIDs rather than readable names; the script matches the adapter GUID back to Get-VMNetworkAdapter to show the real VM and adapter names.
Key columns:
- Rx / Tx Mbps — traffic received by and sent from the VM, in megabits per second so it can be compared directly with link speed (a 25 GbE link is 25,000 Mbps).
- Drops In — packets per second the virtual switch discarded before delivering them to the VM.
- Drops Out — packets per second sent by the VM that the virtual switch discarded.
Going forward
- These are live values over a short sample. Run the script while the issue is happening. For a steadier reading, raise
-SampleIntervalin step 1 (for example to 10) to average over a longer window. - A small, steady incoming drop rate is typically background noise. If every vNIC on the same network shows about the same drop rate — including idle VMs passing no traffic — the virtual switch is most likely discarding broadcast or multicast frames those VMs don't accept, not losing real traffic. Investigate when drops rise along with throughput, or when one vNIC drops far more than others on the same network.
- To see why packets are dropped, run a drop capture with pktmon. Start the capture, let it run for about 30 seconds, then stop and convert it to text. The files are written to the current directory on that node.
Run on: the node currently hosting the VM in question (see the Host column), directly or viaEnter-PSSessionto that node
Covers: one node only — all dropped packets on that node, for every VM and host adapter on it. Repeat on another node if needed.pktmon start --capture --type drop pktmon stop pktmon format PktMon.etl -o drops.txt - There is no per-VM network latency counter. For latency, test from inside the guest — for example
Test-NetConnection <target> -Port <port>or a continuous ping to the destination — then compare against a VM on another host.
Run on: inside the VM’s operating system, not on the host
Covers: one VM — its path to the target you name - If every VM on one host is slow, look at the host's physical adapters and switch uplinks rather than individual VMs.
Optional details
Counter instance names. VM adapters appear as <vmname>_<adapter name>_<VM GUID>--<adapter GUID>. Host management adapters (for example vmanagement[...]), physical NICs, and adapters whose names contain imds are excluded by the script. If a row shows a raw instance name with Adapter ?, the adapter GUID could not be matched to a VM on that host — usually a VM that was migrating while the sample was taken.
Cumulative totals instead of live rates. To see total traffic per VM over days rather than a live snapshot, use Hyper-V resource metering. Enable it with Get-VM | Enable-VMResourceMetering, then later read it with Get-VM | Measure-VM | Format-Table VMName, NetworkMeteredTrafficReport. Totals accumulate from the moment metering is enabled and reset with Reset-VMResourceMetering.
Run on: each cluster node individually, directly or via Enter-PSSession to that node
Covers: one node only — the VMs running on that node at the time. Repeat on every node for full coverage.
Companion article. For per-VM storage IOPS, latency, and bandwidth, see the Storage Spaces Direct article on Get-StorageQoSFlow.