Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -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).
90 changes: 37 additions & 53 deletions src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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]@{
Expand Down
2 changes: 1 addition & 1 deletion src/Modules/SPSWeather.Common/SPSWeather.Common.psd1
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
2 changes: 1 addition & 1 deletion tests/SPSWeather.Common.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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' {
Expand Down
Loading