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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.3.6] - 2026-06-29

### Fixed

- `Get-AppFabricStatus` (SE branch) now reports the real cache Size in MB
(e.g. '2048 MB') and the actual CachePort. `Get-SPCacheHostConfig` only
returns its full payload when executed locally on the cache host after
`Use-SPCacheCluster`; the function now does exactly that, opening a
CredSSP session per cache host and calling `Get-SPCacheHostConfig
-HostName $env:COMPUTERNAME` from inside. When the local lookup still
fails, the row falls back to the SE cluster tier (Small/Medium/Large)
suffixed ' (tier)'. The 2016/2019 (AppFabric) branch is unchanged
(#51, #53).

## [2.3.6] - 2026-06-29

### Fixed

- Distributed Cache: the email body header for the cache size column is now
'Size' (it was 'Size (MB)' but the value was the SE sizing tier - Small /
Medium / Large - when Get-SPCacheHostConfig is null). The fallback value
is now displayed as 'Small (tier)' / 'Medium (tier)' / ... to make clear
it is not a raw MB number (#53).

## [2.3.5] - 2026-06-29

### Fixed
Expand Down
16 changes: 9 additions & 7 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# SPSWeather - Release Notes

## [2.3.5] - 2026-06-29
## [2.3.6] - 2026-06-29

### Fixed

- Distributed Cache report on SharePoint Subscription Edition no longer shows
a phantom 'Microsoft' row (a 2.3.4 regression caused by iterating cluster
health HostInfo objects whose ToString starts with
'Microsoft.SharePoint.Internal.Caching...'). The function iterates the
SPDistributedCacheServiceInstance set again; cluster Size still comes from
Get-SPCacheClusterInfo.
- Distributed Cache report on SharePoint Subscription Edition now shows the
real cache Size in MB (e.g. '2048 MB') and the actual CachePort. The
function opens a CredSSP session per cache host and runs Use-SPCacheCluster
+ Get-SPCacheHostConfig -HostName $env:COMPUTERNAME locally - the SE cmdlet
only returns the full payload when executed on the cache host itself.
When the local lookup still fails, the row falls back to the SE cluster
tier (Small/Medium/Large) suffixed ' (tier)'.
- 2016/2019 (AppFabric) branch is unchanged.

A full list of changes can be found in the [change log](CHANGELOG.md).
250 changes: 143 additions & 107 deletions src/Modules/SPSWeather.Common/Public/Get-AppFabricStatus.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,120 @@
[System.String]
$Farm = 'SPS'
)
$result = Invoke-SPSCommand -Credential $InstallAccount `
-Arguments $PSBoundParameters `
-Server $Server `
-ScriptBlock {

# Pass 1: from the farm entry server, detect SharePoint version and the
# Distributed Cache topology (which servers host the cache + the full server
# list). On SE we do NOT call Get-SPCacheHostConfig here: the cmdlet only
# returns a useful payload (Size in MB, ports, ...) when executed locally on
# the cache host after Use-SPCacheCluster. The 2016/2019 (AppFabric) branch
# keeps its original logic, unchanged.
$inventory = Invoke-SPSCommand -Credential $InstallAccount `
-Arguments $Farm `
-Server $Server `
-ScriptBlock {
$localFarm = $args[0]
$pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll'
$fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1
if ($null -eq $fullPath) {
throw 'SharePoint path {C:\Program Files\Common Files\microsoft shared\Web Server Extensions} does not exist'
}
$productVersion = (Get-Command $fullPath).FileVersionInfo
$isSE = ($productVersion.FileMajorPart -eq 16 -and $productVersion.FileBuildPart -gt 13000)

if ($isSE) {
$allSPServers = (Get-SPServer | Where-Object -FilterScript { $_.Role -ne 'Invalid' }).Name
$dcInstances = Get-SPServiceInstance | Where-Object -FilterScript {
$_.GetType().Name -eq 'SPDistributedCacheServiceInstance'
}
$clusterInfo = Get-SPCacheClusterInfo -ErrorAction SilentlyContinue
$hosts = @()
foreach ($dc in $dcInstances) {
$hosts += [PSCustomObject]@{
Farm = $localFarm
Server = "$($dc.Server.Address)".Split('.')[0]
Status = "$($dc.Status)"
}
}
return [PSCustomObject]@{
IsSE = $true
AllServers = @($allSPServers)
DcHosts = $hosts
ClusterSize = if ($null -ne $clusterInfo) { "$($clusterInfo.Size)" } else { '' }
}
}
else {
return [PSCustomObject]@{ IsSE = $false }
}
}

$tbAppFabricStatus = New-Object -TypeName System.Collections.ArrayList

if ($inventory.IsSE) {
# Pass 2 (SE): query Get-SPCacheHostConfig LOCALLY on each cache host.
# That is where the cmdlet returns the real Size (in MB), ports, etc.
foreach ($dcHost in $inventory.DcHosts) {
$hostConfig = $null
try {
$hostConfig = Invoke-SPSCommand -Credential $InstallAccount `
-Server $dcHost.Server `
-ScriptBlock {
Use-SPCacheCluster -ErrorAction SilentlyContinue
$cfg = Get-SPCacheHostConfig -HostName $env:COMPUTERNAME -ErrorAction SilentlyContinue
if ($null -eq $cfg) { return $null }
return [PSCustomObject]@{
HostName = "$($cfg.HostName)"
CachePort = "$($cfg.CachePort)"
Size = "$($cfg.Size)"
ServiceName = "$($cfg.ServiceName)"
}
}
}
catch {
Write-Verbose -Message "Get-SPCacheHostConfig failed on '$($dcHost.Server)': $($_.Exception.Message)"
}

$port = if ($null -ne $hostConfig) { $hostConfig.CachePort } else { '22233' }
$size = if ($null -ne $hostConfig) { $hostConfig.Size }
elseif ($inventory.ClusterSize) { "$($inventory.ClusterSize) (tier)" }
else { '' }
$serviceName = if ($null -ne $hostConfig) { $hostConfig.ServiceName } else { 'AppFabricCachingService' }
$cacheStatus = if ($dcHost.Status -eq 'Online') { 'Up' } else { 'Unknown' }
$isMailInfo = ($dcHost.Status -eq 'Online' -and $cacheStatus -eq 'Up')

[void]$tbAppFabricStatus.Add([PSCustomObject]@{
Farm = $inventory.DcHosts[0].Farm
Server = $dcHost.Server
Port = $port
ServiceName = $serviceName
Size = $size
CacheStatus = $cacheStatus
SPInstanceStatus = $dcHost.Status
IsInfo = $isMailInfo
})
}
$reportedServers = @($inventory.DcHosts | ForEach-Object { $_.Server })
foreach ($srv in $inventory.AllServers) {
if ($reportedServers -notcontains $srv) {
[void]$tbAppFabricStatus.Add([PSCustomObject]@{
Farm = $Farm
Server = $srv
Port = ''
ServiceName = ''
Size = ''
CacheStatus = ''
SPInstanceStatus = 'Not a cache host'
IsInfo = $true
})
}
}
return $tbAppFabricStatus
}

# SharePoint 2016 / 2019 branch: original AppFabric-based logic, unchanged.
$result = Invoke-SPSCommand -Credential $InstallAccount `
-Arguments $PSBoundParameters `
-Server $Server `
-ScriptBlock {
$params = $args[0]
class AppFabricStatus {
[System.String]$Farm
Expand All @@ -32,117 +141,44 @@
[System.Boolean]$IsInfo
}
$tbAppFabricStatus = New-Object -TypeName System.Collections.ArrayList
$pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll'
$fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1
if ($null -eq $fullPath) {
$message = 'SharePoint path {C:\Program Files\Common Files\microsoft shared\Web Server Extensions} does not exist'
throw $message
}
else {
$productVersion = (Get-Command $fullPath).FileVersionInfo
}
if ($productVersion.FileMajorPart -eq 16 -and $productVersion.FileBuildPart -gt 13000) {
Write-Verbose -Message 'Subscription Edition: iterating SPDistributedCacheServiceInstance + Get-SPCacheClusterInfo for Size'
$allSPServers = (Get-SPServer | Where-Object -FilterScript { $_.Role -ne 'Invalid' }).Name
$dcInstances = @(Get-SPServiceInstance | Where-Object -FilterScript {
Use-CacheCluster -ErrorAction SilentlyContinue
$cacheHosts = Get-CacheHost -ErrorAction SilentlyContinue
if ($null -ne $cacheHosts) {
foreach ($cacheHost in $cacheHosts) {
$isMailInfo = $true
$cacheHostConfig = Get-AFCacheHostConfiguration -ComputerName $cacheHost.HostName `
-CachePort $cacheHost.PortNo `
-ErrorAction SilentlyContinue
$hostName = $cacheHost.HostName
$cacheserver = $hostName.Split('.')[0]
$spCacheSvc = Get-SPServiceInstance -Server $cacheserver | Where-Object -FilterScript {
$_.GetType().Name -eq 'SPDistributedCacheServiceInstance'
})
# Cluster Size comes from the SE-native cmdlet (string like 'Small'/'Medium'/...).
$clusterInfo = Get-SPCacheClusterInfo -ErrorAction SilentlyContinue
$clusterSize = if ($null -ne $clusterInfo) { "$($clusterInfo.Size)" } else { '' }
$reportedServers = New-Object -TypeName System.Collections.ArrayList
foreach ($dcInst in $dcInstances) {
$cacheserver = "$($dcInst.Server.Address)".Split('.')[0]
$SPInstanceStatus = "$($dcInst.Status)"
# Try Get-SPCacheHostConfig with the FQDN then short name; some SE
# builds return null for both - degrade gracefully to SP DC defaults.
$cacheHostConfig = $null
$cacheHost = $null
foreach ($tryHost in @("$($dcInst.Server.Address)", $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
if ($cacheHost.Status -ne 'Up') {
$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 = $port;
ServiceName = $serviceName;
Size = $size;
CacheStatus = $cacheStatus;
SPInstanceStatus = $SPInstanceStatus;
IsInfo = $isMailInfo;
})
[void]$reportedServers.Add($cacheserver)
}
# 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]@{
Farm = $params.Farm
Server = $srv;
Port = '';
ServiceName = '';
Size = '';
CacheStatus = '';
SPInstanceStatus = 'Not a cache host';
IsInfo = $true;
})
}
}
return $tbAppFabricStatus
}
else {
Use-CacheCluster -ErrorAction SilentlyContinue
$cacheHosts = Get-CacheHost -ErrorAction SilentlyContinue
if ($null -ne $cacheHosts) {
foreach ($cacheHost in $cacheHosts) {
$isMailInfo = $true
$cacheHostConfig = Get-AFCacheHostConfiguration -ComputerName $cacheHost.HostName `
-CachePort $cacheHost.PortNo `
-ErrorAction SilentlyContinue
$hostName = $cacheHost.HostName
$cacheserver = $hostName.Split('.')[0]
$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
}
}
else {
$SPInstanceStatus = 'SPService Not Found'
if ($null -ne $spCacheSvc) {
$SPInstanceStatus = $spCacheSvc.Status
if ($SPInstanceStatus -ne 'Online') {
$isMailInfo = $false
}
[void]$tbAppFabricStatus.Add([AppFabricStatus]@{
}
else {
$SPInstanceStatus = 'SPService Not Found'
$isMailInfo = $false
}
[void]$tbAppFabricStatus.Add([AppFabricStatus]@{
Farm = $params.Farm
Server = $cacheserver;
Port = $cacheHost.PortNo;
ServiceName = $cacheHost.ServiceName;
Size = $cacheHostConfig.Size;
CacheStatus = $cacheHost.Status;
SPInstanceStatus = $SPInstanceStatus;
IsInfo = $isMailInfo;
Server = $cacheserver
Port = $cacheHost.PortNo
ServiceName = $cacheHost.ServiceName
Size = $cacheHostConfig.Size
CacheStatus = $cacheHost.Status
SPInstanceStatus = $SPInstanceStatus
IsInfo = $isMailInfo
})
}
return $tbAppFabricStatus
}
return $tbAppFabricStatus
}
}
return $result
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.5'
ModuleVersion = '2.3.6'
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.5')
(Test-ModuleManifest -Path $modulePath).Version | Should -BeGreaterOrEqual ([version]'2.3.6')
}

It 'exports exactly the expected public functions' {
Expand Down
Loading