From a615bff8404844755fde0b982251118889f776ab Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Thu, 22 Jan 2026 19:03:33 -0600 Subject: [PATCH 1/7] improve health report output --- src/modules/SdnDiag.Health.psm1 | 125 ++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 29 deletions(-) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index 440f962e..55571598 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -223,7 +223,7 @@ function New-SdnHealthTest { $object = [PSCustomObject]@{ Name = $Name - Result = 'PASS' # default to PASS. Allowed values are PASS, WARN, FAIL + Result = 'PASS' # default to PASS. Allowed values are PASS, WARNING, FAIL OccurrenceTime = [System.DateTime]::UtcNow Properties = @() Remediation = @() @@ -239,9 +239,10 @@ function New-SdnRoleHealthReport { ) $object = [PSCustomObject]@{ + Role = $Role ComputerName = $env:COMPUTERNAME - Result = 'PASS' # default to PASS. Allowed values are PASS, WARN, FAIL + Result = 'PASS' # default to PASS. Allowed values are PASS, WARNING, FAIL OccurrenceTime = [System.DateTime]::UtcNow HealthTest = @() # array of New-SdnHealthTest objects } @@ -258,7 +259,7 @@ function New-SdnFabricHealthReport { $object = [PSCustomObject]@{ OccurrenceTime = [System.DateTime]::UtcNow Role = $Role - Result = 'PASS' # default to PASS. Allowed values are PASS, WARN, FAIL + Result = 'PASS' # default to PASS. Allowed values are PASS, WARNING, FAIL RoleTest = @() # array of New-SdnRoleHealthReport objects } @@ -289,9 +290,22 @@ function Write-HealthValidationInfo { [String]$Name, [Parameter(Mandatory = $false)] - [String[]]$Remediation + [String[]]$Remediation, + + [Parameter(Mandatory = $false)] + [ValidateSet('WARNING', 'FAIL')] + [string]$Severity ) + switch ($Severity) { + 'WARNING' { + $foregroundColor = 'Yellow' + } + 'FAIL' { + $foregroundColor = 'Red' + } + } + $details = Get-HealthData -Property 'HealthValidations' -Id $Name $outputString += "`r`n`r`n" @@ -300,10 +314,11 @@ function Write-HealthValidationInfo { $outputString += "`r`n`r`n" $outputString += "Description:`t$($details.Description)`r`n" $outputString += "Impact:`t`t$($details.Impact)`r`n" + $outputString += "Severity:`t$Severity`r`n" if (-NOT [string]::IsNullOrEmpty($Remediation)) { - if ($Remediation -ieq [array]) { - $outputString += "Remediation:`r`n`t- $($Remediation -join "`r`n`t - ")`r`n" + if ($Remediation -is [System.Collections.ICollection] -or $Remediation -is [System.Array]) { + $outputString += "Remediation:`r`n`t- $($Remediation -join "`r`n`t- ")`r`n" } else { $outputString += "Remediation:`t$Remediation`r`n" @@ -317,8 +332,7 @@ function Write-HealthValidationInfo { } $outputString += "`r`n--------------------------`r`n" - - $outputString | Write-Host -ForegroundColor Yellow + $outputString | Write-Host -ForegroundColor $foregroundColor } function Debug-SdnFabricInfrastructure { @@ -516,19 +530,17 @@ function Debug-SdnFabricInfrastructure { } } - # evaluate the results of the tests and determine if any completed with Warning or FAIL - # if so, we will want to set the Result of the report to reflect this - foreach ($test in $healthReport) { - if ($test.Result -ieq 'WARN') { - $roleHealthReport.Result = 'WARN' + $roleHealthReport.RoleTest += $healthReport + foreach ($test in $roleHealthReport.RoleTest.HealthTest) { + if ($test.Result -ieq 'WARNING') { + $roleHealthReport.Result = 'WARNING' } - if ($test.Result -ieq 'FAIL') { + elseif ($test.Result -ieq 'FAIL') { $roleHealthReport.Result = 'FAIL' break } } - $roleHealthReport.RoleTest += $healthReport $aggregateHealthReport += $roleHealthReport } } @@ -539,6 +551,51 @@ function Debug-SdnFabricInfrastructure { finally { if ($aggregateHealthReport) { + # Display SDN Health Validation Report Header + $reportHeader = @" + +=============================================================================== + SDN HEALTH VALIDATION REPORT +=============================================================================== + +"@ + Write-Host $reportHeader -ForegroundColor Cyan + + # Calculate aggregate summary + $allRoles = ($aggregateHealthReport | Select-Object -ExpandProperty Role) -join ', ' + $allNodes = ($aggregateHealthReport | ForEach-Object { $_.RoleTest.ComputerName } | Sort-Object -Unique) -join ', ' + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss UTC" + + # Determine overall health state (worst case wins: FAIL > WARNING > PASS) + $overallState = 'PASS' + foreach ($report in $aggregateHealthReport) { + if ($report.Result -ieq 'FAIL') { + $overallState = 'FAIL' + break + } + elseif ($report.Result -ieq 'WARNING' -and $overallState -ne 'FAIL') { + $overallState = 'WARNING' + } + } + + # Set color based on overall state + $stateColor = switch ($overallState) { + 'PASS' { 'Green' } + 'WARNING' { 'Yellow' } + 'FAIL' { 'Red' } + } + + # Display summary + Write-Host "Report Generated: " -NoNewline -ForegroundColor Gray + Write-Host $timestamp -ForegroundColor White + Write-Host "Roles Tested: " -NoNewline -ForegroundColor Gray + Write-Host $allRoles -ForegroundColor White + Write-Host "Nodes Tested: " -NoNewline -ForegroundColor Gray + Write-Host $allNodes -ForegroundColor White + Write-Host "Overall State: " -NoNewline -ForegroundColor Gray + Write-Host $overallState -ForegroundColor $stateColor + Write-Host "" + # enumerate all the roles that were tested so we can determine if any completed with Warning or FAIL $aggregateHealthReport | ForEach-Object { if ($_.Result -ine 'PASS') { @@ -554,13 +611,23 @@ function Debug-SdnFabricInfrastructure { $remediationList = [System.Collections.ArrayList]::new() $_.Remediation | ForEach-Object { [void]$remediationList.Add($_) } - Write-HealthValidationInfo -ComputerName $c -Name $_.Name -Remediation $remediationList + Write-HealthValidationInfo -ComputerName $c -Name $_.Name -Remediation $remediationList -Severity $_.Result } } } } } + # Display SDN Health Validation Report Footer + $reportFooter = @" + +=============================================================================== + END OF SDN HEALTH VALIDATION REPORT +=============================================================================== + +"@ + Write-Host $reportFooter -ForegroundColor Cyan + # save the aggregate health report to cache so we can use it for further analysis $script:SdnDiagnostics_Health.Cache = $aggregateHealthReport } @@ -653,12 +720,12 @@ function Debug-SdnNetworkController { } } - # enumerate all the tests performed so we can determine if any completed with WARN or FAIL - # if any of the tests completed with WARN, we will set the aggregate result to WARN + # enumerate all the tests performed so we can determine if any completed with WARNING or FAIL + # if any of the tests completed with WARNING, we will set the aggregate result to WARNING # if any of the tests completed with FAIL, we will set the aggregate result to FAIL and then break out of the foreach loop # we will skip tests with PASS, as that is the default value foreach ($test in $healthReport.HealthTest) { - if ($test.Result -eq 'WARN') { + if ($test.Result -eq 'WARNING') { $healthReport.Result = $test.Result } elseif ($test.Result -eq 'FAIL') { @@ -721,12 +788,12 @@ function Debug-SdnServer { ) } - # enumerate all the tests performed so we can determine if any completed with WARN or FAIL - # if any of the tests completed with WARN, we will set the aggregate result to WARN + # enumerate all the tests performed so we can determine if any completed with WARNING or FAIL + # if any of the tests completed with WARNING, we will set the aggregate result to WARNING # if any of the tests completed with FAIL, we will set the aggregate result to FAIL and then break out of the foreach loop # we will skip tests with PASS, as that is the default value foreach ($test in $healthReport.HealthTest) { - if ($test.Result -eq 'WARN') { + if ($test.Result -eq 'WARNING') { $healthReport.Result = $test.Result } elseif ($test.Result -eq 'FAIL') { @@ -767,12 +834,12 @@ function Debug-SdnLoadBalancerMux { ) } - # enumerate all the tests performed so we can determine if any completed with WARN or FAIL - # if any of the tests completed with WARN, we will set the aggregate result to WARN + # enumerate all the tests performed so we can determine if any completed with WARNING or FAIL + # if any of the tests completed with WARNING, we will set the aggregate result to WARNING # if any of the tests completed with FAIL, we will set the aggregate result to FAIL and then break out of the foreach loop # we will skip tests with PASS, as that is the default value foreach ($test in $healthReport.HealthTest) { - if ($test.Result -eq 'WARN') { + if ($test.Result -eq 'WARNING') { $healthReport.Result = $test.Result } elseif ($test.Result -eq 'FAIL') { @@ -992,7 +1059,7 @@ function Test-SdnNetworkControllerApiNameResolution { } catch { $_ | Trace-Exception - "`t- Investigate DNS resolution failure against DNS server {0} for name {1}" -f $dnsServer, $Endpoint + "Investigate DNS resolution failure against DNS server {0} for name {1}" -f $dnsServer, $Endpoint } } @@ -1080,7 +1147,7 @@ function Test-SdnCertificateMultiple { "`t- Thumbprint: {0} Subject: {1} Issuer: {2} NotAfter: {3}" -f $_.Thumbprint, $_.Subject, $_.Issuer, $_.NotAfter } - $sdnHealthTest.Result = 'WARN' + $sdnHealthTest.Result = 'WARNING' $sdnHealthTest.Remediation = "Examine and cleanup the certificates if no longer needed:`r`n{0}" -f ($certDetails -join "`r`n") } } @@ -1377,7 +1444,7 @@ function Test-SdnHostAgentConnectionStateToApiService { if ($tcpConnection.State -ine 'Established') { $serviceState = Get-Service -Name NCHostAgent -ErrorAction Stop if ($serviceState.Status -ine 'Running') { - $sdnHealthTest.Result = 'WARN' + $sdnHealthTest.Result = 'WARNING' $sdnHealthTest.Remediation += "Ensure the NCHostAgent service is running." } else { @@ -1888,7 +1955,7 @@ function Test-SdnAdapterPerformanceSetting { if ($adaptersToRepair.Count -gt 0) { $sdnHealthTest.Result = 'WARNING' foreach ($adapter in $adaptersToRepair) { - $sdnHealthTest.Remediation += "Use Invoke-SdnRemediationScript -ScriptName 'ConfigureForwardOptimization.ps1' -ArgumentList @{AdapterName='$adapter'; NoRestart=`$false}" + $sdnHealthTest.Remediation += "Run 'Invoke-SdnRemediationScript -ScriptName 'ConfigureForwardOptimization.ps1' -ArgumentList @{AdapterName='$adapter'; NoRestart=`$false}' on the impacted node to enable Forwarding Optimization." } } } From 1091bd66db5b842bad85f245d354699898c3e4c0 Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Fri, 23 Jan 2026 15:02:36 -0600 Subject: [PATCH 2/7] add transcript --- src/modules/SdnDiag.Health.psm1 | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index 55571598..5a62df2f 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -390,6 +390,14 @@ function Debug-SdnFabricInfrastructure { $script:SdnDiagnostics_Health.Cache = $null $aggregateHealthReport = @() + $dateTimeNow = Get-Date -Format 'yyyyMMdd-HHmmss' + $dateTimeNowFormatted = Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC' + + $transcriptDirectory = Get-WorkingDirectory + $transcriptPath = "{0}\SdnFabricHealthReport_{1}.txt" -f $transcriptDirectory, $dateTimeNow + Start-Transcript -Path $transcriptPath -Force + "Starting SDN Fabric Infrastructure health validation at {0}" -f $dateTimeNowFormatted | Trace-Output -Level:Information + if (Test-ComputerNameIsLocal -ComputerName $NetworkController) { Confirm-IsNetworkController } @@ -427,7 +435,7 @@ function Debug-SdnFabricInfrastructure { $Role = $Role | Sort-Object -Unique foreach ($object in $Role) { - "Processing tests for {0} role" -f $object.ToString() | Trace-Output -Level:Verbose + "Processing tests for {0} role" -f $object.ToString() | Trace-Output -Level:Information $config = Get-SdnModuleConfiguration -Role $object.ToString() $roleHealthReport = New-SdnFabricHealthReport -Role $object.ToString() @@ -549,6 +557,9 @@ function Debug-SdnFabricInfrastructure { $_ | Write-Error } finally { + Stop-Transcript + "Transcript saved to {0}" -f $transcriptPath | Trace-Output -Level:Information + if ($aggregateHealthReport) { # Display SDN Health Validation Report Header @@ -563,9 +574,8 @@ function Debug-SdnFabricInfrastructure { # Calculate aggregate summary $allRoles = ($aggregateHealthReport | Select-Object -ExpandProperty Role) -join ', ' - $allNodes = ($aggregateHealthReport | ForEach-Object { $_.RoleTest.ComputerName } | Sort-Object -Unique) -join ', ' - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss UTC" - + $allSdnNodes = ($aggregateHealthReport | ForEach-Object { $_.RoleTest.ComputerName } | Sort-Object -Unique) -join ', ' + # Determine overall health state (worst case wins: FAIL > WARNING > PASS) $overallState = 'PASS' foreach ($report in $aggregateHealthReport) { @@ -587,11 +597,11 @@ function Debug-SdnFabricInfrastructure { # Display summary Write-Host "Report Generated: " -NoNewline -ForegroundColor Gray - Write-Host $timestamp -ForegroundColor White + Write-Host $dateTimeNowFormatted -ForegroundColor White Write-Host "Roles Tested: " -NoNewline -ForegroundColor Gray Write-Host $allRoles -ForegroundColor White Write-Host "Nodes Tested: " -NoNewline -ForegroundColor Gray - Write-Host $allNodes -ForegroundColor White + Write-Host $allSdnNodes -ForegroundColor White Write-Host "Overall State: " -NoNewline -ForegroundColor Gray Write-Host $overallState -ForegroundColor $stateColor Write-Host "" From 929b9ca3aa3fbfaddbe448e9114a34ae2f82b20f Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Fri, 23 Jan 2026 15:25:44 -0600 Subject: [PATCH 3/7] save latest changes --- src/modules/SdnDiag.Health.psm1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index 5a62df2f..3f46e47d 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -395,7 +395,7 @@ function Debug-SdnFabricInfrastructure { $transcriptDirectory = Get-WorkingDirectory $transcriptPath = "{0}\SdnFabricHealthReport_{1}.txt" -f $transcriptDirectory, $dateTimeNow - Start-Transcript -Path $transcriptPath -Force + $null = Start-Transcript -Path $transcriptPath -Force "Starting SDN Fabric Infrastructure health validation at {0}" -f $dateTimeNowFormatted | Trace-Output -Level:Information if (Test-ComputerNameIsLocal -ComputerName $NetworkController) { @@ -557,9 +557,6 @@ function Debug-SdnFabricInfrastructure { $_ | Write-Error } finally { - Stop-Transcript - "Transcript saved to {0}" -f $transcriptPath | Trace-Output -Level:Information - if ($aggregateHealthReport) { # Display SDN Health Validation Report Header @@ -643,6 +640,9 @@ function Debug-SdnFabricInfrastructure { } } + $null = Stop-Transcript + "Transcript saved to {0}" -f $transcriptPath | Trace-Output -Level:Information + if ($script:SdnDiagnostics_Health.Cache) { "Results for fabric health have been saved to cache for further analysis. Use 'Get-SdnFabricInfrastructureResult' to examine the results." | Trace-Output return $script:SdnDiagnostics_Health.Cache From 23fe562753d841b60e4aa026c94c16f2a6df0415 Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Fri, 23 Jan 2026 15:27:07 -0600 Subject: [PATCH 4/7] update display of warning or fail --- src/modules/SdnDiag.Health.psm1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index 3f46e47d..0464bc0c 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -299,9 +299,11 @@ function Write-HealthValidationInfo { switch ($Severity) { 'WARNING' { + $Severity = 'Warning' $foregroundColor = 'Yellow' } 'FAIL' { + $Severity = 'Failure' $foregroundColor = 'Red' } } From 695d7b3829cc91bc6006858ab318c0b42ec03a47 Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Fri, 23 Jan 2026 16:11:19 -0600 Subject: [PATCH 5/7] save --- src/modules/SdnDiag.Health.psm1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index 6f2823c4..0464bc0c 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -559,9 +559,6 @@ function Debug-SdnFabricInfrastructure { $_ | Write-Error } finally { - Stop-Transcript - "Transcript saved to {0}" -f $transcriptPath | Trace-Output -Level:Information - if ($aggregateHealthReport) { # Display SDN Health Validation Report Header From 3c302eedea3edfed683133607cbefec8c87be21d Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Fri, 23 Jan 2026 16:14:25 -0600 Subject: [PATCH 6/7] skip summary --- src/modules/SdnDiag.Health.psm1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index 0464bc0c..d0b01919 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -354,6 +354,8 @@ function Debug-SdnFabricInfrastructure { Enter a variable that contains a certificate or a command or expression that gets the certificate. .PARAMETER NcRestCredential Specifies a user account that has permission to perform this action against the Network Controller REST API. The default is the current user. + .PARAMETER SkipSummaryDisplay + Switch parameter to skip displaying the summary of results to the console. .EXAMPLE PS> Debug-SdnFabricInfrastructure .EXAMPLE @@ -387,7 +389,11 @@ function Debug-SdnFabricInfrastructure { [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')] - [X509Certificate]$NcRestCertificate + [X509Certificate]$NcRestCertificate, + + [Parameter(Mandatory = $false, ParameterSetName = 'Role')] + [Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')] + [switch]$SkipSummaryDisplay ) $script:SdnDiagnostics_Health.Cache = $null @@ -559,7 +565,7 @@ function Debug-SdnFabricInfrastructure { $_ | Write-Error } finally { - if ($aggregateHealthReport) { + if ($aggregateHealthReport -and (-not $SkipSummaryDisplay)) { # Display SDN Health Validation Report Header $reportHeader = @" From dea8df9ff50c0a8f31f1a4fc81f40f75ae47a6bd Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Fri, 23 Jan 2026 16:21:24 -0600 Subject: [PATCH 7/7] save formatting changes --- src/modules/SdnDiag.Health.psm1 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/SdnDiag.Health.psm1 b/src/modules/SdnDiag.Health.psm1 index d0b01919..c123c94c 100644 --- a/src/modules/SdnDiag.Health.psm1 +++ b/src/modules/SdnDiag.Health.psm1 @@ -293,7 +293,7 @@ function Write-HealthValidationInfo { [String[]]$Remediation, [Parameter(Mandatory = $false)] - [ValidateSet('WARNING', 'FAIL')] + [ValidateSet('WARNING', 'FAIL', 'FAILURE', IgnoreCase = $true)] [string]$Severity ) @@ -306,6 +306,10 @@ function Write-HealthValidationInfo { $Severity = 'Failure' $foregroundColor = 'Red' } + 'FAILURE' { + $Severity = 'Failure' + $foregroundColor = 'Red' + } } $details = Get-HealthData -Property 'HealthValidations' -Id $Name @@ -585,10 +589,10 @@ function Debug-SdnFabricInfrastructure { $overallState = 'PASS' foreach ($report in $aggregateHealthReport) { if ($report.Result -ieq 'FAIL') { - $overallState = 'FAIL' + $overallState = 'FAILURE' break } - elseif ($report.Result -ieq 'WARNING' -and $overallState -ne 'FAIL') { + elseif ($report.Result -ieq 'WARNING' -and $overallState -ne 'FAILURE') { $overallState = 'WARNING' } } @@ -597,7 +601,7 @@ function Debug-SdnFabricInfrastructure { $stateColor = switch ($overallState) { 'PASS' { 'Green' } 'WARNING' { 'Yellow' } - 'FAIL' { 'Red' } + 'FAILURE' { 'Red' } } # Display summary