Skip to content

Instantly share code, notes, and snippets.

@scriptingstudio
Last active March 20, 2023 06:04
Show Gist options
  • Save scriptingstudio/bb35f52ff8aa996e795ad31a8b8373dd to your computer and use it in GitHub Desktop.
Save scriptingstudio/bb35f52ff8aa996e795ad31a8b8373dd to your computer and use it in GitHub Desktop.
Disk S.M.A.R.T. info
<#
.SYNOPSIS
Reads SMART info from supporting SATA drives.
.PARAMETER CimSession
Specifies an existing CIM session or a computername. By default the script works on local computer.
.PARAMETER NoEmpty
Indicates to exclude drives without SMART information from output.
.EXAMPLE
Shows SMART info for the first SATA drive present
Get-SMARTInfo | Select-Object -First 1 -ExpandProperty SMART | Out-GridView
.NOTES
Version: 2.2
Requires admin privileges to run.
#>
function Get-SMARTInfo {
param (
[alias('ComputerName')]$CimSession,
[switch]$NoEmpty,
[switch]$Quiet
)
$params = @{ErrorAction=0} # ; OperationTimeoutSec=2
if ($cimsession) {
$params['cimsession'] = if ($cimsession -isnot [CimSession]) {
New-CimSession -ComputerName $cimsession -ErrorAction 0
} else {$cimsession}
if (-not $params['cimsession']) {
if (-not $Quiet) {Write-Warning "Could not connect to $cimsession"}
return
}
}
$enumparam = @{
0x00 = "Invalid"
0x01 = "Raw read error rate"
0x02 = "Throughput performance"
0x03 = "Spinup time"
0x04 = "Start/Stop count"
0x05 = "Reallocated sector count"
0x06 = "Read channel margin"
0x07 = "Seek error rate"
0x08 = "Seek timer performance"
0x09 = "Power-on hours count"
0x0A = "Spinup retry count"
0x0B = "Calibration retry count"
0x0C = "Power cycle count"
0x0D = "Soft read error rate"
0x16 = "Current helium level"
0xAA = "Available reserved space"
0xAB = "Program fail count"
0xAC = "Erase fail count"
0xAD = "Wear leveling count"
0xAE = "Unexpected power loss count"
0xAF = "Power loss protection failure"
0xB0 = "Erase fail count"
0xB1 = "Wear range delta"
0xB3 = "Used reserved block count"
0xB4 = "Unused reserved block count"
0xB5 = "Program fail count total / non-4K aligned access count"
0xB6 = "Erase fail count"
0xB7 = "SATA downshift error count / Runtime bad block"
0xB8 = "End-to-End error"
0xB9 = "Head stability"
0xBA = "Induced Op-vibration detection"
0xBB = "Reported uncorrectable errors"
0xBC = "Command timeout"
0xBD = "High fly writes"
0xBE = "Airflow Temperature Celsius"
0xBF = "G-sense error rate"
0xC0 = "Power-off retract count"
0xC1 = "Load/Unload cycle count"
0xC2 = "HDD temperature"
0xC3 = "Hardware ECC recovered"
0xC4 = "Reallocation count"
0xC5 = "Current pending sector count"
0xC6 = "Offline scan uncorrectable count"
0xC7 = "UDMA CRC error rate"
0xC8 = "Write error rate"
0xC9 = "Soft read error rate"
0xCA = "Data Address Mark errors"
0xCB = "Run out cancel"
0xCC = "Soft ECC correction"
0xCD = "Thermal asperity rate (TAR)"
0xCE = "Flying height"
0xCF = "Spin high current"
0xD0 = "Spin buzz"
0xD1 = "Offline seek performance"
0xD2 = "Vibration during write"
0xD3 = "Vibration during write"
0xD4 = "Shock during write"
0xDC = "Disk shift"
0xDD = "G-sense error rate"
0xDE = "Loaded hours"
0xDF = "Load/unload retry count"
0xE0 = "Load friction"
0xE1 = "Load/Unload cycle count"
0xE2 = "Load-in time"
0xE3 = "Torque amplification count"
0xE4 = "Power-off retract count"
0xE6 = "GMR head amplitude"
0xE7 = "Temperature"
0xE8 = "Endurance remaining / available reserved space"
0xE9 = "Power-on hours / media wearout indicator"
0xEA = "Average erase count / maximum erase count"
0xEB = "Good block count / System free block count"
0xF0 = "Head flying hours"
0xF1 = "Total LBAs written"
0xF2 = "Total LBAs read"
0xF3 = "Total LBAs written expanded"
0xF4 = "Total LBAs read expanded"
0xF9 = "NAND writes 1GiB"
0xFA = "Read error retry rate"
0xFB = "Minimum spares remaining"
0xFC = "Newly added bad flash block"
0xFE = "Free fall protection"
}
$enumhealth = @{
'0' = 'Healthy'
'1' = 'Warning'
'2' = 'Unhealthy'
'5' = 'Unknown'
}
$enumBT = @{
'0' = 'Unknown'; '1' = 'SCSI'
'2' = 'ATAPI'; '3' = 'ATA'
'4' = '1394'; '5' = 'SSA'
'6' = 'Fibre Channel'; '7' = 'USB'
'8' = 'RAID'; '9' = 'iSCSI'
'10' = 'SAS'; '11' = 'SATA'
'12' = 'SD'; '13' = 'MMC'
'14' = 'Virtual'; '15' = 'File Backed Virtual'
'16' = 'Storage Spaces'; '17' = 'NVMe'
'18' = 'Microsoft Reserved'
}
$enumMT = @{
'0' = 'Unspecified'
'3' = 'HDD'
'4' = 'SSD'
}
# collect disk info
$disks = @{} # disk collection
$diskindex = @{} # NOTE: reusable variable
Get-CimInstance -Class MSFT_PhysicalDisk -Namespace root/microsoft/windows/storage -Property DeviceID,MediaType,BusType,SerialNumber,FirmwareVersion,HealthStatus @params |
ForEach-Object {$diskindex[[int]$_.DeviceID] = $_}
# NOTE: PNPDeviceID of Win32_DiskDrive and InstanceName of MSStorageDriver are slightly different; InstanceName adds the suffix "_0"
Get-CimInstance -Class Win32_DiskDrive @params |
ForEach-Object {
if (-not $Quiet) {Write-Host "Drive '$($_.PNPDeviceID)' found"}
$currentdisk = $diskindex[[int]$_.index]
$disks["$($_.PNPDeviceID)_0"] = [PSCustomObject]@{
ComputerName = $_.SystemName.tolower()
DriveId = $_.index
Model = $_.Model
DriveInfo = $_
MediaType = if ($currentdisk.MediaType.gettype().name -eq 'string') {$currentdisk.MediaType} else {$enumMT[[string]$currentdisk.MediaType]}
BusType = if ($currentdisk.BusType.gettype().name -eq 'string') {$currentdisk.BusType} else {$enumBT[[string]$currentdisk.BusType]}
SerialNumber = $_.SerialNumber
Firmware = $currentdisk.FirmwareVersion
HealthStatus = if ($currentdisk.HealthStatus.gettype().name -eq 'string') {$currentdisk.BusType} else {$enumhealth[[string]$currentdisk.HealthStatus]}
FailureImminent = $null
Reason = $null
SMART = [System.Collections.Generic.List[object]]::new()
}
}
# test admin privileges in order to obtain S.M.A.R.T. info
if (-not $params['cimsession'] -and -not
([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) {
if (-not $Quiet) {
Write-Warning 'Local system: to collect S.M.A.R.T. info administrator privileges are required.'
$disks.Values
}
return
}
# fill PredictFailure data
Get-CimInstance -class MSStorageDriver_FailurePredictStatus -Namespace root\wmi @params |
ForEach-Object {
if (-not $Quiet) {Write-Host "MSStorageDriver data for '$($_.InstanceName)' found"}
if ($disks[$_.InstanceName]) {
$disks[$_.InstanceName].FailureImminent = $_.PredictFailure
$disks[$_.InstanceName].Reason = $_.Reason
}
}
# collect thresholds
$thresholdTable = @{}
Get-CimInstance -Class MSStorageDriver_FailurePredictThresholds -Namespace root\wmi @params |
ForEach-Object {
$tcol = ($thresholdTable[$_.InstanceName] = @{})
$bytes = $_.VendorSpecific
for ($i=0; $i -lt 360; $i+=12) {
$idnumeric = [int]$bytes[$i + 2]
if ($idnumeric) {
$tcol[$idnumeric] = $bytes[$i + 3]
}
}
}
# collect S.M.A.R.T. info
Get-CimInstance -Class MSStorageDriver_ATAPISmartData -Namespace root\wmi @params |
#Get-CimInstance -Class MSStorageDriver_FailurePredictData -Namespace root\wmi @params |
ForEach-Object {
$diskindex = $disks[$_.InstanceName]
if (-not $diskindex) { # just in case
if (-not $Quiet) {Write-Warning "No such disk '$($_.InstanceName)' is known"}
return
}
$tcol = $thresholdTable[$_.InstanceName]
$bytes = $_.VendorSpecific
for ($i=0; $i -lt 360; $i+=12) {
$idnumeric = [int]$bytes[$i + 2]
if (-not $idnumeric) {continue}
$id = if ($enumparam.ContainsKey($idnumeric)) {
$enumparam[$idnumeric]
} else {
"ID $idnumeric"
}
$rawvendordata = [BitConverter]::ToInt32($bytes, ($i + 7))
$attr = @{} # experimental
$flags = $bytes[$i + 4] # least significant status byte, +3 most significant byte, but not used so ignored.
#$advisory = ($flags -band 0x1) -eq 0x0 # experimental
$failureImminent = ($flags -band 0x1) -eq 0x1
#$onlineDataCollection = ($flags -band 0x2) -eq 0x2 # experimental
$diskindex.SMART.Add([PSCustomObject]@{
Name = $id
Current = $bytes[$i + 5]
Worst = $bytes[$i + 6]
RawData = $rawvendordata #[convert]::ToString($rawvendordata,2)
IsOK = -not $failureImminent
Threshold = $tcol[$idnumeric]
ThresholdUndefined = -not $tcol.ContainsKey($idnumeric)
})
} # for
} # foreach
if ($params['cimsession'] -is [CimSession]) {
$null = remove-cimsession $params['cimsession'] -ErrorAction 4
}
if ($NoEmpty) {
$disks.Values | Where-Object {$_.smart.count}
} else {$disks.Values}
} # END Get-SMARTInfo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment