Query multiple servers for performance counters in parallel

I’ve written a script to query multiple Hyper-V hosts for some metrics, then using some logic, returns the name of host, that I can then use to create a vm on. Here is my script, note being I haven’t parameterized it yet.

Workflow Pick-VMHost {

    $Servers = @('host00', 'host01', 'host02', 'host03')
    $UseHost = @{}
    $VMRAM = 1.5GB
    $VMDiskSize = 40GB

    ForEach -Parallel ($Server in $Servers) {
        $CPU = InlineScript { (Get-Counter -Counter "\Processor(_Total)\% Processor Time" -ComputerName $using:Server -SampleInterval 1 -MaxSamples 5).CounterSamples.CookedValue | Measure-Object -Average | Select-Object -ExpandProperty Average }
        $RAM = InlineScript { (Get-Counter -Counter "\Memory\Available Bytes" -ComputerName $using:Server -SampleInterval 1 -MaxSamples 5).CounterSamples.CookedValue | Measure-Object -Average | Select-Object -ExpandProperty Average }
        $Disk = Get-WmiObject win32_logicaldisk -PSComputerName $Server -Filter "DeviceID='D:'" | Select-Object -ExpandProperty FreeSpace
        
        If ( ($CPU -le 80) -and ($RAM -ge ($VMRAM + 3GB)) -and ($Disk -ge ($VMDiskSize + 25GB)) ) {
            If ($UseHost.Count -eq 0) {
                $Workflow:UseHost = @{ "Server" = $Server; "Disk" = $Disk }
            }

            ElseIf ($Disk -gt $UseHost.Disk) {
                $Workflow:UseHost = @{ "Server" = $Server; "Disk" = $Disk }
            }
        }
    }

    $UseHost.Server
}

$Result = Pick-VMHost

I feel like this code isn’t well written and there is a better way to do this, I’ve tried use just Get-Counter on its own but didn’t find a way to get average results for the counters, tried using Jobs, but couldn’t figure out how to return values properly, I’ve ended up using a Workflow. I’ve also posted part of this script on StackOverflow, because I couldn’t at the time figure out how to return the values I wanted in a meaningful way.

Finally I know that Hyper-V has VM Metric functionality, but as far as I’m aware it only queries the VM’s and not the host.

If you have PSRemoting enabled on the target servers, the simplest approach would be to use Invoke-Command. When you pass it an array to the ComputerName parameter, it automatically does the operation in parallel (up to a maximum number at a time, which you can specify with the -ThrottleLimit parameter). For example:

Function Pick-VMHost {

    $Servers = @('host00', 'host01', 'host02', 'host03')
    $UseHost = @{}
    $VMRAM = 1.5GB
    $VMDiskSize = 40GB

    $serverMetrics = Invoke-Command -ComputerName $Servers -ScriptBlock {
        $CPU = (Get-Counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5).CounterSamples.CookedValue | Measure-Object -Average | Select-Object -ExpandProperty Average
        $RAM = (Get-Counter -Counter "\Memory\Available Bytes" -SampleInterval 1 -MaxSamples 5).CounterSamples.CookedValue | Measure-Object -Average | Select-Object -ExpandProperty Average
        $Disk = Get-WmiObject win32_logicaldisk -Filter "DeviceID='D:'" | Select-Object -ExpandProperty FreeSpace

        New-Object psobject -Property @{
            CPU = $CPU
            RAM = $RAM
            Disk = $Disk
        }
    }

    # Code here locally to determine which host to use from the $serverMetrics array.  Each object will have a PSComputerName property automatically added by PSRemoting.

    $UseHost.PSComputerName

}

$Result = Pick-VMHost

This code still collects each counter synchronously on the remote computers, but process the computers themselves in parallel. If you need to parallelize the counter collection as well, that’s probably also possible, but would involve a lot more code, and may not be worth the effort (depends on how long this takes to run, and if it meets your needs already.)

I didn’t know about New-Object, more reading to do. If I want to run the commands synchronously on each remote machine, would you recommend using a workflow? As it is at the moment, I will keep the code as is, but if I add more counters then I might need to speed up the process.

This makes it so much easier to read, thanks.

a simple approach …
Get-counter -Counter “\PhysicalDisk(_Total)\Avg. Disk Read Queue Length”,”\Processor(_Total)% Processor Time”,”\PhysicalDisk(_Total)\Avg. Disk Write Queue Length”,”\Memory\Available MBytes”,”\Memory\Pages/sec” -computername computername1,computername2,computername3 -Continuous | export-counter desktop\counter.blg

Multiple counters , multiple remote computers…