I have this script to make a CSV of VMs hosted on our Hyper-V clusters. It's working great, and I even got it separating each VM into a job!

Side note: I wouldn't mind advice on how to more efficient do my scheduling. Main issue is some clusters are WS 2016 and others are WS 2012R2, which require an older Hyper-V PowerShell module. I'm not sure how to parallelize different modules, so I just get the VM list for each Hyper-V node, parallelize them, and then change the module if the next node requires it.

Anyway, my issue. Here's my script.

$numThreads = 4

$ScriptBlock = {
    Param (

    If ($ADComputer = (Get-ADComputer $VM.Name -Properties OperatingSystem)) {
        $OS = $ADComputer.OperatingSystem
    Else {
        $OS = 'Unknown (not on domain)'

    try {
        Return [pscustomobject] @{
            Cluster               = $Cluster
            Node                  = $Node
            Name                  = $VM.Name
            OS                    = $OS
            State                 = $VM.State
            Status                = $VM.Status
            Uptime                = "{0:dd} days {0:hh} hours" -f $VM.Uptime
            CPUUsage              = $VM.CPUUsage
            ProcessorCount        = $VM.ProcessorCount
            'MemoryDemand (GB)'   = [math]::Round( $VM.MemoryDemand / 1GB, 2 )
            'MemoryAssigned (GB)' = [math]::Round( $VM.MemoryAssigned / 1GB, 2 )
            'VHD Size (GB)'       = [math]::Round( ((Get-VHD -ComputerName $Node -VMId $VM.Id).FileSize | Measure-Object -Sum).Sum / 1GB, 2 )
            Version               = $VM.Version
            'VLAN IDs'            = ($VM | Select-Object -ExpandProperty NetworkAdapters | Select-Object -ExpandProperty VlanSetting).AccessVlanId -Join ", "
            'IP Addresses'        = ($VM | Select-Object -ExpandProperty NetworkAdapters).IPAddresses -Join ", "
    catch {
        Return [pscustomobject] @{
            Cluster = $Cluster
            Node    = $Node
            Name    = $VM.Name
            State   = "Script Fail"
            Status  = $_.Exception.Message

$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $numThreads)

$Jobs = @()
$VMInfoList = @()

ForEach ($Cluster in 'USHOMECLU0', 'USHOMECLU1') {
    If ((Get-ADComputer $Cluster -Property OperatingSystem).OperatingSystem -Match '2016') {
        Remove-Module Hyper-V
        Import-Module Hyper-V -RequiredVersion
    Else {
        Remove-Module Hyper-V
        Import-Module Hyper-V -RequiredVersion 1.1
    ForEach ($Node in Get-ClusterNode -Cluster $Cluster) {
        Get-VM -ComputerName $Node | ForEach-Object {
            $Job = [powershell]::Create().AddScript($ScriptBlock).AddParameter("Cluster", $Cluster).AddParameter("Node", $Node).AddParameter("VM", $_)
            $Job.RunspacePool = $RunspacePool
            $Jobs += New-Object PSObject -Property @{
                Job    = $Job
                Result = $Job.BeginInvoke()

# EndInvoke returns the objects from the background threads
ForEach ($Job in $Jobs) {
    $VMInfoList += $Job.Job.EndInvoke($Job.Result)

$VMInfoList | Export-Csv -NoTypeInformation C:\PSReports_vms.csv

The issue I have is that jobs for VMs on Windows Server 2012R2 nodes always return 0 for this:

        'VHD Size'       = ((Get-VHD -ComputerName $Node -VMId $VM.Id).FileSize | Measure-Object -Sum).Sum

However, for Windows Server 2016 nodes, all VMs return the appropriate value. What's also interesting is that if I capture the exact expression being assigned to VHD Size for those Server 2012R2 hosted VMs (replace variables with the direct string), and execute it outside of the scriptblock for the job, I get the accurate value.

  • Try to update the PowerShell on the Windows 2012R2 to the 5.1 version and try again. docs.microsoft.com/en-us/powershell/scripting/setup/… – Strepsils Feb 27 '18 at 21:39
  • Have you considered simply invoking to each cluster node, and running the Hyper-V commands locally there, then just aggregate the results on your local box? Something like $VMInfoList += (Invoke-Command -ComputerName (Get-ClusterNode -Cluster $Cluster) { Get-VM |% ... create your custom object... }). That lets PowerShell do the implicit fan-out across, and aggregate the results from the cluster nodes itself. – Matthew Wetmore Mar 3 '18 at 7:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.