Last active
October 18, 2025 11:36
-
-
Save Sharkrit/7589be27268a739dd223d8d1ffdec54c to your computer and use it in GitHub Desktop.
Collect-ClusterDiagnostics.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Collect complete diagnostics for a Windows Failover Cluster including: | |
| Cluster config, Preferred Owners, RDMA, SMB, SR-IOV, Hyper-V, and Storage Spaces Direct (S2D) | |
| .NOTES | |
| Author: GPT-5 | |
| Requires: Administrator privilege, FailoverClusters, Hyper-V, Storage modules | |
| Usage: .\Collect-ClusterDiagnostics.ps1 -HoursBack 3 -OutputRoot "D:\ClusterDiag" | |
| #> | |
| param( | |
| [int]$HoursBack = 2, | |
| [string]$OutputRoot = $PWD | |
| ) | |
| Import-Module FailoverClusters -ErrorAction Stop | |
| Import-Module Hyper-V -ErrorAction SilentlyContinue | |
| Import-Module Storage -ErrorAction SilentlyContinue | |
| $timeStamp = Get-Date -Format "yyyyMMdd_HHmmss" | |
| $outputDir = Join-Path -Path $OutputRoot -ChildPath "ClusterDiag_$timeStamp" | |
| New-Item -Path $outputDir -ItemType Directory -Force | Out-Null | |
| Function Save-ToFile { | |
| param($Name, $Object) | |
| $path = Join-Path $outputDir $Name | |
| if ($Object -is [string]) { | |
| $Object | Out-File -FilePath $path -Encoding UTF8 | |
| } else { | |
| $Object | Out-String -Width 4096 | Out-File -FilePath $path -Encoding UTF8 | |
| } | |
| } | |
| # --- Core Cluster Info --- | |
| Write-Host "Collecting cluster configuration..." | |
| $cluster = Get-Cluster | |
| Save-ToFile "01_Cluster_Info.txt" $cluster | |
| Save-ToFile "02_Cluster_Nodes.txt" (Get-ClusterNode) | |
| Save-ToFile "03_Cluster_Networks.txt" (Get-ClusterNetwork) | |
| Save-ToFile "04_Cluster_Groups.txt" (Get-ClusterGroup | Select Name, OwnerNode, State, @{N='PreferredOwners';E={($_ | Get-ClusterOwnerNode) -join ','}}) | |
| Save-ToFile "05_Cluster_Resources.txt" (Get-ClusterResource) | |
| Save-ToFile "06_Cluster_Quorum.txt" (Get-ClusterQuorum) | |
| # --- RDMA / SR-IOV / NIC info --- | |
| $nodes = (Get-ClusterNode).Name | |
| foreach ($n in $nodes) { | |
| Write-Host "Collecting NIC/RDMA/SRIOV from $n..." | |
| $nicInfo = Invoke-Command -ComputerName $n -ScriptBlock { Get-NetAdapter | Select Name, InterfaceDescription, Status, LinkSpeed, VmqEnabled, SriovSupport, RDMAEnabled } | |
| $rdma = Invoke-Command -ComputerName $n -ScriptBlock { Get-NetAdapterRdma -ErrorAction SilentlyContinue } | |
| $sriov = Invoke-Command -ComputerName $n -ScriptBlock { Get-NetAdapterSriov -ErrorAction SilentlyContinue } | |
| Save-ToFile "Node_${n}_NICs.txt" $nicInfo | |
| Save-ToFile "Node_${n}_RDMA.txt" $rdma | |
| Save-ToFile "Node_${n}_SRIOV.txt" $sriov | |
| } | |
| # --- SMB & Storage Network --- | |
| Save-ToFile "07_SMB_ServerConfig.txt" (Get-SmbServerConfiguration) | |
| Save-ToFile "08_SMB_ServerNetworkInterface.txt" (Get-SmbServerNetworkInterface) | |
| Save-ToFile "09_SMB_Multichannel.txt" (Get-SmbMultichannelConnection -ErrorAction SilentlyContinue) | |
| # --- Hyper-V Section --- | |
| Write-Host "Collecting Hyper-V info..." | |
| foreach ($n in $nodes) { | |
| try { | |
| $vms = Invoke-Command -ComputerName $n -ScriptBlock { Get-VM | Select Name, State, CPUUsage, MemoryAssigned, Uptime, Version } | |
| $vSwitch = Invoke-Command -ComputerName $n -ScriptBlock { Get-VMSwitch | Select Name, SwitchType, BandwidthReservationMode, IovEnabled, IovQueuePairsRequested, Notes } | |
| $vNic = Invoke-Command -ComputerName $n -ScriptBlock { Get-VMNetworkAdapter -All | Select VMName, Name, MacAddress, SwitchName, IPAddresses, IsManagementOs } | |
| $vhd = Invoke-Command -ComputerName $n -ScriptBlock { Get-VMHardDiskDrive | Select VMName, Path, ControllerType, ControllerNumber } | |
| Save-ToFile "Node_${n}_HyperV_VMs.txt" $vms | |
| Save-ToFile "Node_${n}_HyperV_vSwitch.txt" $vSwitch | |
| Save-ToFile "Node_${n}_HyperV_vNIC.txt" $vNic | |
| Save-ToFile "Node_${n}_HyperV_VHDs.txt" $vhd | |
| } catch { | |
| Save-ToFile "Node_${n}_HyperV_Error.txt" $_ | |
| } | |
| } | |
| # --- S2D / Storage Spaces Direct --- | |
| Write-Host "Collecting S2D health and storage information..." | |
| try { | |
| $s2dHealth = Get-ClusterS2D | |
| Save-ToFile "10_S2D_ClusterS2D.txt" $s2dHealth | |
| Save-ToFile "11_S2D_HealthReport.txt" (Get-StorageSubSystem *cluster* | Get-StorageHealthReport) | |
| Save-ToFile "12_S2D_StoragePool.txt" (Get-StoragePool) | |
| Save-ToFile "13_S2D_VirtualDisks.txt" (Get-VirtualDisk) | |
| Save-ToFile "14_S2D_PhysicalDisks.txt" (Get-PhysicalDisk) | |
| Save-ToFile "15_S2D_CacheDevices.txt" (Get-PhysicalDisk | Where-Object { $_.Usage -eq 'Journal' }) | |
| Save-ToFile "16_S2D_Enclosures.txt" (Get-StorageEnclosure) | |
| Save-ToFile "17_S2D_FaultDomains.txt" (Get-StorageFaultDomain) | |
| Save-ToFile "18_S2D_Tiers.txt" (Get-StorageTier) | |
| } catch { | |
| Save-ToFile "S2D_Error.txt" $_ | |
| } | |
| # --- Performance Counters (S2D + CSV) --- | |
| Write-Host "Collecting performance counters (S2D + CSV)..." | |
| try { | |
| $csvPerf = Get-Counter '\Cluster CSVFS(* )\*' -ErrorAction SilentlyContinue | |
| $s2dPerf = Get-Counter '\Cluster Storage Spaces Direct(*)\*' -ErrorAction SilentlyContinue | |
| Save-ToFile "19_Perf_CSVFS.txt" $csvPerf | |
| Save-ToFile "20_Perf_S2D.txt" $s2dPerf | |
| } catch { | |
| Save-ToFile "Perf_Error.txt" $_ | |
| } | |
| # --- Event Logs --- | |
| $startTime = (Get-Date).AddHours(-$HoursBack) | |
| Write-Host "Collecting Event Logs from $startTime..." | |
| foreach ($n in $nodes) { | |
| try { | |
| $evtFailover = Invoke-Command -ComputerName $n -ScriptBlock { param($t) Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-FailoverClustering/Operational'; StartTime=$t} } -ArgumentList $startTime | |
| $evtHyperV = Invoke-Command -ComputerName $n -ScriptBlock { param($t) Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Hyper-V-Worker/Admin'; StartTime=$t} } -ArgumentList $startTime | |
| $evtS2D = Invoke-Command -ComputerName $n -ScriptBlock { param($t) Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-StorageSpaces-Driver/Operational'; StartTime=$t} } -ArgumentList $startTime | |
| Save-ToFile "Node_${n}_Evt_Failover.txt" $evtFailover | |
| Save-ToFile "Node_${n}_Evt_HyperV.txt" $evtHyperV | |
| Save-ToFile "Node_${n}_Evt_S2D.txt" $evtS2D | |
| } catch { | |
| Save-ToFile "Node_${n}_EventError.txt" $_ | |
| } | |
| } | |
| # --- Cluster Logs --- | |
| Write-Host "Generating cluster logs..." | |
| $clusterLogDir = Join-Path $outputDir "ClusterLogs" | |
| New-Item $clusterLogDir -ItemType Directory -Force | Out-Null | |
| Get-ClusterLog -Destination $clusterLogDir -UseLocalTime -Force | Out-Null | |
| # --- Compress results --- | |
| $zipPath = Join-Path $OutputRoot ("ClusterDiag_$timeStamp.zip") | |
| Compress-Archive -Path "$outputDir\*" -DestinationPath $zipPath -Force | |
| Write-Host "โ Diagnostics complete." | |
| Write-Host "๐ Output folder: $outputDir" | |
| Write-Host "๐๏ธ Zipped file: $zipPath" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment