Private/Kinds/Storage.ps1
|
# The Storage Kind: what the machine stores things on, and whether there is room. # # The reliability counters behind a solid-state drive's wear need admin rights and ship # as their own Kind in the Elevated Part. What is here is what a Technician can see # without elevating: the device, its media, its free space, and whether it is thrashing. $script:StorageSampleCount = 3 function Get-StorageData { [CmdletBinding()] [OutputType([psobject])] param([hashtable]$Parameters = @{}) $disks = @() try { $disks = @(Get-PhysicalDisk -ErrorAction Stop | ForEach-Object { [pscustomobject]@{ DeviceId = $_.DeviceId FriendlyName = $_.FriendlyName MediaType = "$($_.MediaType)" BusType = "$($_.BusType)" SizeBytes = $_.Size HealthStatus = "$($_.HealthStatus)" } }) } catch { } $systemDrive = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='$env:SystemDrive'" -ErrorAction SilentlyContinue $filter = "Name='_Total'" # The first read of a formatted-performance class is primed with zeroes; discard it. [void](Get-CimInstance Win32_PerfFormattedData_PerfDisk_PhysicalDisk -Filter $filter -ErrorAction SilentlyContinue) $samples = @(1..$script:StorageSampleCount | ForEach-Object { Write-CheckProgress -Status ((Get-Text 'Console.Progress.Sample') -f $_, $script:StorageSampleCount) ` -Step ($_ - 1) -Of $script:StorageSampleCount Wait-Sample Get-CimInstance Win32_PerfFormattedData_PerfDisk_PhysicalDisk -Filter $filter -ErrorAction SilentlyContinue }) [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.Storage' PhysicalDisks = $disks SystemDriveLetter = $env:SystemDrive SystemDriveFreeBytes = $systemDrive.FreeSpace SystemDriveSizeBytes = $systemDrive.Size IdleTimeSamples = @($samples | ForEach-Object { $_.PercentIdleTime }) } } function ConvertTo-StorageFinding { [CmdletBinding()] [OutputType([psobject])] param( [AllowNull()]$Data, [hashtable]$Parameters = @{} ) New-PhysicalDiskFinding -Data $Data -Parameters $Parameters New-FreeSpaceFinding -Data $Data -Parameters $Parameters New-DiskBusyFinding -Data $Data -Parameters $Parameters } function New-PhysicalDiskFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) $disks = (Get-DataCollection $Data 'PhysicalDisks') if (-not $disks.Count) { # Named so it cannot be mistaken for a device: the per-device Findings below are # "Disk 0", "Disk 1". The script abandoned the whole Check here and lost free # space and disk-busy with it; a Check that could not run belongs in the Report. return New-UnavailableFinding -Category Storage -Check (Get-Text 'Check.Storage.PhysicalDisks') ` -Hint (Get-Text 'Hint.Storage.NoPhysicalDiskCouldBe') } # One Finding per device: a machine with a healthy SSD and a failing HDD is not # summarised honestly by either one of them. foreach ($disk in $disks) { $severity = 'OK' if ("$($disk.HealthStatus)" -ne 'Healthy') { $severity = 'FAIL' } elseif ("$($disk.MediaType)" -eq 'HDD') { $severity = 'FAIL' } New-Finding -Category Storage -Check ((Get-Text 'Check.Storage.Disk') -f $disk.DeviceId) -Severity $severity ` -Value ('{0} | {1} | {2} | {3} GB | Health: {4}' -f $disk.FriendlyName, $disk.MediaType, $disk.BusType, [math]::Round(($disk.SizeBytes / 1GB)), $disk.HealthStatus) ` -Hint (Get-Text 'Hint.Storage.UnhealthyDiskOrHDDReplace') } } function New-FreeSpaceFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) # Two thresholds that disagree are resolved the way the script resolved them: whichever # is worse wins, because 12 GB free on a 2 TB disk is as fatal as 8 % on a small one. $failBelowGB = Get-Parameter $Parameters 'FreeSpaceFailBelowGB' 15 $failBelowPercent = Get-Parameter $Parameters 'FreeSpaceFailBelowPercent' 10 $warnBelowPercent = Get-Parameter $Parameters 'FreeSpaceWarnBelowPercent' 20 $free = ConvertTo-Number (Get-DataProperty $Data 'SystemDriveFreeBytes') $size = ConvertTo-Number (Get-DataProperty $Data 'SystemDriveSizeBytes') $drive = Get-DataProperty $Data 'SystemDriveLetter' if ($null -eq $free -or $null -eq $size -or $size -le 0) { return New-UnavailableFinding -Category Storage -Check ((Get-Text 'Check.Storage.FreeSpace') -f $drive) ` -Hint (Get-Text 'Hint.Storage.TheSystemDriveDidNot') } $freeGB = $free / 1GB $freePercent = $free / $size * 100 # Two thresholds that disagree are resolved the way the script resolved them: whichever # is worse wins. 12 GB free on a 2 TB disk is a comfortable-looking 0.6 % and still # fatal, and 9 % of a 1 TB disk is 90 comfortable-looking gigabytes and still fatal. $severity = Get-SeverityBelow $freePercent $warnBelowPercent $failBelowPercent if ($freeGB -lt $failBelowGB) { $severity = 'FAIL' } New-Finding -Category Storage -Check ((Get-Text 'Check.Storage.FreeSpace') -f $drive) -Severity $severity ` -Value ('{0:N1} GB ({1:N0} %)' -f $freeGB, $freePercent) ` -Hint (Get-Text 'Hint.Storage.FullSSDsGetSlowUpdates') } function New-DiskBusyFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) $warn = Get-Parameter $Parameters 'DiskBusyAtRestWarnPercent' 40 $fail = Get-Parameter $Parameters 'DiskBusyAtRestFailPercent' 80 $idle = Get-SampleAverage (Get-DataProperty $Data 'IdleTimeSamples') if ($null -eq $idle) { return New-UnavailableFinding -Category Storage -Check (Get-Text 'Check.Storage.DiskBusyAtRest') ` -Hint (Get-Text 'Hint.Storage.TheDiskPerformanceCountersDid') } $busy = 100 - $idle New-Finding -Category Storage -Check (Get-Text 'Check.Storage.DiskBusyAtRest') -Severity (Get-Severity $busy $warn $fail) ` -Value ('{0:N0} %' -f $busy) ` -Hint (Get-Text 'Hint.Storage.DiskPermanentlyBusyWhileIdle') } |