From 7e395e0c4a71705513fb9c10c8789d79a1cadc30 Mon Sep 17 00:00:00 2001
From: TheAbider <51920546+TheAbider@users.noreply.github.com>
Date: Fri, 29 May 2026 15:57:15 -0700
Subject: [PATCH] Add cluster validation report CLI action (Failover
Clustering)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
New ClusterValidationReport CLI action: run a non-disruptive failover-cluster
validation (Inventory + Network + System Configuration) against the cluster's
nodes (or this node if not yet clustered), archive the native HTML report to
an admin-only path, and report a best-effort overall result + pass/warn/fail
counts. Read-only — makes no changes.
Complements the interactive Cluster Management -> [3] Validate Configuration
(which can run the full, potentially disruptive suite). The saved HTML report
is authoritative for per-test detail; storage / Hyper-V categories stay in the
interactive validator. JSON-aware for fleet pre-flight.
Addition to 27-FailoverClustering (no new module). CLI actions 193 -> 194;
docs in lockstep. 5045 tests, 0 failures; PSScriptAnalyzer clean.
---
CONTRIBUTING.md | 2 +-
Changelog.md | 8 ++
Header.ps1 | 4 +-
Modules/00-Initialization.ps1 | 2 +-
Modules/27-FailoverClustering.ps1 | 74 +++++++++++++++++++
Modules/34-Help.ps1 | 2 +-
Modules/50-EntryPoint.ps1 | 6 ++
README.md | 12 +--
RackStack.ps1 | 2 +-
RackStack.psd1 | 2 +-
Tests/Run-Tests.ps1 | 29 +++++++-
dist/chocolatey/rackstack.nuspec | 2 +-
dist/scoop/rackstack.json | 2 +-
.../TheAbider.RackStack.locale.en-US.yaml | 2 +-
14 files changed, 129 insertions(+), 20 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 495f808..56274d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -30,7 +30,7 @@ powershell -ExecutionPolicy Bypass -File Tests\pssa-check.ps1
## Pull Request Checklist
-- [ ] All 5,040 tests pass (`Run-Tests.ps1` exits with code 0)
+- [ ] All 5,045 tests pass (`Run-Tests.ps1` exits with code 0)
- [ ] PSScriptAnalyzer reports 0 errors (`pssa-check.ps1`)
- [ ] Monolithic synced (`sync-to-monolithic.ps1` shows 0 parse errors)
- [ ] New functions follow PowerShell verb-noun naming (`Get-`, `Set-`, `Test-`, `Show-`)
diff --git a/Changelog.md b/Changelog.md
index 55819f4..f1e212b 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,13 @@
# Changelog
+## v1.111.0
+
+Failover Cluster validation report — new `ClusterValidationReport` CLI action. Runs a **non-disruptive** failover-cluster validation (Inventory + Network + System Configuration categories) against the cluster's nodes (or this node if it isn't yet clustered), archives the native HTML report to an admin-only path, and reports a best-effort overall result (pass / warning / failed counts).
+
+It complements the interactive **Cluster Management → [3] Validate Configuration** (which can run the full, potentially disruptive suite). Read-only — it makes no changes. `-Action ClusterValidationReport -OutputFormat JSON` emits the overall result + report path for fleet pre-flight checks; the saved HTML report is authoritative for per-test detail. Storage and Hyper-V validation categories (which need shared storage / offline VMs) stay in the interactive validator.
+
+Addition to 27-FailoverClustering (no new module). CLI actions: 193 → 194.
+
## v1.110.0
Enable AD Recycle Bin — added to **AD DS Domain Controller Promotion** (`[6] Enable AD Recycle Bin`) and via the `ADRecycleBin` CLI action. Turns on Active Directory object recovery so deleted users/groups/OUs can be restored with their attributes intact.
diff --git a/Header.ps1 b/Header.ps1
index 4aa5ece..e6ca5f2 100644
--- a/Header.ps1
+++ b/Header.ps1
@@ -30,7 +30,7 @@
7h3 4b1d3r
.VERSION
- 1.110.0
+ 1.111.0
.LAST UPDATED
05/23/2026
@@ -1391,7 +1391,7 @@
param(
# CLI headless mode: run a specific action without interactive menus
# Usage: RackStack.exe -Action Cleanup [-Tier Standard] [-Silent] [-OutputFormat JSON]
- [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit', 'ADRecycleBin')]
+ [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit', 'ADRecycleBin', 'ClusterValidationReport')]
[string]$Action,
[ValidateSet('Light', 'Standard', 'Aggressive')]
diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1
index 514b839..839f9aa 100644
--- a/Modules/00-Initialization.ps1
+++ b/Modules/00-Initialization.ps1
@@ -225,7 +225,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
if (-not $script:ModuleRoot -and $script:ScriptPath) {
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
}
-$script:ScriptVersion = "1.110.0"
+$script:ScriptVersion = "1.111.0"
$script:ScriptStartTime = Get-Date
# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.
diff --git a/Modules/27-FailoverClustering.ps1 b/Modules/27-FailoverClustering.ps1
index cdfcedb..77752e1 100644
--- a/Modules/27-FailoverClustering.ps1
+++ b/Modules/27-FailoverClustering.ps1
@@ -1354,4 +1354,78 @@ function Test-ClusterQuorumHealth {
Write-PressEnter
}
+
+# CLI: ClusterValidationReport — run a non-disruptive cluster validation
+# headlessly, archive the HTML report to the hardened state dir, and report a
+# best-effort overall result. Read-only (validation makes no changes). The HTML
+# report is the authoritative artifact; the tally is a best-effort scan.
+function Start-ClusterValidationReport {
+ $jsonOut = ($script:CLIOutputFormat -eq 'JSON')
+ if (-not (Test-FailoverClusteringInstalled)) {
+ if ($jsonOut) { Write-Output (@{ Tool = $script:ToolFullName; Version = $script:ScriptVersion; Action = 'ClusterValidationReport'; Hostname = $env:COMPUTERNAME; Available = $false } | ConvertTo-Json) }
+ else { Write-OutputColor " Failover Clustering is not installed." -color "Error" }
+ return $false
+ }
+ # Validate the existing cluster's nodes if clustered, else this node.
+ $nodes = @($env:COMPUTERNAME)
+ try {
+ if (Get-Command -Name Get-Cluster -ErrorAction SilentlyContinue) {
+ if (Get-Cluster -ErrorAction SilentlyContinue) {
+ $cn = @(Get-ClusterNode -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name)" })
+ if ($cn.Count -gt 0) { $nodes = $cn }
+ }
+ }
+ }
+ catch { }
+
+ $secureDir = Get-RackStackSecureStateDir
+ if ([string]::IsNullOrWhiteSpace($secureDir)) { $secureDir = $script:TempPath }
+ $reportDir = Join-Path $secureDir "ClusterValidation"
+ if (-not (Test-Path -LiteralPath $reportDir)) { $null = New-Item -LiteralPath $reportDir -ItemType Directory -Force -ErrorAction SilentlyContinue }
+ $base = Join-Path $reportDir "ClusterValidation_$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+
+ if (-not $jsonOut) {
+ Clear-Host
+ Write-CenteredOutput "Cluster Validation Report" -color "Info"
+ Write-OutputColor " Validating: $($nodes -join ', ') (non-disruptive: Inventory + Network + System Config)" -color "Info"
+ Write-OutputColor " This may take a few minutes..." -color "Info"
+ }
+
+ $reportFile = "$base.htm"; $failed = 0; $warned = 0; $overall = 'Unknown'
+ try {
+ # Non-disruptive categories only — storage/Hyper-V tests can disrupt and
+ # need shared storage / offline VMs; those stay in the interactive validator.
+ $r = Test-Cluster -Node $nodes -Include 'Inventory', 'Network', 'System Configuration' -ReportName $base -ErrorAction Stop
+ if ($r -and $r.FullName) { $reportFile = "$($r.FullName)" }
+ if (Test-Path -LiteralPath $reportFile) {
+ $html = Get-Content -LiteralPath $reportFile -Raw -ErrorAction SilentlyContinue
+ $failed = @([regex]::Matches($html, '>\s*Failed\s*<')).Count
+ $warned = @([regex]::Matches($html, '>\s*Warning\s*<')).Count
+ $overall = if ($failed -gt 0) { 'Failed' } elseif ($warned -gt 0) { 'Warning' } else { 'Pass' }
+ }
+ else { $overall = 'Completed' }
+ }
+ catch {
+ $overall = 'Error'
+ if (-not $jsonOut) { Write-OutputColor " Validation error: $($_.Exception.Message)" -color "Error" }
+ }
+
+ if ($jsonOut) {
+ Write-Output (@{
+ Tool = $script:ToolFullName; Version = $script:ScriptVersion; Action = 'ClusterValidationReport'
+ Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss"); Hostname = $env:COMPUTERNAME
+ Available = $true; Nodes = $nodes; OverallResult = $overall
+ FailedCount = $failed; WarningCount = $warned; ReportPath = $reportFile
+ } | ConvertTo-Json)
+ }
+ else {
+ $c = switch ($overall) { 'Pass' { 'Success' } 'Warning' { 'Warning' } default { 'Error' } }
+ Write-OutputColor "" -color "Info"
+ Write-OutputColor " Overall result: $overall (failed: $failed, warnings: $warned)" -color $c
+ Write-OutputColor " Report: $reportFile" -color "Info"
+ Write-OutputColor " The HTML report is authoritative — open it for per-test detail." -color "Debug"
+ Add-SessionChange -Category "Cluster" -Description "Ran cluster validation report ($overall)"
+ }
+ return ($overall -ne 'Error')
+}
#endregion
diff --git a/Modules/34-Help.ps1 b/Modules/34-Help.ps1
index b1a469e..b4a3d7b 100644
--- a/Modules/34-Help.ps1
+++ b/Modules/34-Help.ps1
@@ -253,7 +253,7 @@ function Search-HelpTopics {
@{ Title = "Performance"; Keywords = @("performance", "cpu", "memory", "disk", "io", "bandwidth", "dashboard", "process"); Description = "Live performance dashboard with CPU, memory, disk I/O, and network bandwidth monitoring" }
@{ Title = "Licensing & NTP"; Keywords = @("license", "activation", "kms", "avma", "ntp", "time", "timezone", "clock"); Description = "Windows licensing status (KMS/AVMA/Retail), NTP configuration, time sync, and timezone setup" }
@{ Title = "VM Management"; Keywords = @("checkpoint", "snapshot", "export", "import", "migration", "vhd", "iso"); Description = "VM checkpoints, export/import, migration readiness, VHD health, and ISO inventory" }
- @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "193 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." }
+ @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "194 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." }
@{ Title = "SelfTest Action"; Keywords = @("selftest", "self-test", "diagnose", "diagnostic", "verify", "healthcheck", "sanity"); Description = "Internal diagnostic. -Action SelfTest checks PS version, elevation, module count, version consistency, defaults.json validity, temp path writability, FileServer reachability, and agent installer config. Exit 1 on any failure. Use -OutputFormat JSON for structured output." }
@{ Title = "Security Audits"; Keywords = @("security", "audit", "hardening", "compliance", "tls", "smb", "kerberos", "credguard", "applocker", "bitlockeraudit", "defenderexclusionaudit", "audit-policy", "secureboot", "tpm"); Description = "Security-focused CLI audits: TLSAudit, SMBAudit, KerberosAudit, CredGuardAudit, AppLockerAudit, BitLockerAudit, DefenderExclusionAudit, AuditPolicyAudit, SecureBootAudit, TPMAudit, UserAudit, LogonAudit, InsecureServiceAudit, RegistryAudit. All support -OutputFormat JSON." }
@{ Title = "Network Audits"; Keywords = @("netaudit", "dns", "firewall-audit", "firewalllog", "arp", "route", "tcp", "netstat", "dhcp", "netprofile", "winrm", "qos", "nicoffload"); Description = "Network audits: DNSAudit, DNSCacheAudit, FirewallAudit, FirewallRuleAudit, FirewallLogAudit, ARPTableAudit, RouteTableAudit, TcpSettingsAudit, NetStatAudit, DHCPAudit, NetworkProfileAudit, WinRMAudit, QoSPolicyAudit, NICOffloadAudit, NICErrorAudit, HostsFileAudit, VPNAudit, ProxyAudit." }
diff --git a/Modules/50-EntryPoint.ps1 b/Modules/50-EntryPoint.ps1
index 8b9a636..1690054 100644
--- a/Modules/50-EntryPoint.ps1
+++ b/Modules/50-EntryPoint.ps1
@@ -411,6 +411,7 @@ function Assert-Elevation {
@{ Action = 'WACStatus'; Description = 'Show Windows Admin Center gateway status (service, port, listening)' }
@{ Action = 'VHDXEncryptionAudit'; Description = 'Read-only: report whether each VM virtual disk sits on a BitLocker-protected volume' }
@{ Action = 'ADRecycleBin'; Description = 'Show AD Recycle Bin status (JSON) or enable it interactively (irreversible)' }
+ @{ Action = 'ClusterValidationReport'; Description = 'Run a non-disruptive failover-cluster validation and archive the HTML report (JSON-aware)' }
@{ Action = 'Batch'; Description = 'JSON-driven full configuration' }
)
if ($script:CLIOutputFormat -eq 'JSON') {
@@ -2053,6 +2054,11 @@ footer{text-align:center;color:#999;font-size:12px;padding:16px}
$adrbOk = Start-ADRecycleBin
[Environment]::Exit([int](-not $adrbOk))
}
+ 'ClusterValidationReport' {
+ # Non-disruptive cluster validation + archived HTML report (JSON-aware).
+ $cvrOk = Start-ClusterValidationReport
+ [Environment]::Exit([int](-not $cvrOk))
+ }
'Batch' {
if (-not $script:CLIConfig) {
Write-OutputColor " ERROR: -Action Batch requires -Config
-
+