Monitoring with PowerShell: Monitoring SMART status using SmartCTL.

Some time ago I wrote a blog about monitoring SMART status with CrystalDiskInfo. After bringing this script over to our production RMM environment everything seemed good. But when I looked a little deeper I found that the script failed on NVME drives. NVME drives handle SMART-Status different from ‘regular’ SATA drives.

This started me on a quest for a solution that also worked on NVME drives. I’ve decided to use SmartMonTools as it has the same benefits as crystaldiskmark – It’s portable, does not require an installation, and is small enough to be downloaded on demand.

The script is fairly straightforward, it downloads the utility from a host, extracts the utility and runs an update for SmartCTL so it can fill in the data correctly. After this for each HDD in the system it will run a compare to the thresholds you’ve setup.

I’ve also had a request for disk monitoring on specifically the available spare count. The script can be edited to monitor this too – that way you can decide your own thresholds over what the manufacturer said is default for the disk.

The script

############ Thresholds #############
$PowerOnTime = 35063 #about 4 years constant runtime.
$PowerCycles = 4000 #4000 times of turning drive on and off
$Temperature = 60 #60 degrees celcius
############ End Thresholds #########
$DownloadURL = "https://cyberdrain.com/wp-content/uploads/2020/02/Smartmontools.zip"
$DownloadLocation = "$($Env:ProgramData)\SmartmonTools"
try {
    $TestDownloadLocation = Test-Path $DownloadLocation
    if (!$TestDownloadLocation) { new-item $DownloadLocation -ItemType Directory -force }
    $TestDownloadLocationZip = Test-Path "$DownloadLocation\Smartmontools.zip"
    if (!$TestDownloadLocationZip) { Invoke-WebRequest -UseBasicParsing -Uri $DownloadURL -OutFile "$($DownloadLocation)\Smartmontools.zip" }
    $TestDownloadLocationExe = Test-Path "$DownloadLocation\smartctl.exe"
    if (!$TestDownloadLocationExe) { Expand-Archive "$($DownloadLocation)\Smartmontools.zip" -DestinationPath $DownloadLocation -Force }
}
catch {
    write-host "The download and extraction of SMARTCTL failed. Error: $($_.Exception.Message)"
    exit 1
}
#update the smartmontools database
start-process -filepath "$DownloadLocation\update-smart-drivedb.exe" -ArgumentList "/S" -Wait
#find all connected HDDs
$HDDs = (& "$DownloadLocation\smartctl.exe" --scan -j | ConvertFrom-Json).devices
$HDDInfo = foreach ($HDD in $HDDs) {
    (& "$DownloadLocation\smartctl.exe" -t short -a -j $HDD.name) | convertfrom-json
}
$DiskHealth = @{}
#Checking SMART status
$SmartFailed = $HDDInfo | Where-Object { $_.Smart_Status.Passed -ne $true }
if ($SmartFailed) { $DiskHealth.add('SmartErrors',"Smart Failed for disks: $($SmartFailed.serial_number)") }
#checking Temp Status
$TempFailed = $HDDInfo | Where-Object { $_.temperature.current -ge $Temperature }
if ($TempFailed) { $DiskHealth.add('TempErrors',"Temperature failed for disks: $($TempFailed.serial_number)") }
#Checking Power Cycle Count status
$PCCFailed = $HDDInfo | Where-Object { $_.Power_Cycle_Count -ge $PowerCycles }
if ($PCCFailed ) { $DiskHealth.add('PCCErrors',"Power Cycle Count Failed for disks: $($PCCFailed.serial_number)") }
#Checking Power on Time Status
$POTFailed = $HDDInfo | Where-Object { $_.Power_on_time.hours -ge $PowerOnTime }
if ($POTFailed) { $DiskHealth.add('POTErrors',"Power on Time for disks failed : $($POTFailed.serial_number)") }

if (!$DiskHealth) { $DiskHealth = "Healthy" }

And that’s it! as always, Happy PowerShelling.

5 Comments

  1. Remco March 9, 2020 at 10:44 pm

    Hi,

    For some reason it doesn’t work on my Microsoft Surface laptop, when i run “smartctl.exe -t short -a -j /dev/sda” it shows the information.

    But when i run this “smartctl.exe -t short -a -j /dev/sda | Where-Object { $_.smart_status.Passed -ne $true }” it shows ALL the values, even when i change $true to $false.

    I have a NVME drive, but i don’t think this script as idiot proof as it could be. I don’t know what the problem is yet, but just inform you.

    1. Kelvin Tegelaar March 9, 2020 at 10:47 pm

      I’ll have to check that out, could you send me the entire printout of “smartctl.exe -t short -a -j /dev/sda” and remove your serial numbers? I’ll be able to see where it fails exactly. 🙂 It could also be that your hard drive is not in the SmartCTL database yet. I’ll be able to evaluate that based on the output.

  2. NAC October 21, 2020 at 5:53 pm

    Can you spot what I’m doing wrong? I’ve copied it character by character. Thank you in advance!

    At C:\hd.ps1:23 char:12
    + $HDDs = (& "$DownloadLocation\smartctl.exe” –scan -j | ConvertF …
    + ~
    The ampersand (&) character is not allowed. The & operator is reserved for future use; wrap an ampersand in double quotation marks (“&”) to pass it as part of a string.
    At C:\hd.ps1:23 char:17
    + $HDDs = (& "$DownloadLocation\smartctl.exe” –scan -j | ConvertF …
    + ~
    Missing closing ‘)’ in expression.
    At C:\hd.ps1:23 char:35
    + … $DownloadLocation\smartctl.exe” –scan -j | ConvertFrom-Json).devices
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Unexpected token ‘\smartctl.exe” –scan -j | ConvertFrom-Json).devices
    $HDDInfo = foreach ($HDD in $HDDs) {
    (& “$DownloadLocation\smartctl.exe” -t short -a -j $HDD.name) | convertfrom-json
    }
    $DiskHealth = @{}
    #Checking SMART status
    $SmartFailed = $HDDInfo | Where-Object { $_.Smart_Status.Passed -ne $true }
    if ($SmartFailed) { $DiskHealth.add(‘SmartErrors’,”Smart’ in expression or statement.
    At C:\hd.ps1:41 char:43
    + if (!$DiskHealth) { $DiskHealth = “Healthy” }
    + ~~~
    The string is missing the terminator: “.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : AmpersandNotAllowed

  3. NS November 11, 2020 at 5:54 am

    NAC There is some unicode on line 23 " probably should be replaced with a single “

  4. AdamM March 17, 2021 at 11:34 am

    I’m having difficulty getting this working in SyncroRMM.
    It works great when run directly from the machine but when run from Syncro, it returns: ‘System.Collections.DictionaryEntry’…
    What am I missing? I’d really appreciate a little help on this one.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.