diff --git a/CHANGELOG.md b/CHANGELOG.md index 7033259..4046692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.3.2] - 2026-06-29 + +### Fixed + +- `Get-AppFabricStatus` (SE branch) iterates Distributed Cache hosts via + `Get-SPServiceInstance` (filtered on `SPDistributedCacheServiceInstance`) and + uses the instance's `Server.Address`, falling back to the short name when the + cluster stores a different form. This fixes the missing Port / Size / + ServiceName / CacheStatus on the actual DC host (typically a WFE) on + SharePoint Subscription Edition (#45). + +### Changed + +- Farm servers that are not part of the Distributed Cache cluster are now + reported as 'Not a cache host' (informational, `IsInfo = $true`) instead of + a red 'SPService Not Found' alert, since hosting Distributed Cache on a + subset of servers (often a single WFE) is a legitimate topology (#45). + ## [2.3.1] - 2026-06-29 ### Fixed diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5a21917..00de0b1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,15 +1,19 @@ # SPSWeather - Release Notes -## [2.3.1] - 2026-06-29 +## [2.3.2] - 2026-06-29 ### Fixed -- `Clear-SPSLog` now actually targets `.log` files (it was filtering on an - undefined variable). Retention 0 disables pruning. +- Distributed Cache report now collects Port / Size / ServiceName / CacheStatus + for the actual cache host on SharePoint Subscription Edition (previously the + details were empty because the cluster expected the FQDN while the call used + the short name). ### Changed -- New config setting `LogRetentionDays` (default 180, 0 disables) replaces the - hardcoded 180-day retention, mirroring `JsonHistoryRetentionDays`. +- Servers that are not part of the Distributed Cache cluster are reported as + 'Not a cache host' (informational) instead of a red 'SPService Not Found' + alert. Hosting Distributed Cache on a subset of servers (often a single WFE) + is a legitimate topology. 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 d91c8f5..290a53b 100644 --- a/src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1 +++ b/src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1 @@ -42,42 +42,60 @@ $productVersion = (Get-Command $fullPath).FileVersionInfo } if ($productVersion.FileMajorPart -eq 16 -and $productVersion.FileBuildPart -gt 13000) { - Write-Verbose -Message 'Get list of SharePoint Servers' - $listSPServer = (Get-SPServer | Where-Object -FilterScript {$_.Role -ne 'Invalid'}).Name - Write-Verbose -Message "'Use-CacheCluster' cmdlet not required for SPSE" - Write-Verbose -Message "Using newer 'Get-SPCacheHostConfig' cmdlet for SPSE" - foreach ($cacheserver in $listSPServer) { - $cacheHostConfig = Get-SPCacheHostConfig -HostName $cacheserver -ErrorAction SilentlyContinue - if ($null -ne $cacheHostConfig) { - $isMailInfo = $true - $cacheHost = Get-SPCacheHost -HostName $cacheHostConfig.HostName -CachePort $cacheHostConfig.CachePort - } - $spCacheSvc = Get-SPServiceInstance -Server $cacheserver | Where-Object -FilterScript { - $_.GetType().Name -eq 'SPDistributedCacheServiceInstance' - } - if ($cacheHost.Status -ne 'Up') { - $isMailInfo = $false - } - if ($null -ne $spCacheSvc) { - $SPInstanceStatus = $spCacheSvc.Status - if ($SPInstanceStatus -ne 'Online') { - $isMailInfo = $false + Write-Verbose -Message "Use-CacheCluster' cmdlet not required for SPSE - using newer Get-SPCacheHostConfig" + $allSPServers = (Get-SPServer | Where-Object -FilterScript { $_.Role -ne 'Invalid' }).Name + $dcInstances = Get-SPServiceInstance | Where-Object -FilterScript { + $_.GetType().Name -eq 'SPDistributedCacheServiceInstance' + } + $reportedServers = New-Object -TypeName System.Collections.ArrayList + foreach ($dcInst in $dcInstances) { + $isMailInfo = $true + $hostFqdn = "$($dcInst.Server.Address)" + $cacheserver = $hostFqdn.Split('.')[0] + $cacheHostConfig = $null + $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 } } - else { - $SPInstanceStatus = 'SPService Not Found' - $isMailInfo = $false - } + if ($null -ne $cacheHost -and $cacheHost.Status -ne 'Up') { $isMailInfo = $false } + $SPInstanceStatus = $dcInst.Status + if ($SPInstanceStatus -ne 'Online') { $isMailInfo = $false } [void]$tbAppFabricStatus.Add([AppFabricStatus]@{ - Farm = $params.Farm - Server = $cacheserver; - Port = $cacheHostConfig.CachePort; - ServiceName = $cacheHost.ServiceName; - Size = $cacheHostConfig.Size; - CacheStatus = $cacheHost.Status; - SPInstanceStatus = $SPInstanceStatus; - IsInfo = $isMailInfo; - }) + Farm = $params.Farm + Server = $cacheserver; + Port = $cacheHostConfig.CachePort; + ServiceName = $cacheHost.ServiceName; + Size = $cacheHostConfig.Size; + CacheStatus = $cacheHost.Status; + 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. + foreach ($srv in $allSPServers) { + if ($reportedServers -notcontains $srv) { + [void]$tbAppFabricStatus.Add([AppFabricStatus]@{ + Farm = $params.Farm + Server = $srv; + Port = ''; + ServiceName = ''; + Size = ''; + CacheStatus = ''; + SPInstanceStatus = 'Not a cache host'; + IsInfo = $true; + }) + } } return $tbAppFabricStatus } diff --git a/src/Modules/SPSWeather.Common/Public/Join-HtmlBodyFromPSo.ps1 b/src/Modules/SPSWeather.Common/Public/Join-HtmlBodyFromPSo.ps1 index d60ee7a..7242394 100644 --- a/src/Modules/SPSWeather.Common/Public/Join-HtmlBodyFromPSo.ps1 +++ b/src/Modules/SPSWeather.Common/Public/Join-HtmlBodyFromPSo.ps1 @@ -247,18 +247,24 @@ $htmlTableRows += "$($AppFabricStatusItem.Port)" $htmlTableRows += "$($AppFabricStatusItem.ServiceName)" $htmlTableRows += "$($AppFabricStatusItem.Size)" - if ($AppFabricStatusItem.CacheStatus -ne 'Up') { - $htmlTableRows += "$($AppFabricStatusItem.CacheStatus)" - } - else { + if ($AppFabricStatusItem.CacheStatus -eq 'Up') { $htmlTableRows += "$($AppFabricStatusItem.CacheStatus)" } - if ($AppFabricStatusItem.SPInstanceStatus -ne 'Online') { - $htmlTableRows += "$($AppFabricStatusItem.SPInstanceStatus)" + elseif ([string]::IsNullOrEmpty([string]$AppFabricStatusItem.CacheStatus)) { + $htmlTableRows += "-" } else { + $htmlTableRows += "$($AppFabricStatusItem.CacheStatus)" + } + if ($AppFabricStatusItem.SPInstanceStatus -eq 'Online') { $htmlTableRows += "$($AppFabricStatusItem.SPInstanceStatus)" } + elseif ($AppFabricStatusItem.SPInstanceStatus -eq 'Not a cache host') { + $htmlTableRows += "$($AppFabricStatusItem.SPInstanceStatus)" + } + else { + $htmlTableRows += "$($AppFabricStatusItem.SPInstanceStatus)" + } } $htmlBodyMerge += Join-HtmlTable -TitleH1 'SharePoint Distributed Cache Status' ` -TableRole 'AppFabricStatus' ` diff --git a/src/Modules/SPSWeather.Common/SPSWeather.Common.psd1 b/src/Modules/SPSWeather.Common/SPSWeather.Common.psd1 index 1e832a9..db2d95c 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.1' + ModuleVersion = '2.3.2' 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 e9850da..ee25db8 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.1') + (Test-ModuleManifest -Path $modulePath).Version | Should -BeGreaterOrEqual ([version]'2.3.2') } It 'exports exactly the expected public functions' {