Private/Checks/Vsan/Test-VcfVsanHealthCheck.ps1
|
# Copyright (c) 2026 Broadcom. All Rights Reserved. # Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc. # and/or its subsidiaries. # # ============================================================================= # # SOFTWARE LICENSE AGREEMENT # # Copyright (c) CA, Inc. All rights reserved. # # You are hereby granted a non-exclusive, worldwide, royalty-free license # under CA, Inc.'s copyrights to use, copy, modify, and distribute this # software in source code or binary form for use in connection with CA, Inc. # products. # # This copyright notice shall be included in all copies or substantial # portions of the software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # ============================================================================= function Test-VcfVsanHealthCheck { <# .SYNOPSIS Checks overall vSAN cluster health status for every vSAN-enabled cluster attached to a vCenter. .DESCRIPTION Queries vSAN cluster health across all vCenter appliances connected to SDDC Manager using Get-VcfCheckVsanClusterHealth (Test-VsanClusterHealth). Evaluates overall cluster health statuses and per-category sub-tests: - Evaluates OverallHealthStatus using both color-based ('green', 'yellow', 'red') and state-based ('passed', 'warning', 'failed') status indicators. - Captures human-readable diagnostic summaries from OverallHealthDescription. - Parses per-category sub-tests (Cluster, Network, Limits, Physical Disk, Data, Encryption, Hardware Compatibility, File Service) via Get-VcfCheckVsanHealthAllSubTests and isolates red/yellow findings via Get-VcfCheckVsanHealthFailingSubTests. Evaluation logic (via ConvertTo-VcfCheckVsanOverallStatusLabel): - Pass: All vSAN clusters report healthy status ('green', 'passed', or 'info' - vCenter's "nothing wrong" state). - Warning: One or more vSAN clusters report warning status ('yellow' or 'warning') or unrecognized health states. - Fail: One or more vSAN clusters report failed status ('red' or 'failed'). - Skipped: No vSAN-enabled clusters are found in the target vCenter inventory. Populates a structured Rows summary table detailing ClusterName, OverallHealthStatus, OverallHealthDescription, and SubTasks, sorted alphabetically by ClusterName, and constructs HostDetails cards for detailed sub-test visualization. For Warning and Fail results, Rows and the Detail message include only the clusters at that status - clusters reporting Pass are omitted, and the Detail text says so explicitly. Delegates per-vCenter execution and per-domain outcome packaging to Invoke-VcfCheckPerVCenterCheck. .PARAMETER Context The VcfCheck.Context object. Must already be connected to SDDC Manager. .PARAMETER DisplayName Optional friendly display name for the check result. .OUTPUTS [PSObject[]] Per-vCenter check results generated by Invoke-VcfCheckPerVCenterCheck. #> [CmdletBinding()] [OutputType([PSObject])] Param ( [Parameter(Mandatory = $true)] [PSObject]$Context, [Parameter(Mandatory = $false)] [String]$DisplayName = '' ) return Invoke-VcfCheckPerVCenterCheck -Context $Context -CheckId 'vsan_health_check' -Area vSAN -DisplayName $DisplayName -Body { param($Context, $VCenterFqdn) $healthResults = @(Get-VcfCheckVsanClusterHealth -Server $VCenterFqdn -Context $Context) if ($healthResults.Count -eq 0) { return [PSCustomObject]@{ Status = 'Skipped'; Detail = 'No vSAN-enabled cluster found on this vCenter.'; SkipReasonTag = 'no vSAN cluster'; Rows = @() } } $rows = @($healthResults | ForEach-Object { [PSCustomObject]@{ ClusterName = $_.Cluster.Name OverallHealthStatus = $_.OverallHealthStatus OverallHealthDescription = $_.OverallHealthDescription SubTasks = Get-VcfCheckVsanHealthFailingSubTests -HealthResult $_ } }) $hostDetails = @($healthResults | ForEach-Object { [PSCustomObject]@{ HostName = $_.Cluster.Name Status = ConvertTo-VcfCheckVsanOverallStatusLabel -Status $_.OverallHealthStatus OverallHealthDescription = $_.OverallHealthDescription SubTests = @(Get-VcfCheckVsanHealthAllSubTests -HealthResult $_) } }) $troubleshootingReference = Get-VcfCheckVsanTroubleshootingKbText -Number '326929' -Url 'https://knowledge.broadcom.com/external/article/326929/vsan-health-service-data-health-vsan-o.html' $failRows = @($rows | Where-Object { (ConvertTo-VcfCheckVsanOverallStatusLabel -Status $_.OverallHealthStatus) -eq 'Fail' } | Sort-Object -Property ClusterName) $warnRows = @($rows | Where-Object { (ConvertTo-VcfCheckVsanOverallStatusLabel -Status $_.OverallHealthStatus) -eq 'Warning' } | Sort-Object -Property ClusterName) if ($failRows.Count -gt 0) { $clusterNames = ($failRows.ClusterName) -join '; ' return [PSCustomObject]@{ Status = 'Fail'; Detail = "vSAN health is unhealthy on: $clusterNames. $troubleshootingReference"; Rows = $failRows; HostDetails = $hostDetails; HostDetailsLabel = 'vSAN Cluster Health Details' } } if ($warnRows.Count -gt 0) { $clusterNames = ($warnRows.ClusterName) -join '; ' return [PSCustomObject]@{ Status = 'Warning'; Detail = "vSAN health needs attention on the following cluster(s) with Warning status (clusters reporting Pass are not shown): $clusterNames. $troubleshootingReference"; Rows = $warnRows; HostDetails = $hostDetails; HostDetailsLabel = 'vSAN Cluster Health Details' } } return [PSCustomObject]@{ Status = 'Pass'; Detail = "Checked $($healthResults.Count) vSAN cluster(s); all report healthy."; Rows = $rows; HostDetails = $hostDetails; HostDetailsLabel = 'vSAN Cluster Health Details' } } } function ConvertTo-VcfCheckVsanFlagHealth { <# .SYNOPSIS Converts a boolean success flag into a standard vSAN health status string. .DESCRIPTION Maps nullable boolean flags returned by vSAN health queries to standardized 'green' or 'red' status indicators used in sub-test results. Returns $null if the input flag is $null. .PARAMETER Flag The boolean success flag to convert. .OUTPUTS [String] 'green', 'red', or $null. #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $false)] [Nullable[Boolean]]$Flag ) if ($null -eq $Flag) { return $null } if ($Flag) { return 'green' } else { return 'red' } } function ConvertTo-VcfCheckVsanIssueHealth { <# .SYNOPSIS Converts an IssueFound boolean flag into a standard vSAN health status string. .DESCRIPTION Maps nullable IssueFound boolean flags returned by vSAN health queries to standardized 'green' or 'red' status indicators (where $true converts to 'red' and $false converts to 'green'). Returns $null if the input flag is $null. .PARAMETER IssueFound The IssueFound flag to convert. .OUTPUTS [String] 'green', 'red', or $null. #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $false)] [Nullable[Boolean]]$IssueFound ) if ($null -eq $IssueFound) { return $null } if ($IssueFound) { return 'red' } else { return 'green' } } function ConvertTo-VcfCheckVsanOverallStatusLabel { <# .SYNOPSIS Normalizes a vSAN OverallHealthStatus value into the check's Pass/Warning/Fail badge vocabulary. .DESCRIPTION vCenter's vSAN health API returns OverallHealthStatus as a mix of color words ('green', 'yellow', 'red') and state words ('passed', 'warning', 'failed', 'info'). 'info' means "nothing wrong" - it is not a warning - so it maps to 'Pass' alongside 'green'/'passed'. An unrecognized value defaults to 'Warning' rather than silently rendering as a healthy Pass. .PARAMETER Status The raw OverallHealthStatus value returned by Test-VsanClusterHealth. .OUTPUTS [String] 'Pass', 'Warning', or 'Fail'. #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $false)] [String]$Status ) if (-not $Status) { return 'Warning' } $normalized = $Status.ToLowerInvariant() if ($normalized -in @('red', 'failed')) { return 'Fail' } if ($normalized -in @('yellow', 'warning')) { return 'Warning' } if ($normalized -in @('green', 'passed', 'info')) { return 'Pass' } return 'Warning' } function ConvertTo-VcfCheckVsanSafeArray { <# .SYNOPSIS Safely wraps array properties from vSAN health responses. .DESCRIPTION Ensures null or unpopulated array properties from vSAN health API responses evaluate to an empty array rather than a single $null element. .PARAMETER Value The array property value to evaluate and wrap. .OUTPUTS [Object[]] Empty array if input is $null; otherwise an array wrapping the input value. #> [CmdletBinding()] [OutputType([Object[]])] Param ( [Parameter(Mandatory = $false)] [PSObject]$Value ) if ($null -eq $Value) { return @() } return @($Value) } function New-VcfCheckVsanHealthTestRow { <# .SYNOPSIS Constructs a structured sub-test report object for vSAN cluster health details. .DESCRIPTION Creates a custom object representing an individual vSAN health sub-test result, containing GroupName, GroupHealth, TestName, TestHealth, and TestDescription properties. .PARAMETER GroupName The vSAN health category name (e.g., 'Network', 'Limits'). .PARAMETER GroupHealth The rollup health status for the category. .PARAMETER TestName The display name of the sub-test. .PARAMETER TestHealth The health status value for the sub-test (defaults to 'N/A' if empty). .PARAMETER TestDescription Optional descriptive details for the sub-test outcome. .OUTPUTS [PSCustomObject] Structured sub-test report row object. #> [CmdletBinding()] [OutputType([PSObject])] Param ( [Parameter(Mandatory = $true)] [String]$GroupName, [Parameter(Mandatory = $false)] [String]$GroupHealth, [Parameter(Mandatory = $true)] [String]$TestName, [Parameter(Mandatory = $false)] [String]$TestHealth, [Parameter(Mandatory = $false)] [String]$TestDescription ) return [PSCustomObject]@{ GroupName = $GroupName GroupHealth = $GroupHealth TestName = $TestName TestHealth = if ($TestHealth) { $TestHealth } else { 'N/A' } TestDescription = $TestDescription } } function Get-VcfCheckVsanHealthClusterTests { <# .SYNOPSIS Builds sub-test report rows for the Cluster vSAN health category. .DESCRIPTION Extracts cluster-level health indicators from a vSAN cluster health object, including overall health findings, software version compatibility, daemon liveness, and advanced configuration synchronization state. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Array of sub-test report rows for the Cluster category. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $rows = [System.Collections.Generic.List[Object]]::new() if ($HealthResult.HealthSystemStatus) { $status = $HealthResult.HealthSystemStatus.Status $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Cluster' -GroupHealth $status -TestName 'Overall health findings' -TestHealth $status)) } if ($HealthResult.HealthSystemVersion) { $versionHealth = ConvertTo-VcfCheckVsanIssueHealth -IssueFound $HealthResult.HealthSystemVersion.IssueFound $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Cluster' -GroupHealth $versionHealth -TestName 'Software version compatibility' -TestHealth $versionHealth -TestDescription $HealthResult.HealthSystemVersion.VsanVersion)) } if ($HealthResult.ClomdLiveness) { $clomdHealth = ConvertTo-VcfCheckVsanIssueHealth -IssueFound $HealthResult.ClomdLiveness.IssueFound $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Cluster' -GroupHealth $clomdHealth -TestName 'vSAN daemon liveness' -TestHealth $clomdHealth)) } if ('AdvancedConfigInSync' -in $HealthResult.PSObject.Properties.Name) { # $null here means "no mismatches found" (healthy), not "not applicable" - unlike the # other categories, so this checks the property's existence rather than its truthiness # to avoid fabricating a green result for an object that never had this field. $advancedConfigHealth = if ((ConvertTo-VcfCheckVsanSafeArray -Value $HealthResult.AdvancedConfigInSync).Count -gt 0) { 'red' } else { 'green' } $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Cluster' -GroupHealth $advancedConfigHealth -TestName 'Advanced vSAN configuration in sync' -TestHealth $advancedConfigHealth)) } return $rows.ToArray() } function Get-VcfCheckVsanHealthNetworkTests { <# .SYNOPSIS Builds sub-test report rows for the Network vSAN health category. .DESCRIPTION Extracts network health indicators from a vSAN cluster health object, including unicast ping tests, MTU packet size tests, vmknic presence, subnet matching, multicast configuration, partition status, and host communication status. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Array of sub-test report rows for the Network category. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $rows = [System.Collections.Generic.List[Object]]::new() if (-not $HealthResult.NetworkHealth) { return $rows.ToArray() } $net = $HealthResult.NetworkHealth $pingHealth = ConvertTo-VcfCheckVsanFlagHealth -Flag $net.PingTestSuccess $largePingHealth = ConvertTo-VcfCheckVsanFlagHealth -Flag $net.LargePingTestSuccess $vmknicHealth = ConvertTo-VcfCheckVsanFlagHealth -Flag $net.VsanVmknicPresent $subnetHealth = ConvertTo-VcfCheckVsanFlagHealth -Flag $net.MatchingIPSubnets $multicastHealth = ConvertTo-VcfCheckVsanFlagHealth -Flag $net.MatchingMulticastConfig # NetworkPartition always lists at least one group - the group containing all connected # hosts - so a count of 1 means the cluster is unified, not partitioned. Only 2+ groups # indicate an actual network split. $partitionCount = (ConvertTo-VcfCheckVsanSafeArray -Value $net.NetworkPartition).Count $partitionHealth = if ($partitionCount -gt 1) { 'red' } else { 'green' } $partitionDescription = if ($partitionCount -gt 1) { "Cluster is split into $partitionCount network partitions" } else { $null } $connectivityIssueHostCount = (ConvertTo-VcfCheckVsanSafeArray -Value $net.HostDisconnected).Count + (ConvertTo-VcfCheckVsanSafeArray -Value $net.HostCommunicationFailure).Count $connectivityHealth = if ($connectivityIssueHostCount -gt 0) { 'red' } else { 'green' } # GroupHealth is derived as the worst of this category's own sub-tests, not vCenter's # separate net.IssueFound rollup - that flag doesn't always agree with the sub-tests below # (e.g. a 1-host partition it doesn't count as "IssueFound"), which let GroupHealth show # green next to a red TestHealth in the same row. $netHealth = if (@($pingHealth, $largePingHealth, $vmknicHealth, $subnetHealth, $multicastHealth, $partitionHealth, $connectivityHealth) -contains 'red') { 'red' } else { 'green' } $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'vSAN: Basic (unicast) connectivity check' -TestHealth $pingHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'vSAN: MTU check (ping with large packet size)' -TestHealth $largePingHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'All hosts have a vSAN vmknic configured' -TestHealth $vmknicHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'All hosts have matching subnets' -TestHealth $subnetHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'Multicast configuration consistent' -TestHealth $multicastHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'vSAN cluster partition' -TestHealth $partitionHealth -TestDescription $partitionDescription)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Network' -GroupHealth $netHealth -TestName 'Hosts with connectivity issues' -TestHealth $connectivityHealth)) return $rows.ToArray() } function Get-VcfCheckVsanHealthLimitTests { <# .SYNOPSIS Builds sub-test report rows for the Limits vSAN health category. .DESCRIPTION Extracts resource limit health indicators from a vSAN cluster health object, including component limits, disk free space, and read cache reservations. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Array of sub-test report rows for the Limits category. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $rows = [System.Collections.Generic.List[Object]]::new() if (-not $HealthResult.LimitHealth) { return $rows.ToArray() } $limit = $HealthResult.LimitHealth $limitHealth = ConvertTo-VcfCheckVsanIssueHealth -IssueFound $limit.IssueFound $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Limits' -GroupHealth $limitHealth -TestName 'Current cluster situation' -TestHealth $limit.ComponentLimitHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Limits' -GroupHealth $limitHealth -TestName 'Disk space' -TestHealth $limit.DiskFreeSpaceHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Limits' -GroupHealth $limitHealth -TestName 'Read cache reservations' -TestHealth $limit.ReadCacheFreeReservationHealth)) return $rows.ToArray() } function Get-VcfCheckVsanHealthPhysicalDiskTests { <# .SYNOPSIS Builds sub-test report rows for the Physical Disk vSAN health category. .DESCRIPTION Extracts physical disk health indicators from a vSAN cluster health object, including vSAN disk balance and per-host physical disk health statuses. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Array of sub-test report rows for the Physical Disk category. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $rows = [System.Collections.Generic.List[Object]]::new() $diskBalance = ConvertTo-VcfCheckVsanSafeArray -Value $HealthResult.DiskBalance $diskBalanceHealth = $null if ($diskBalance.Count -gt 0) { $imbalancedCount = @($diskBalance | Where-Object { $_.UsageAboveThreshold -gt 0 }).Count $diskBalanceHealth = if ($imbalancedCount -gt 0) { 'yellow' } else { 'green' } } $diskHealthResult = ConvertTo-VcfCheckVsanSafeArray -Value $HealthResult.DiskHealthResult # GroupHealth is the worst of the disk balance result and every per-host disk health below - # there is no separate "Physical Disk" rollup field on the health result to read instead, and # leaving GroupHealth unset here (as before) left it blank on every per-host row. $perHostHealthValues = @($diskHealthResult | ForEach-Object { $_.OverallHealth }) $physicalDiskHealth = if (@($diskBalanceHealth) + $perHostHealthValues -contains 'red') { 'red' } elseif (@($diskBalanceHealth) + $perHostHealthValues -contains 'yellow') { 'yellow' } else { 'green' } if ($diskBalance.Count -gt 0) { $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Physical Disk' -GroupHealth $physicalDiskHealth -TestName 'vSAN Disk Balance' -TestHealth $diskBalanceHealth -TestDescription "$imbalancedCount of $($diskBalance.Count) disk(s) above balance threshold")) } foreach ($perHostDiskHealth in $diskHealthResult) { $diskHostName = if ($perHostDiskHealth.Host) { $perHostDiskHealth.Host.Name } else { 'Unknown host' } $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Physical Disk' -GroupHealth $physicalDiskHealth -TestName "Disk health: $diskHostName" -TestHealth $perHostDiskHealth.OverallHealth -TestDescription $perHostDiskHealth.Error)) } return $rows.ToArray() } function Get-VcfCheckVsanHealthDataTests { <# .SYNOPSIS Builds sub-test report rows for the Data vSAN health category. .DESCRIPTION Extracts per-host "Create a new VM" data-health results from a vSAN cluster health object. vCenter's OverallHealthStatus rollup includes this test, but it was previously absent from every category function below - a cluster could show OverallHealthStatus 'yellow' ("Cluster health issue") while every visible sub-test read green, because CreateVMHealth was never surfaced in the Rows/HostDetails tables. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Array of sub-test report rows for the Data category. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $rows = [System.Collections.Generic.List[Object]]::new() $createVmResults = ConvertTo-VcfCheckVsanSafeArray -Value $HealthResult.CreateVMHealth if ($createVmResults.Count -eq 0) { return $rows.ToArray() } $dataHealth = if (@($createVmResults | Where-Object { $_.State -and $_.State -ne 'green' -and $_.State -ne 'passed' }).Count -gt 0) { 'red' } else { 'green' } foreach ($createVmResult in $createVmResults) { $hostName = if ($createVmResult.Host) { $createVmResult.Host.Name } else { 'Unknown host' } $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Data' -GroupHealth $dataHealth -TestName "Create a new VM: $hostName" -TestHealth $createVmResult.State)) } return $rows.ToArray() } function Get-VcfCheckVsanHealthMiscTests { <# .SYNOPSIS Builds sub-test report rows for Encryption, Hardware Compatibility, and File Service vSAN health categories. .DESCRIPTION Extracts health indicators for Encryption (configuration and KMS cluster health), Hardware Compatibility (HCL database age), and File Service overall health from a vSAN cluster health object. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Array of sub-test report rows for Encryption, Hardware Compatibility, and File Service categories. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $rows = [System.Collections.Generic.List[Object]]::new() if ($HealthResult.EncryptionHealth) { $encryption = $HealthResult.EncryptionHealth $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Encryption' -GroupHealth $encryption.OverallHealth -TestName 'Encryption configuration' -TestHealth $encryption.ConfigurationHealth)) $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Encryption' -GroupHealth $encryption.OverallHealth -TestName 'KMS cluster health' -TestHealth $encryption.OverallKmsHealth)) } if ($HealthResult.HclInfo) { $hcl = $HealthResult.HclInfo $hclDescription = if ($hcl.LastUpdated) { "Last updated: $($hcl.LastUpdated)" } else { $null } $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'Hardware Compatibility' -GroupHealth $hcl.DatabaseAgeHealth -TestName 'HCL database up to date' -TestHealth $hcl.DatabaseAgeHealth -TestDescription $hclDescription)) } if ($HealthResult.FileServiceHealth -and 'OverallHealth' -in $HealthResult.FileServiceHealth.PSObject.Properties.Name) { $fileServiceHealth = $HealthResult.FileServiceHealth.OverallHealth $rows.Add((New-VcfCheckVsanHealthTestRow -GroupName 'File Service' -GroupHealth $fileServiceHealth -TestName 'File service health' -TestHealth $fileServiceHealth)) } return $rows.ToArray() } function Sort-VcfCheckVsanHealthTestsBySeverity { <# .SYNOPSIS Sorts vSAN health sub-test report rows by health severity. .DESCRIPTION Orders sub-test report rows so that unhealthy or warning items ('red', 'failed', 'yellow', 'warning') appear ahead of healthy items ('green', 'passed'), preserving logical grouping and scannability. .PARAMETER Tests Array of sub-test report row objects to sort. .OUTPUTS [PSObject[]] Sorted array of sub-test report rows. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $false)] [AllowEmptyCollection()] [PSObject[]]$Tests ) $severityRank = @{ 'red' = 0; 'failed' = 0; 'yellow' = 1; 'warning' = 1; 'green' = 3; 'passed' = 3 } $rankedTests = foreach ($test in $Tests) { $rank = if ($test.TestHealth -ne 'N/A' -and $severityRank.ContainsKey($test.TestHealth.ToLowerInvariant())) { $severityRank[$test.TestHealth.ToLowerInvariant()] } else { 2 } $test | Add-Member -MemberType NoteProperty -Name SeverityRank -Value $rank -PassThru } return @(@($rankedTests) | Sort-Object -Property SeverityRank -Stable | Select-Object -Property GroupName, GroupHealth, TestName, TestHealth, TestDescription) } function Get-VcfCheckVsanHealthAllSubTests { <# .SYNOPSIS Retrieves all sub-test results across categories for a vSAN cluster health evaluation. .DESCRIPTION Aggregates sub-tests across Cluster, Network, Limits, Physical Disk, Data, Encryption, Hardware Compatibility, and File Service categories from a vSAN cluster health object, returning a severity-sorted list of all sub-tests. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [PSObject[]] Severity-sorted array of all sub-test report rows for the cluster. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $allTests = [System.Collections.Generic.List[Object]]::new() $allTests.AddRange([Object[]]@(Get-VcfCheckVsanHealthClusterTests -HealthResult $HealthResult)) $allTests.AddRange([Object[]]@(Get-VcfCheckVsanHealthNetworkTests -HealthResult $HealthResult)) $allTests.AddRange([Object[]]@(Get-VcfCheckVsanHealthLimitTests -HealthResult $HealthResult)) $allTests.AddRange([Object[]]@(Get-VcfCheckVsanHealthPhysicalDiskTests -HealthResult $HealthResult)) $allTests.AddRange([Object[]]@(Get-VcfCheckVsanHealthDataTests -HealthResult $HealthResult)) $allTests.AddRange([Object[]]@(Get-VcfCheckVsanHealthMiscTests -HealthResult $HealthResult)) return Sort-VcfCheckVsanHealthTestsBySeverity -Tests $allTests.ToArray() } function Get-VcfCheckVsanHealthFailingSubTests { <# .SYNOPSIS Extracts red/yellow failing sub-tests from a vSAN cluster health evaluation. .DESCRIPTION Filters the full set of sub-tests returned by Get-VcfCheckVsanHealthAllSubTests to isolate red/yellow findings - explicitly excluding 'N/A' sub-tests that were never applicable rather than treating them as failures - formatting the result as a semicolon-separated summary string. Returns 'N/A' if no failing sub-tests are detected. .PARAMETER HealthResult A vSAN cluster health result object returned by Test-VsanClusterHealth. .OUTPUTS [String] Semicolon-delimited summary string of failing sub-tests, or 'N/A'. #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $true)] [PSObject]$HealthResult ) $allSubTests = @(Get-VcfCheckVsanHealthAllSubTests -HealthResult $HealthResult) if ($allSubTests.Count -eq 0) { return 'N/A' } $failingValues = @('red', 'failed', 'yellow', 'warning') $failingTests = foreach ($test in $allSubTests) { if ($test.TestHealth -and $test.TestHealth.ToLowerInvariant() -in $failingValues) { $description = if ($test.TestDescription) { $test.TestDescription } else { $test.TestName } "$($test.GroupName): $($test.TestName) - $description" } } if (@($failingTests).Count -eq 0) { return 'N/A' } return ($failingTests -join '; ') } |