diff --git a/CHANGELOG.md b/CHANGELOG.md index effd86a..77e6a44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.3.4] - 2026-06-29 + +### Fixed + +- `Get-AppFabricStatus` (SE branch) now uses `Get-SPCacheClusterHealth.Hosts` + (canonical FQDN list) and `Get-SPCacheClusterInfo.Size` as the source of + truth, instead of relying on `Get-SPCacheHostConfig` which returns null on + several SE builds. Port/Size/ServiceName/CacheStatus degrade gracefully to + the SP DC defaults (22233, AppFabricCachingService, Up when the SP service + instance is Online) when `Get-SPCacheHostConfig` cannot resolve the host. + Removes the dead AppFabric fallback added in 2.3.3 (those cmdlets are not + present on SE) (#49). + ## [2.3.3] - 2026-06-29 ### Fixed diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 146a893..b1d01d8 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,13 +1,14 @@ # SPSWeather - Release Notes -## [2.3.3] - 2026-06-29 +## [2.3.4] - 2026-06-29 ### Fixed - Distributed Cache report on SharePoint Subscription Edition now populates - Port / Size / ServiceName / CacheStatus on hosts where `Get-SPCacheHostConfig` - returns null: the function falls back to the historical AppFabric cmdlets - (`Use-CacheCluster` + `Get-CacheHost` + `Get-AFCacheHostConfiguration`), which - are still installed on SE. + Port / Size / ServiceName / CacheStatus from the SE-native cluster cmdlets + (`Get-SPCacheClusterHealth` + `Get-SPCacheClusterInfo`), with a graceful + degradation to the SP DC defaults (22233, AppFabricCachingService) when + `Get-SPCacheHostConfig` cannot resolve the host. Removes the AppFabric + fallback from 2.3.3 (those cmdlets do not exist on SE). A full list of changes can be found in the [change log](CHANGELOG.md). diff --git a/src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1 b/src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1 index b863ae3..4679281 100644 --- a/src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1 +++ b/src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1 @@ -42,72 +42,56 @@ $productVersion = (Get-Command $fullPath).FileVersionInfo } if ($productVersion.FileMajorPart -eq 16 -and $productVersion.FileBuildPart -gt 13000) { - Write-Verbose -Message "Use-CacheCluster' cmdlet not required for SPSE - using newer Get-SPCacheHostConfig" + Write-Verbose -Message 'Subscription Edition: using Get-SPCacheClusterHealth/Info as the source of truth' $allSPServers = (Get-SPServer | Where-Object -FilterScript { $_.Role -ne 'Invalid' }).Name - $dcInstances = Get-SPServiceInstance | Where-Object -FilterScript { - $_.GetType().Name -eq 'SPDistributedCacheServiceInstance' + $dcInstances = @(Get-SPServiceInstance | Where-Object -FilterScript { + $_.GetType().Name -eq 'SPDistributedCacheServiceInstance' + }) + # Canonical host list (FQDNs) and cluster Size come from the SE-native cmdlets. + $clusterHealth = Get-SPCacheClusterHealth -ErrorAction SilentlyContinue + $clusterInfo = Get-SPCacheClusterInfo -ErrorAction SilentlyContinue + $clusterSize = if ($null -ne $clusterInfo) { "$($clusterInfo.Size)" } else { '' } + $clusterHosts = @() + if ($null -ne $clusterHealth -and $null -ne $clusterHealth.Hosts) { + $clusterHosts = @($clusterHealth.Hosts | ForEach-Object { "$_" }) } $reportedServers = New-Object -TypeName System.Collections.ArrayList - foreach ($dcInst in $dcInstances) { - $isMailInfo = $true - $hostFqdn = "$($dcInst.Server.Address)" - $cacheserver = $hostFqdn.Split('.')[0] - $cacheHostConfig = $null + foreach ($clusterFqdn in $clusterHosts) { + $cacheserver = $clusterFqdn.Split('.')[0] + # Match the SP service instance for this host (Server.Address is short) + $dcInst = $dcInstances | Where-Object -FilterScript { + "$($_.Server.Address)" -eq $cacheserver -or + "$($_.Server.Address)" -eq $clusterFqdn + } | Select-Object -First 1 + $SPInstanceStatus = if ($null -ne $dcInst) { "$($dcInst.Status)" } else { 'Unknown' } + # Try Get-SPCacheHostConfig with the FQDN; degrade gracefully if it + # returns null (some SE builds cannot resolve the host name). + $cacheHostConfig = Get-SPCacheHostConfig -HostName $clusterFqdn -ErrorAction SilentlyContinue $cacheHost = $null - if ($dcInst.Status -eq 'Online') { - # The cluster may store the host as FQDN or as short name; try both. - foreach ($tryHost in @($hostFqdn, $cacheserver)) { - if ([string]::IsNullOrEmpty($tryHost)) { continue } - $cacheHostConfig = Get-SPCacheHostConfig -HostName $tryHost -ErrorAction SilentlyContinue - if ($null -ne $cacheHostConfig) { break } - } - if ($null -ne $cacheHostConfig) { - $cacheHost = Get-SPCacheHost -HostName $cacheHostConfig.HostName -CachePort $cacheHostConfig.CachePort -ErrorAction SilentlyContinue - } - # Fallback: Get-SPCacheHostConfig occasionally returns null on SE - # even when the SP service instance is Online. The underlying - # AppFabric cmdlets are still installed - use them to recover - # Port/Size/ServiceName/CacheStatus. - if ($null -eq $cacheHostConfig) { - try { - Use-CacheCluster -ErrorAction Stop | Out-Null - $afHosts = Get-CacheHost -ErrorAction SilentlyContinue - $afHost = $afHosts | Where-Object -FilterScript { - $_.HostName -eq $hostFqdn -or - $_.HostName -eq $cacheserver -or - ($_.HostName -like "$cacheserver.*") - } | Select-Object -First 1 - if ($null -ne $afHost) { - $cacheHost = $afHost - $cacheHostConfig = Get-AFCacheHostConfiguration -ComputerName $afHost.HostName -CachePort $afHost.PortNo -ErrorAction SilentlyContinue - if ($null -ne $cacheHostConfig -and -not ($cacheHostConfig.PSObject.Properties.Name -contains 'CachePort')) { - Add-Member -InputObject $cacheHostConfig -NotePropertyName CachePort -NotePropertyValue $afHost.PortNo -Force - } - } - } - catch { - Write-Verbose -Message "AppFabric fallback failed for '$cacheserver': $($_.Exception.Message)" - } - } + if ($null -ne $cacheHostConfig) { + $cacheHost = Get-SPCacheHost -HostName $cacheHostConfig.HostName -CachePort $cacheHostConfig.CachePort -ErrorAction SilentlyContinue } - if ($null -ne $cacheHost -and $cacheHost.Status -ne 'Up') { $isMailInfo = $false } - $SPInstanceStatus = $dcInst.Status - if ($SPInstanceStatus -ne 'Online') { $isMailInfo = $false } + $port = if ($null -ne $cacheHostConfig) { "$($cacheHostConfig.CachePort)" } else { '22233' } + $size = if ($null -ne $cacheHostConfig) { "$($cacheHostConfig.Size)" } else { $clusterSize } + $serviceName = if ($null -ne $cacheHost) { "$($cacheHost.ServiceName)" } else { 'AppFabricCachingService' } + $cacheStatus = if ($null -ne $cacheHost) { "$($cacheHost.Status)" } + elseif ($SPInstanceStatus -eq 'Online') { 'Up' } + else { 'Unknown' } + $isMailInfo = ($SPInstanceStatus -eq 'Online' -and $cacheStatus -eq 'Up') [void]$tbAppFabricStatus.Add([AppFabricStatus]@{ Farm = $params.Farm Server = $cacheserver; - Port = $cacheHostConfig.CachePort; - ServiceName = $cacheHost.ServiceName; - Size = $cacheHostConfig.Size; - CacheStatus = $cacheHost.Status; + Port = $port; + ServiceName = $serviceName; + Size = $size; + CacheStatus = $cacheStatus; SPInstanceStatus = $SPInstanceStatus; IsInfo = $isMailInfo; }) [void]$reportedServers.Add($cacheserver) } - # SP servers that are not part of the cache cluster: report them as - # informational (IsInfo=$true) instead of red alerts, since hosting - # Distributed Cache on a subset of servers is a legitimate topology. + # Servers that are not part of the cache cluster: informational row + # (legitimate topology to host Distributed Cache on a subset of servers). foreach ($srv in $allSPServers) { if ($reportedServers -notcontains $srv) { [void]$tbAppFabricStatus.Add([AppFabricStatus]@{ diff --git a/src/Modules/SPSWeather.Common/SPSWeather.Common.psd1 b/src/Modules/SPSWeather.Common/SPSWeather.Common.psd1 index b892826..c54dc2f 100644 --- a/src/Modules/SPSWeather.Common/SPSWeather.Common.psd1 +++ b/src/Modules/SPSWeather.Common/SPSWeather.Common.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'SPSWeather.Common.psm1' - ModuleVersion = '2.3.3' + ModuleVersion = '2.3.4' GUID = 'c39bd612-8520-4e65-9037-80060894d654' Author = 'Jean-Cyril DROUHIN' CompanyName = 'luigilink' diff --git a/tests/SPSWeather.Common.Tests.ps1 b/tests/SPSWeather.Common.Tests.ps1 index 8eea9fb..4dd7b05 100644 --- a/tests/SPSWeather.Common.Tests.ps1 +++ b/tests/SPSWeather.Common.Tests.ps1 @@ -36,7 +36,7 @@ Describe 'SPSWeather.Common module' { } It 'manifest version is 2.0.0 or higher' { - (Test-ModuleManifest -Path $modulePath).Version | Should -BeGreaterOrEqual ([version]'2.3.3') + (Test-ModuleManifest -Path $modulePath).Version | Should -BeGreaterOrEqual ([version]'2.3.4') } It 'exports exactly the expected public functions' {