mqsft.diskconfigvalidation.psm1
|
# ============================================================================== # mqsft.diskconfigvalidation.psm1 - AUTO-GENERATED by Create_Module_0_0_7.ps1 v0.0.7 # ------------------------------------------------------------------------------ # Module Version : 1.0.1 # Script Version : 0.0.7 # Built : 2026-02-24 21:48:17 # Source : C:\Modules\functions\mqsft.diskconfigvalidation # # DO NOT EDIT THIS FILE DIRECTLY. # Edit source files in C:\Modules\functions\mqsft.diskconfigvalidation and re-run Create_Module_0_0_7.ps1. # ============================================================================== Set-StrictMode -Version Latest # --- Source: Show-DiskReport.ps1 ----------------------------------------- function Show-DiskReport { $fmt = "{0,-4} {1,-15} {2,-16} {3,-7} {4,-8} {5,-10} {6,-9} {7,-9} {8,-4} {9,-12} {10,-16} {11,-7} {12}" $divider = "-" * 135 $physDisks = Get-PhysicalDisk $physLookup = @{} foreach ($pd in $physDisks) { $physLookup[$pd.SerialNumber.Trim()] = $pd } $allDisks = Get-Disk $nonSystemDisks = $allDisks | Where-Object { -not $_.IsSystem -and -not $_.IsBoot -and $_.BusType -ne "USB" -and $_.FriendlyName -notmatch "LOGICAL VOLUME" } $sizeGroups = $nonSystemDisks | Group-Object { [math]::Round($_.Size / 1GB, 0) } | Sort-Object Count -Descending $dominantSizeGB = [int]$sizeGroups[0].Name $dominantName = ($nonSystemDisks | Group-Object FriendlyName | Sort-Object Count -Descending | Select-Object -First 1).Name Write-Host "" Write-Host " DISK INVENTORY — $env:COMPUTERNAME" -ForegroundColor Cyan Write-Host " Dominant pool disk : $dominantName ($dominantSizeGB GB)" -ForegroundColor DarkGray Write-Host " NOTE: Offline disks are normal on a cluster node — not scored as spare signal" -ForegroundColor DarkGray Write-Host $divider -ForegroundColor DarkGray Write-Host ($fmt -f "No.","Friendly Name","Serial","Health","Status","Size GB","Size TB","Partition","Bus","PD Usage","S2D Status","CanPool","Role") -ForegroundColor Cyan Write-Host $divider -ForegroundColor DarkGray $allDisks | Sort-Object { [int]$_.Number } | ForEach-Object { $disk = $_ $sizeGB = [math]::Round($disk.Size / 1GB, 1) $sizeTB = [math]::Round($disk.Size / 1TB, 3) $serial = $disk.SerialNumber.Trim() $pd = $physLookup[$serial] # Pre-compute everything - no inline if inside -f $pdUsage = if ($pd) { $pd.Usage } else { "Unknown" } $canPool = if ($pd) { "$($pd.CanPool)" } else { "Unknown" } $pdHealth = if ($pd) { $pd.HealthStatus } else { $disk.HealthStatus } # Spare scoring - Offline removed as signal (normal on cluster nodes) $sizeOutlier = ([math]::Round($sizeGB, 0)) -ne $dominantSizeGB $isHotSpare = $pdUsage -eq "Hot Spare" $cantPool = ($pd -and $pd.CanPool -eq $false) $modelMismatch = ($disk.FriendlyName -ne $dominantName) -and -not ($disk.IsSystem -or $disk.IsBoot) -and ($disk.BusType -ne "USB") $score = 0 $score += if ($sizeOutlier) { 3 } else { 0 } # strong - different size to pool $score += if ($isHotSpare) { 3 } else { 0 } # strong - explicitly flagged $score += if ($cantPool) { 2 } else { 0 } # medium - storage subsystem says no $score += if ($modelMismatch) { 1 } else { 0 } # weak - different model $isSpare = $score -ge 3 $s2d = switch ($true) { { $disk.IsSystem -or $disk.IsBoot } { "N/A OS Disk"; break } { $disk.BusType -eq "USB" } { "N/A USB"; break } { $disk.FriendlyName -match "LOGICAL VOLUME" } { "N/A RAID"; break } { $isSpare } { "⚠ Spare?"; break } { $disk.PartitionStyle -eq "RAW" } { "✔ Ready"; break } { $disk.PartitionStyle -eq "GPT" } { "⚠ Need Reset"; break } { $disk.PartitionStyle -eq "MBR" } { "✗ MBR"; break } default { "Unknown" } } $role = switch ($true) { { $disk.IsSystem -or $disk.IsBoot } { "⚠ OS/BOOT"; break } { $disk.BusType -eq "USB" } { "⚠ USB"; break } { $disk.FriendlyName -match "LOGICAL VOLUME" } { "⚠ RAID VOL"; break } { $isSpare } { "⚠ SPARE [$score/8]"; break } { $disk.PartitionStyle -eq "RAW" } { "✔ S2D Ready"; break } default { "S2D Candidate" } } $color = switch ($true) { { $disk.IsSystem -or $disk.IsBoot } { "Red"; break } { $disk.BusType -eq "USB" } { "Magenta"; break } { $disk.FriendlyName -match "LOGICAL VOLUME" } { "Yellow"; break } { $isSpare } { "DarkYellow"; break } { $disk.PartitionStyle -eq "RAW" } { "Green"; break } { $disk.PartitionStyle -eq "GPT" } { "Yellow"; break } default { "White" } } Write-Host ($fmt -f ([int]$disk.Number), ($disk.FriendlyName -replace "HPE ",""), $serial, $pdHealth, $disk.OperationalStatus, "$sizeGB GB", "$sizeTB TB", $disk.PartitionStyle, $disk.BusType, $pdUsage, $s2d, $canPool, $role ) -ForegroundColor $color } Write-Host $divider -ForegroundColor DarkGray # Legend Write-Host "" Write-Host " LEGEND" -ForegroundColor Cyan Write-Host " Red = OS / Boot - do not touch" -ForegroundColor Red Write-Host " Yellow = GPT / RAID - needs attention" -ForegroundColor Yellow Write-Host " DarkYellow = Likely hot spare [score/8]" -ForegroundColor DarkYellow Write-Host " Green = RAW - ready for S2D" -ForegroundColor Green Write-Host " Magenta = USB - exclude" -ForegroundColor Magenta Write-Host "" Write-Host " Score signals: Size mismatch=3 Usage=HotSpare=3 CanPool=False=2 Model mismatch=1" -ForegroundColor DarkGray Write-Host " (Offline not scored - normal behaviour on cluster nodes)" -ForegroundColor DarkGray Write-Host "" # Summary - pre-compute spare details to avoid inline if errors $candidates = $allDisks | Where-Object { $_.PartitionStyle -eq "RAW" -and $_.BusType -ne "USB" -and -not $_.IsSystem -and -not $_.IsBoot } $needsReset = $allDisks | Where-Object { $_.PartitionStyle -eq "GPT" -and $_.BusType -ne "USB" -and -not $_.IsSystem -and -not $_.IsBoot -and $_.FriendlyName -notmatch "LOGICAL VOLUME" } $spares = $allDisks | Where-Object { $s = $_.SerialNumber.Trim() $pd = $physLookup[$s] $szMismatch = ([math]::Round([math]::Round($_.Size/1GB,1),0)) -ne $dominantSizeGB $hotSpare = if ($pd) { $pd.Usage -eq "Hot Spare" } else { $false } $noPool = if ($pd) { $pd.CanPool -eq $false } else { $false } $modelOdd = $_.FriendlyName -ne $dominantName $sc = ($(if ($szMismatch) {3} else {0})) + ($(if ($hotSpare) {3} else {0})) + ($(if ($noPool) {2} else {0})) + ($(if ($modelOdd) {1} else {0})) $sc -ge 3 -and -not $_.IsSystem -and -not $_.IsBoot -and $_.BusType -ne "USB" -and $_.FriendlyName -notmatch "LOGICAL VOLUME" } $excluded = $allDisks | Where-Object { $_.IsSystem -or $_.IsBoot -or $_.BusType -eq "USB" -or $_.FriendlyName -match "LOGICAL VOLUME" } Write-Host " S2D SUMMARY" -ForegroundColor Cyan Write-Host " ✔ Ready for S2D : " -NoNewline; Write-Host $candidates.Count -ForegroundColor Green Write-Host " ⚠ Need Reset-Disk : " -NoNewline; Write-Host $needsReset.Count -ForegroundColor Yellow Write-Host " ⚠ Likely Hot Spares : " -NoNewline; Write-Host $spares.Count -ForegroundColor DarkYellow foreach ($sp in $spares) { $s = $sp.SerialNumber.Trim() $pd = $physLookup[$s] $usage = if ($pd) { $pd.Usage } else { "Unknown" } $cp = if ($pd) { "$($pd.CanPool)" } else { "?" } $szMatch = ([math]::Round([math]::Round($sp.Size/1GB,1),0)) -ne $dominantSizeGB $model = $sp.FriendlyName -replace "HPE ","" Write-Host (" Disk {0,-3} {1,-16} {2,-16} Usage:{3,-12} CanPool:{4,-5} SizeMismatch:{5} Model:{6}" -f [int]$sp.Number, $model, $s, $usage, $cp, $szMatch, $model ) -ForegroundColor DarkYellow } Write-Host " ✗ Excluded : " -NoNewline; Write-Host $excluded.Count -ForegroundColor DarkGray foreach ($ex in $excluded) { $reason = if ($ex.IsSystem -or $ex.IsBoot) { "OS/Boot" } elseif ($ex.BusType -eq "USB") { "USB" } elseif ($ex.FriendlyName -match "LOGICAL VOLUME") { "RAID Volume" } else { "Other" } $name = $ex.FriendlyName -replace "HPE ","" Write-Host (" Disk {0,-3} {1,-16} ({2})" -f [int]$ex.Number, $name, $reason) -ForegroundColor DarkGray } Write-Host "" } Show-DiskReport |