Private/Kinds/DiskHealth.ps1
|
# The DiskHealth Kind: what the drive itself says about how much life it has left. # # The Storage Kind reports that a disk is Healthy, which is Windows repeating what the # driver told it. This reads the reliability counters underneath: wear, uncorrected read # errors, temperature and hours powered on. A solid-state drive at 85 % wear reports # Healthy right up until it does not, and this is the Check that gives a Technician the # weeks of notice that makes the difference between a replacement and a data recovery. # Declared for the Kind vocabulary: the reliability counters are not readable without # admin rights, so this Check belongs to the Elevated Part. See Private/Kind.ps1. $script:DiskHealthNeedsAdmin = $true function Get-DiskHealthData { [CmdletBinding()] [OutputType([psobject])] param([hashtable]$Parameters = @{}) $disks = @() try { $disks = @(Get-PhysicalDisk -ErrorAction Stop | ForEach-Object { $device = $_ $counter = $null $readable = $true try { $counter = $device | Get-StorageReliabilityCounter -ErrorAction Stop } catch { # Plenty of drives and controllers simply do not expose these counters, and # a Run without admin rights cannot read them at all. Which of the two this # was is the Judge's call, from the Privilege the Part carries. $readable = $false } [pscustomobject]@{ DeviceId = $device.DeviceId FriendlyName = "$($device.FriendlyName)" MediaType = "$($device.MediaType)" CountersReadable = $readable Wear = $counter.Wear TemperatureC = $counter.Temperature ReadErrorsUncorrected = $counter.ReadErrorsUncorrected PowerOnHours = $counter.PowerOnHours } }) } catch { } [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.DiskHealth' PhysicalDisks = $disks # Which rights the readings were taken with: the difference between "this drive # does not report wear" and "nobody was allowed to ask". Elevated = (Get-CurrentPrivilege) -eq 'admin' } } function ConvertTo-DiskHealthFinding { [CmdletBinding()] [OutputType([psobject])] param( [AllowNull()]$Data, [hashtable]$Parameters = @{} ) $wearWarn = Get-Parameter $Parameters 'DiskWearWarnAbovePercent' 49 $wearFail = Get-Parameter $Parameters 'DiskWearFailAbovePercent' 79 $tempWarn = Get-Parameter $Parameters 'DiskTemperatureWarnAboveC' 60 $tempFail = Get-Parameter $Parameters 'DiskTemperatureFailAboveC' 70 $disks = Get-DataCollection $Data 'PhysicalDisks' if (-not $disks.Count) { return New-UnavailableFinding -Category Storage -Check (Get-Text 'Check.DiskHealth.DiskHealth') ` -Hint (Get-Text 'Hint.DiskHealth.NoPhysicalDiskCouldBe') } $elevated = [bool](Get-DataProperty $Data 'Elevated') foreach ($disk in $disks) { $check = (Get-Text 'Check.DiskHealth.DiskWearErrors') -f $disk.DeviceId if (-not $disk.CountersReadable) { $why = $(if ($elevated) { 'not supported by disk/driver' } else { (Get-Text 'Value.Shared.NeedsAdmin') }) New-Finding -Category Storage -Check $check -Severity INFO -Value $why ` -Hint $(if ($elevated) { 'This drive or its controller does not expose reliability counters; use the vendor''s own tool' } else { 'Rerun and allow the admin prompt: without it this drive''s remaining life is unknown, not good' }) continue } $wear = ConvertTo-Number $disk.Wear $errors = ConvertTo-Number $disk.ReadErrorsUncorrected $temp = ConvertTo-Number $disk.TemperatureC # Wear and uncorrected read errors both end in the same place. A single # uncorrected read error is data the drive could not give back, so it fails on its # own however young the drive is. $wearSeverity = 'OK' if ($null -ne $wear) { $wearSeverity = Get-Severity $wear $wearWarn $wearFail } $errorSeverity = 'OK' if ($null -ne $errors -and $errors -gt 0) { $errorSeverity = 'FAIL' } $tempSeverity = 'OK' if ($null -ne $temp -and $temp -gt 0) { $tempSeverity = Get-Severity $temp $tempWarn $tempFail } New-Finding -Category Storage -Check $check ` -Severity (Get-WorstSeverity $wearSeverity $errorSeverity $tempSeverity) ` -Value ((Get-Text 'Value.DiskHealth.WearTempErrors') -f $disk.Wear, $disk.TemperatureC, $disk.ReadErrorsUncorrected, $disk.PowerOnHours) ` -Hint (Get-Text 'Hint.DiskHealth.SSDWornOutOrReporting') } } |