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

## [Unreleased]

## [2.2.5] - 2026-06-29

### Fixed

- `Add-SPSWeatherEvent` no longer returns silently when its source is already
registered against another log (legacy flat scripts could bind 'SPSWeather'
to Application): it now re-points the source to the SPSWeather log and only
warns if the registration/write fails. This is why no SPSWeather log or events
appeared on a fresh install (#35).
- Per-server SYS checks (IIS app pools, W3WP, certs, last reboot, .NET, disks,
event log) wrap each server in try/catch: an unreachable node yields a single
'Unreachable' row instead of dumping a raw 0x80090322 double-hop error and
aborting the whole farm's collection (#33). Fixed a `$nulll` typo that always
blanked the .NET version.
- Search checks (crawl status, crawl logs, topology) wrap the Search admin calls
in try/catch: a down Search topology host yields one 'Search unavailable' row
instead of a raw 503 stack trace (#34).

## [2.2.4] - 2026-06-29

### Fixed
Expand Down
13 changes: 13 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# SPSWeather - Release Notes

## [2.2.5] - 2026-06-29

### Fixed

- The SPSWeather Event Log is now created reliably: Add-SPSWeatherEvent re-points
a source that legacy scripts bound to the Application log, instead of giving up
silently, so install/run/alert events are recorded.
- One unreachable farm server no longer wipes the system report: each server is
reported as 'Unreachable' and the rest continue, and Search outages show as a
single 'Search unavailable' row instead of a raw 503/double-hop dump.

A full list of changes can be found in the [change log](CHANGELOG.md).

## [2.2.4] - 2026-06-29

### Fixed
Expand Down
35 changes: 23 additions & 12 deletions src/Modules/SPSWeather.Common/Public/Add-SPSWeatherEvent.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,32 @@

$LogName = 'SPSWeather'

if ([System.Diagnostics.EventLog]::SourceExists($Source)) {
$sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName($Source, '.')
if ($LogName -ne $sourceLogName) {
Write-Verbose -Message "[ERROR] Specified source {$Source} already exists on log {$sourceLogName}"
return
}
}
else {
if ([System.Diagnostics.EventLog]::Exists($LogName) -eq $false) {
$null = New-EventLog -LogName $LogName -Source $Source
# Ensure the event source exists and is mapped to the SPSWeather log. The
# legacy flat scripts could register some sources under 'Application'; in that
# case re-point them to SPSWeather instead of silently giving up (which is why
# no SPSWeather log/events appeared). Requires admin (the script validates it).
try {
if ([System.Diagnostics.EventLog]::SourceExists($Source)) {
$sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName($Source, '.')
if ($LogName -ne $sourceLogName) {
Write-Verbose -Message "Source '$Source' is mapped to log '$sourceLogName'; re-pointing it to '$LogName'."
[System.Diagnostics.EventLog]::DeleteEventSource($Source)
[System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
}
}
else {
[System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
if ([System.Diagnostics.EventLog]::Exists($LogName) -eq $false) {
$null = New-EventLog -LogName $LogName -Source $Source
}
else {
[System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
}
}
}
catch {
Write-Warning -Message "Could not register event source '$Source' on log '$LogName' (need admin?): $($_.Exception.Message)"
return
}

$autoVersion = $MyInvocation.MyCommand.Module.Version
if ($null -eq $autoVersion) {
Expand All @@ -92,7 +103,7 @@ ComputerName: $($env:COMPUTERNAME)
Write-EventLog -LogName $LogName -Source $Source -EventId $EventID -Message ($headerMessage + "`r`n" + $Message) -EntryType $EntryType
}
catch {
Write-Error -Message @"
Write-Warning -Message @"
SPSWeather Version: $scriptVersion
An error occurred while writing to Event Log in Source: $Source
User: $userName
Expand Down
19 changes: 14 additions & 5 deletions src/Modules/SPSWeather.Common/Public/Get-SPSSearchEntCrawlLogs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@
}
$spSearchEntSvc = Get-SPEnterpriseSearchServiceApplication -ErrorAction SilentlyContinue
if ($null -ne $spSearchEntSvc) {
$spSchContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $spSearchEntSvc
if ($null -ne $spSchContentSources) {
$tbSPSSearchEntCrawlLogs = New-Object -TypeName System.Collections.ArrayList
try {
$spSchContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $spSearchEntSvc -ErrorAction Stop
$spSchCrawlLogPso = New-Object Microsoft.Office.Server.Search.Administration.CrawlLog $spSearchEntSvc
$tbSPSSearchEntCrawlLogs = New-Object -TypeName System.Collections.ArrayList

foreach ($contentSource in $spSchContentSources) {
$getSPCrawlErrors = $spSchCrawlLogPso.GetCrawlErrors($contentSource.ID, -1)
if ($getSPCrawlErrors.Rows.Count -ne 0) {
Expand All @@ -50,8 +49,18 @@
}
}
}
return $tbSPSSearchEntCrawlLogs
}
catch {
[void]$tbSPSSearchEntCrawlLogs.Add([SearchContentCrawlLog]@{
Farm = $params.Farm;
SearchService = $spSearchEntSvc.Name;
ContentSource = 'Search unavailable';
ErrorID = '503';
Message = $_.Exception.Message;
Count = '0';
})
}
return $tbSPSSearchEntCrawlLogs
}
}
return $result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@
}
$spSearchEntSvc = Get-SPEnterpriseSearchServiceApplication -ErrorAction SilentlyContinue
if ($null -ne $spSearchEntSvc) {
$spSchContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $spSearchEntSvc
if ($null -ne $spSchContentSources) {
#Initialize ArrayList variable
$tbSPSSearchEntCrawlStatus = New-Object -TypeName System.Collections.ArrayList

#Initialize ArrayList variable
$tbSPSSearchEntCrawlStatus = New-Object -TypeName System.Collections.ArrayList
try {
$spSchContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $spSearchEntSvc -ErrorAction Stop
foreach ($contentSource in $spSchContentSources) {
$spCrawlDuration = 'OK'
$isMailInfo = $true
Expand All @@ -57,8 +56,20 @@
IsInfo = $isMailInfo;
})
}
return $tbSPSSearchEntCrawlStatus
}
catch {
[void]$tbSPSSearchEntCrawlStatus.Add([SearchContentLastCrawl]@{
Farm = $params.Farm;
SearchService = $spSearchEntSvc.Name;
ContentSource = 'Search unavailable';
CrawlState = $_.Exception.Message;
Duration = 'Error';
CrawlStarted = '';
CrawlCompleted = '';
IsInfo = $false;
})
}
return $tbSPSSearchEntCrawlStatus
}
}
return $result
Expand Down
18 changes: 14 additions & 4 deletions src/Modules/SPSWeather.Common/Public/Get-SPSSearchEntTopology.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
}
$spSearchEntSvc = Get-SPEnterpriseSearchServiceApplication -ErrorAction SilentlyContinue
if ($null -ne $spSearchEntSvc) {
$getSchStatus = Get-SPEnterpriseSearchStatus -SearchApplication $spSearchEntSvc -Detailed
if ($null -ne $getSchStatus) {
$tbSearchTopologyStatus = New-Object -TypeName System.Collections.ArrayList
$tbSearchTopologyStatus = New-Object -TypeName System.Collections.ArrayList
try {
$getSchStatus = Get-SPEnterpriseSearchStatus -SearchApplication $spSearchEntSvc -Detailed -ErrorAction Stop
foreach ($compoSch in $getSchStatus) {
$isMailInfo = $True
if ($compoSch.State -ne 'Active') {
Expand All @@ -53,8 +53,18 @@
IsInfo = $isMailInfo;
})
}
return $tbSearchTopologyStatus
}
catch {
[void]$tbSearchTopologyStatus.Add([SearchTopologyStatus]@{
Farm = $params.Farm;
SearchService = $spSearchEntSvc.Name;
ComponentHost = 'Unreachable';
ComponentName = 'Search unavailable';
State = $_.Exception.Message;
IsInfo = $false;
})
}
return $tbSearchTopologyStatus
}
}
return $result
Expand Down
54 changes: 32 additions & 22 deletions src/Modules/SPSWeather.Common/Public/Get-SYSDOTNETVersion.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,42 @@
}
$tbSYSDOTNETVersion = New-Object -TypeName System.Collections.ArrayList
foreach ($spServer in $params.Servers) {
[System.String]$remoteServer = [System.Net.Dns]::GetHostByName($spServer).HostName
$resultReg = Invoke-Command -ComputerName $remoteServer -ScriptBlock {
try {
$getItemProperty = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"
try {
[System.String]$remoteServer = [System.Net.Dns]::GetHostByName($spServer).HostName
$resultReg = Invoke-Command -ComputerName $remoteServer -ErrorAction Stop -ScriptBlock {
try {
$getItemProperty = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"
}
catch {
$null = $getItemProperty
}
return $getItemProperty
}
catch {
$null = $getItemProperty
if ($null -ne $resultReg) {
[System.Boolean]$NetRequiredBool = ($resultReg.Release -ge 528040)
[void]$tbSYSDOTNETVersion.Add([SYSDOTNETVersion]@{
Farm = $params.Farm;
Server = $spServer;
NetVersion = "$($resultReg.Version)";
NetRequiredVersion = $NetRequiredBool;
})
}
else {
[void]$tbSYSDOTNETVersion.Add([SYSDOTNETVersion]@{
Farm = $params.Farm;
Server = $spServer;
NetVersion = $null;
NetRequiredVersion = $null;
})
}
return $getItemProperty
}
if ($nulll -ne $resultReg){
[System.Boolean]$NetRequiredBool = ($resultReg.Release -ge 528040)
[void]$tbSYSDOTNETVersion.Add([SYSDOTNETVersion]@{
Farm = $params.Farm;
Server = $spServer;
NetVersion = "$($resultReg.Version)";
NetRequiredVersion = $NetRequiredBool;
})
}
else {
catch {
[void]$tbSYSDOTNETVersion.Add([SYSDOTNETVersion]@{
Farm = $params.Farm;
Server = $spServer;
NetVersion = $null;
NetRequiredVersion = $null;
})
Farm = $params.Farm;
Server = $spServer;
NetVersion = 'Unreachable';
NetRequiredVersion = $false;
})
}
}
return $tbSYSDOTNETVersion
Expand Down
49 changes: 31 additions & 18 deletions src/Modules/SPSWeather.Common/Public/Get-SYSDiskUsageStatus.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,41 @@
}
$tbSYSDiskUsageStatus = New-Object -TypeName System.Collections.ArrayList
foreach ($spServer in $params.Servers) {
[System.String]$remoteServer = [System.Net.Dns]::GetHostByName($spServer).HostName
$getDrives = Invoke-Command -ComputerName $remoteServer { Get-Volume | Where-Object -FilterScript { $_.DriveType -eq 'Fixed' -and $null -ne $_.DriveLetter } }
foreach ($getDrive in $getDrives) {
$driveSize = [math]::Round($($getDrive.Size) / 1073741824, 2)
$driveFree = [math]::Round($($getDrive.SizeRemaining) / 1073741824, 2)
$perFreeSpace = ($getDrive.SizeRemaining / $getDrive.Size) * 100
if ($perFreeSpace -gt $params.WarningPercentage) {
$freeSpaceStatus = 'OK'
$isMailInfo = $true
}
else {
$freeSpaceStatus = "Less than $WarningPercentage %"
$isMailInfo = $false
try {
[System.String]$remoteServer = [System.Net.Dns]::GetHostByName($spServer).HostName
$getDrives = Invoke-Command -ComputerName $remoteServer -ErrorAction Stop { Get-Volume | Where-Object -FilterScript { $_.DriveType -eq 'Fixed' -and $null -ne $_.DriveLetter } }
foreach ($getDrive in $getDrives) {
$driveSize = [math]::Round($($getDrive.Size) / 1073741824, 2)
$driveFree = [math]::Round($($getDrive.SizeRemaining) / 1073741824, 2)
$perFreeSpace = ($getDrive.SizeRemaining / $getDrive.Size) * 100
if ($perFreeSpace -gt $params.WarningPercentage) {
$freeSpaceStatus = 'OK'
$isMailInfo = $true
}
else {
$freeSpaceStatus = "Less than $($params.WarningPercentage) %"
$isMailInfo = $false
}
[void]$tbSYSDiskUsageStatus.Add([SYSDiskUsageStatus]@{
Farm = $params.Farm;
Server = $spServer;
DriveLetter = $getDrive.DriveLetter;
Size = $driveSize;
FreeSpace = $driveFree;
Status = $freeSpaceStatus;
IsInfo = $isMailInfo
})
}
}
catch {
[void]$tbSYSDiskUsageStatus.Add([SYSDiskUsageStatus]@{
Farm = $params.Farm;
Server = $spServer;
DriveLetter = $getDrive.DriveLetter;
Size = $driveSize;
FreeSpace = $driveFree;
Status = $freeSpaceStatus;
IsInfo = $isMailInfo
DriveLetter = '';
Size = '';
FreeSpace = '';
Status = 'Unreachable';
IsInfo = $false
})
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/Modules/SPSWeather.Common/Public/Get-SYSEvtAppErrors.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
foreach ($pSserver in $params.Servers) {
[System.String]$remoteServer = [System.Net.Dns]::GetHostByName($pSserver).HostName
try {
$appErrors = Invoke-Command -ComputerName $remoteServer -ScriptBlock {
$appErrors = Invoke-Command -ComputerName $remoteServer -ErrorAction Stop -ScriptBlock {
Get-WinEvent -FilterHashTable @{LogName = 'Application'; Level = 2; StartTime = ((Get-Date) - (New-TimeSpan -Days 1)) } `
-ErrorAction SilentlyContinue
}
Expand Down Expand Up @@ -67,7 +67,14 @@
}
}
catch {
return $_
[void]$tbSYSEventViewerAppErrors.Add([SYSEventViewerAppError]@{
Farm = $params.Farm
Server = $pSserver;
ID = 'Non Applicable';
Severity = 'Warning';
Name = 'Unreachable';
Count = '0';
})
}
}
return $tbSYSEventViewerAppErrors
Expand Down
Loading
Loading