I noticed a link to an article on twitter yesterday that talked about how you can use PowerShell to query for a SMART enabled hard drive and detect a possible imminent failure. It was an interesting article and showed me something I didn’t know regarding detecting a potential failure on a drive.
What is SMART you ask? Good question, SMART stands for Self-Monitoring, Analysis and Reporting Technology and is used, as cited in this Wikipedia article:
The purpose of S.M.A.R.T. is to warn a user of impending drive failure while there is still time to take action, such as copying the data to a replacement devices
While I enjoyed the article and the PowerShell example that referenced the MSStorageDriver_FailurePredictStatus class used to determine if a drive was failing, something kept nagging at me saying that I could really put something useful together that would give you more information if a potential failure was detected rather than bringing up the device manager and looking for the drive and its data.
So with that, I went ahead and started looking at what it would take to not only detect if a failure was going to happen, but to also look for that drive and gather some more information. Understand that this only works on local drives, not SAN attached or network drives.
The first thing I did was deciding to use the Win32_DiskDrive class as that lists each local hard drive installed on my computers. Once I had that information, I then had to figure out what items from each of the WMI classes I could use to match the failed drive up with the appropriate information. At last I found my two items:
gwmi win32_diskdrive | Select PNPDeviceID
PNPDeviceID
———–
IDE\DISKST9320320AS_____________________________DE05____\5&446824F&0&0.0.0
Get-WmiObject -namespace root\wmi -class MSStorageDriver_FailurePredictStatus | Select InstanceName
InstanceName
————
IDE\DiskST9320320AS_____________________________DE05____\5&446824f&0&0.0.0_0
Ok, close… But not completely equal. The problem is that the InstanceName has an extra _0 after it,thus ruining the chance to perform a –filter in the get-WmiObject to locate the appropriate drive. I cannot perform a wildcard query either because of that extra stuff at the end. Looks like this is a job for Regular Expressions!
Here is the RegEx code that I used to parse the string:
[regex]$regex = "(?<DriveName>\w+\\[A-Za-z0-9_]*)\w+"
I created a RegEx object that I can use later to grab an important piece of the drive name that I can add into a wildcard search to locate the proper drive. Some key pieces from this expression:
- (?<DriveName>) This allows me to use a named group which makes it easier to locate the correct match. More information on this can be found here.
- \w+ This allows me to search for anyword or character with a couple of limitations. More information on that can be found here.
- \\ The “\” backslash is the escape character used in Regular Expressions in PowerShell. By doing this, I am escaping a “\” backslash for my query.
- [A-Za-z0-9_] I am looking for a Upper or Lower case letter or a number within the range given plus any underscores. More information can be found here.
- * This means to search for 1 or more of whatever you have preceding this, in my case look for more matching letters or numbers.
That was a very brief look into the RegEx values I used to pull the drive information that can then be used to perform my wmi query and find the drive.
$drive = $regex.Matches('IDE\DiskST9320320AS_____________________________DE05____\5&446824f&0&0.0.0_0') | ForEach { $_.Groups['DriveName'].value } $drive
Now we are able to use this to query the Win32_DiskDrive class and find the matching drive.
gwmi Win32_DiskDrive| Where {$_.PNPDeviceID -like "$drive*"}
And now we have the problem drive.
The finished Advanced Function that I came up with is Get-FailingDrive which can be ran against either a local or remote machine/s. If a drive using SMART shows that a failure may occur, it will then retrieve more information about that drive and display the information for the user.
For the sake of showing the output, I am modifying a piece of the code to return a non-failing drive. But the code I am making available to download is configured properly.
Get-FailingDrive
The Reason code appears to be vendor specific based on limited research. However, there is a list of reason codes available here.
Of course, I have performed limited testing and use of this code on my laptop at home, so I welcome any and all feedback so I can make improvements as needed. Thanks!
Download Script
(remove ‘.doc’ extension)
Code
Function Get-FailingDrive { <# .SYNOPSIS Checks for any potentially failing drives and reports back drive information. .DESCRIPTION Checks for any potentially failing drives and reports back drive information. This only works against local hard drives using SMART technology. Reason values and their meanings can be found here: http://en.wikipedia.org/wiki/S.M.A.R.T#Known_ATA_S.M.A.R.T._attributes .PARAMETER Computer Remote or local computer to check for possible failed hard drive. .PARAMETER Credential Provide alternate credential to perform query. .NOTES Author: Boe Prox Version: 1.0 http://learn-powershell.net .EXAMPLE Get-FailingDrive WARNING: ST9320320AS ATA Device may fail! MediaType : Fixed hard disk media InterFace : IDE DriveName : ST9320320AS ATA Device Reason : 1 SerialNumber : 202020202020202020202020533531584e5a4d50 FailureImminent : True Description ----------- Command ran against the local computer to check for potential failed hard drive. #> [cmdletbinding()] Param ( [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)] [string[]]$Computername, [parameter()] [System.Management.Automation.PSCredential]$Credential ) Begin { $queryhash = @{} $BadDriveHash = @{} } Process { ForEach ($Computer in $Computername) { If ($PSBoundParameters['Computer']) { $queryhash['Computername'] = $Computer $BadDriveHash['Computername'] = $Computer } Else { $queryhash['Computername'] = $Env:Computername $BadDriveHash['Computername'] = $Env:Computername } If ($PSBoundParameters['Credential']) { $queryhash['Credential'] = $Credential $BadDriveHash['Credential'] = $Credential } Write-Verbose "Creating SplatTable" $queryhash['NameSpace'] = 'root\wmi' $queryhash['Class'] = 'MSStorageDriver_FailurePredictStatus' $queryhash['Filter'] = "PredictFailure='False'" $queryhash['ErrorAction'] = 'Stop' $BadDriveHash['Class'] = 'win32_diskdrive' $BadDriveHash['ErrorAction'] = 'Stop' [regex]$regex = "(?<DriveName>\w+\\[A-Za-z0-9_]*)\w+" Try { Write-Verbose "Checking for failed drives" Get-WmiObject @queryhash | ForEach { $drive = $regex.Matches($_.InstanceName) | ForEach {$_.Groups['DriveName'].value} Write-Verbose "Gathering more information about failing drive" $BadDrive = gwmi @BadDriveHash | Where {$_.PNPDeviceID -like "$drive*"} If ($BadDrive) { Write-Warning "$($BadDriveHash['Computername']): $($BadDrive.Model) may fail!" New-Object PSObject -Property @{ DriveName = $BadDrive.Model FailureImminent = $_.PredictFailure Reason = $_.Reason MediaType = $BadDrive.MediaType SerialNumber = $BadDrive.SerialNumber InterFace = $BadDrive.InterfaceType Partitions = $BadDrive.Partitions Size = $BadDrive.Size Computer = $BadDriveHash['Computername'] } } } } Catch { Write-Warning "$($Error[0])" } } } }
Pingback: SCCM – контроль здоровья дисков | ILYA Sazonov: ITPro
How can I do if I want receive an email when the status of the value “PredictFailure” is true ?
Thanks a lot for your help
So if we run this script and it returns nothing, that’s a good thing right? It only returns output if a SMART drive is flagged as failure predicted?
That is correct.
can you add script wherein result will be sent via email? appreciate your help.
I put this in my powershell script so that it generates an email when it runs. You can have emails sent to multiple addresses, just use a semi-colon as a separator.
$Machinename = ($env:COMPUTERNAME)
$SMTPServer = “FQDN SMTP server”
$msg = new-object Net.Mail.MailMessage
$SMTP = New-Object Net.Mail.SmtpClient($SMTPServer)
$msg.From = “DO-NOT ReplyEmail@whatever.com”
$msg.To.Add(“John.Doe@whatever.com; Jane.Doe@whatever.com“)
$msg.Subject = “Pending Hard Disk Failure for $MachineName”
$msg.Priority = “High”
$msg.Body = “This email is to inform that there is a pending hard disk failure on machine…$MachineName.”
$msg.Attachments.Add($Attach)
$SMTP.Send($msg)