Public/healthcheck/Get-HyperVHostHealth.ps1
|
#Requires -Version 5.1 function Get-HyperVHostHealth { <# .SYNOPSIS Checks Hyper-V host health on Windows servers .DESCRIPTION Queries the Hyper-V Virtual Machine Management service and enumerates virtual machines to assess host resource utilization and VM health. Returns a typed health object per host with VM counts by state, memory utilization, and an overall health assessment. .PARAMETER ComputerName One or more computer names to query. Defaults to the local machine. Accepts pipeline input by value and by property name. .PARAMETER Credential Optional PSCredential for authenticating to remote computers. Not used for local queries. .EXAMPLE Get-HyperVHostHealth Checks Hyper-V health on the local host. .EXAMPLE Get-HyperVHostHealth -ComputerName 'HV01' Checks Hyper-V health on a remote Hyper-V host. .EXAMPLE 'HV01', 'HV02' | Get-HyperVHostHealth Checks Hyper-V health on multiple hosts via pipeline. .OUTPUTS PSWinOps.HyperVHostHealth Returns one object per target host containing service status, VM counts by state, memory utilization metrics, and an overall health assessment string. .NOTES Author: Franck SALLET Version: 1.0.0 Last Modified: 2026-03-26 Requires: PowerShell 5.1+ / Windows only Requires: Hyper-V role .LINK https://github.com/k9fr4n/PSWinOps .LINK https://learn.microsoft.com/en-us/powershell/module/hyper-v/ #> [CmdletBinding()] [OutputType('PSWinOps.HyperVHostHealth')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'Name', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) begin { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting" $localNames = @($env:COMPUTERNAME, 'localhost', '.') $scriptBlock = { $data = @{ ServiceStatus = 'NotFound' ModuleAvailable = $false LogicalProcessors = 0 MemoryCapacityBytes = [long]0 TotalVMs = 0 VMsRunning = 0 VMsOff = 0 VMsSaved = 0 VMsPaused = 0 VMsCritical = 0 AssignedMemoryBytes = [long]0 } try { $svc = Get-Service -Name 'vmms' -ErrorAction Stop $data.ServiceStatus = $svc.Status.ToString() } catch { $data.ServiceStatus = 'NotFound' } $moduleCheck = Get-Module -Name 'Hyper-V' -ListAvailable -ErrorAction SilentlyContinue if ($moduleCheck) { $data.ModuleAvailable = $true } if ($data.ModuleAvailable -and $data.ServiceStatus -eq 'Running') { try { $vmHost = Get-VMHost -ErrorAction Stop $data.LogicalProcessors = [int]$vmHost.LogicalProcessorCount $data.MemoryCapacityBytes = [long]$vmHost.MemoryCapacity $allVMs = @(Get-VM -ErrorAction Stop) $data.TotalVMs = $allVMs.Count $data.VMsRunning = @($allVMs | Where-Object -FilterScript { $_.State -eq 'Running' }).Count $data.VMsOff = @($allVMs | Where-Object -FilterScript { $_.State -eq 'Off' }).Count $data.VMsSaved = @($allVMs | Where-Object -FilterScript { $_.State -eq 'Saved' }).Count $data.VMsPaused = @($allVMs | Where-Object -FilterScript { $_.State -eq 'Paused' }).Count $data.VMsCritical = $data.TotalVMs - $data.VMsRunning - $data.VMsOff - $data.VMsSaved - $data.VMsPaused $memMeasure = $allVMs | Measure-Object -Property 'MemoryAssigned' -Sum if ($memMeasure.Sum) { $data.AssignedMemoryBytes = [long]$memMeasure.Sum } } catch { Write-Warning -Message "Failed to query Hyper-V data: $_" } } $data } } process { foreach ($machine in $ComputerName) { $displayName = $machine.ToUpper() Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying '${machine}'" try { $isLocal = $localNames -contains $machine if ($isLocal) { $result = & $scriptBlock } else { $invokeParams = @{ ComputerName = $machine ScriptBlock = $scriptBlock ErrorAction = 'Stop' } if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $invokeParams['Credential'] = $Credential } $result = Invoke-Command @invokeParams } $totalMemoryGB = [math]::Round($result.MemoryCapacityBytes / 1GB, 2) $assignedMemoryGB = [math]::Round($result.AssignedMemoryBytes / 1GB, 2) $memUsagePct = if ($totalMemoryGB -gt 0) { [math]::Round(($assignedMemoryGB / $totalMemoryGB) * 100, 2) } else { [decimal]0 } # Compute OverallHealth if (-not $result.ModuleAvailable) { $healthStatus = 'RoleUnavailable' } elseif ($result.ServiceStatus -ne 'Running' -or $result.VMsCritical -gt 0) { $healthStatus = 'Critical' } elseif ($memUsagePct -gt 90) { $healthStatus = 'Degraded' } else { $healthStatus = 'Healthy' } [PSCustomObject]@{ PSTypeName = 'PSWinOps.HyperVHostHealth' ComputerName = $displayName ServiceName = 'vmms' ServiceStatus = $result.ServiceStatus LogicalProcessors = [int]$result.LogicalProcessors TotalMemoryGB = [decimal]$totalMemoryGB TotalVMs = [int]$result.TotalVMs VMsRunning = [int]$result.VMsRunning VMsOff = [int]$result.VMsOff VMsSaved = [int]$result.VMsSaved VMsPaused = [int]$result.VMsPaused VMsCritical = [int]$result.VMsCritical AssignedMemoryGB = [decimal]$assignedMemoryGB MemoryUsagePercent = [decimal]$memUsagePct OverallHealth = $healthStatus Timestamp = Get-Date -Format 'o' } } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_" continue } } } end { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed" } } |