TL;DR: Two one-liners that turn the verbose Get-StorageQoSFlow output into a clean per-VM table of live IOPS, latency, and bandwidth on an Azure Local / Storage Spaces Direct cluster — either for the local node only, or for every VM across every node.

Recommended action:

Open a PowerShell prompt on any cluster node (or remote in via Enter-PSSession).

For the VMs currently running on the local node:

Get-StorageQoSFlow | Select-Object `
    @{name='VM Name';expression={$_.InitiatorName}}, `
    @{name='IOPS';expression={$_.InitiatorIOPS}}, `
    @{name='Latency (ms)';expression={$_.InitiatorLatency}}, `
    @{name='Bandwidth (MB/s)';expression={$_.InitiatorBandwidth}}

For every VM across every node in the cluster (run from any node):

Get-StorageQoSFlow -CimSession (Get-ClusterNode).Name | Select-Object `
    @{name='Node';expression={$_.PSComputerName}}, `
    @{name='VM Name';expression={$_.InitiatorName}}, `
    @{name='IOPS';expression={$_.InitiatorIOPS}}, `
    @{name='Latency (ms)';expression={$_.InitiatorLatency}}, `
    @{name='Bandwidth (MB/s)';expression={$_.InitiatorBandwidth}} |
    Sort-Object Node, 'VM Name'

Each row in the output is one active I/O flow from a VM to a storage file, with current performance counters as seen from the initiator (VM) side. The clusterwide form fans out via CimSession to every cluster node and adds a Node column so you can see which host owns each VM.

Why:

Get-StorageQoSFlow returns the active storage I/O flows the Storage QoS resource is tracking. The default output is wide and includes GUIDs, policy IDs, and target-side counters that are noisy for quick triage. Renaming the four most useful initiator-side properties to friendly column headers gives a clean snapshot of which VMs are doing storage work and how that work is performing — useful for spotting “noisy neighbor” VMs, validating that a Storage QoS policy is taking effect, or sanity-checking a “my VM is slow” complaint against the actual numbers.

Going forward:

  • Why two forms. Each Hyper-V host tracks its own initiator-side flows, so a local Get-StorageQoSFlow call only returns flows for the VMs currently running on that node. The clusterwide form uses CimSession fan-out to query every node in one pipeline. This relies on WinRM / WS-Man, which is already required for normal cluster operation, so no extra setup is needed.

  • Real-time snapshot, not history. Values change second to second. For a transient spike, run the command repeatedly (or in a loop with while ($true) { ...; Start-Sleep 2; cls }). For sustained baselining and trending, use cluster Performance History (Get-ClusterPerf) instead. Idle VMs will not appear because there is no active flow to report.

Optional details:

  • InitiatorBandwidth is reported in bytes per second, not megabytes per second. The “MB/s” column label is for readability only — divide the value by 1MB if you need true MB/s.

  • To filter to a single VM, append | Where-Object {$_.InitiatorName -eq 'MyVM'}.

  • To sort by hottest VM, sort on IOPS after the Select, e.g. ... | Sort-Object IOPS -Descending.

  • To see every available property (including target-side IOPS / latency / bandwidth, which can diverge from initiator-side under contention) run Get-StorageQoSFlow | Format-List *.

  • Works on any Storage QoS-enabled cluster: Azure Local, Azure Stack HCI, and Storage Spaces Direct on Windows Server 2016 and later.