<# .SYNOPSIS Display the SMART status for all local harddisks in a popup window and in Windows' Notification Area (a.k.a. System Tray) .DESCRIPTION This script uses WMI to query the status of all local physical harddisk drives, and displays the results in a popup window and in Windows' Notification Area (a.k.a. System Tray). In the status display, each physical harddisk is associated with the drive letter of one of its volumes. Since Windows 10 limits the number of lines in desktop notifications, any errors will be placed at the top of the displayed status list, otherwise the list is sorted by drive letter. If all disks report OK, the script returns True and return code ("ErrorLevel") 0, otherwise False and return code 1. Since this script uses WMI and drive letters, it will not work in Linux. .PARAMETER Modal Make the popup window always stay on top .PARAMETER Hide Hide the console window when the script is started, restore it (but minimized) afterwards .PARAMETER Version Show this script's version number; if combined with -Verbose show full script path, version number and last modified or release date .PARAMETER Debug Display intermediate results for each disk drive in console; if combined with -Verbose show intermediate results for each associated volume too .PARAMETER Help Show this script's help screen .OUTPUTS True and return code 0 if all disks report OK, otherwise False and return code 1 .EXAMPLE . ./GetHDDStatusGUI.ps1 Will display a popup window with the drive letters in use and the SMART status of the associated physical harddisk drive. .EXAMPLE . ./GetHDDStatusGUI.ps1 -Debug -Verbose Will list details for all physical harddisk drives and volumes in the console, and display a popup window with the drive letters in use and the SMART status of the associated physical harddisk drive. .EXAMPLE . ./GetHDDStatusGUI.ps1 -Version -Verbose Will display this script's full path, version number and last modified or release date. .LINK Script written by Rob van der Woude: https://www.robvanderwoude.com/ .LINK Disk check based on code by Geoff: http://www.uvm.edu/~gcd/2013/01/which-disk-is-that-volume-on .LINK System Tray ToolTip Balloon code by Don Jones: http://blog.sapien.com/current/2007/4/27/creating-a-balloon-tip-notification-in-powershell.html .LINK Extract icons from Shell32.dll by Thomas Levesque: http://stackoverflow.com/questions/6873026 .LINK Hide and restore console by Anthony: http://stackoverflow.com/a/15079092 .LINK Capture common parameters by mklement0: https://stackoverflow.com/a/48643616 #> param ( [parameter( ValueFromRemainingArguments = $true )] [string[]]$Args, # Leave all argument validation to the script, not to PowerShell [switch]$Modal, [switch]$Hide, [switch]$Version, [switch]$Help ) $progver = "1.04" [bool]$Debug = ( $PSBoundParameters.ContainsKey( 'Debug' ) ) [bool]$Verbose = ( $PSBoundParameters.ContainsKey( 'Verbose' ) ) # Show help if any unnamed argument is given $Help = $Help -or ( $Args.Length -gt 0 ) # Show help if running in Linux $Help = $Help -or ( $HOME[0] -eq '/' ) # Help disables Hide parameter $Hide = $Hide -and -not $Help # Debug disables Hide parameter $Hide = $Hide -and -not $Debug if ( $Help ) { Get-Help "$PSCommandPath" -Full exit 1 } if ( $Version ) { if ( $Verbose ) { $lastmod = ( [System.IO.File]::GetLastWriteTime( $PSCommandPath ) ) if ( $lastmod.ToString( "h.mm" ) -eq $progver ) { "`"{0}`", Version {1}, release date {2}" -f $PSCommandPath, $progver, $lastmod.ToString( "yyyy-MM-dd" ) } else { # if last modified time is not equal to program version, the script has been tampered with "`"{0}`", Version {1}, last modified date {2}" -f $PSCommandPath, $progver, $lastmod.ToString( "yyyy-MM-dd" ) } } else { $progver } exit 0 } ####################################### # Hide console window # # by Anthony on StackOverflow.com # # http://stackoverflow.com/a/15079092 # ####################################### $signature1 = @' public static void ShowConsoleWindow( int state ) { var handle = GetConsoleWindow( ); ShowWindow( handle, state ); } [System.Runtime.InteropServices.DllImport( "kernel32.dll" )] static extern IntPtr GetConsoleWindow( ); [System.Runtime.InteropServices.DllImport( "user32.dll" )] static extern bool ShowWindow( IntPtr hWnd, int nCmdShow ); '@ $hideconsole = Add-Type -MemberDefinition $signature1 -Name Hide -Namespace HideConsole -ReferencedAssemblies System.Runtime.InteropServices -PassThru # Hide console if ( $Hide ) { $hideconsole::ShowConsoleWindow( 0 ) } ################## # Disk inventory # ################## [System.Collections.SortedList]$volumedetails = New-Object System.Collections.SortedList [System.Collections.SortedList]$volumestatus = New-Object System.Collections.SortedList if ( $Debug ) { Write-Host "Disk#`tStatus`tSize (GB)`tModel" Write-Host "=====`t======`t=========`t=====" } $diskdrives = Get-WmiObject -Namespace "root/CIMV2" -Class Win32_DiskDrive $warnings = $false foreach ( $disk in $diskdrives ) { $diskindex = $disk.Index $diskmodel = $disk.Model -replace "Disk Device","Disk" $disksize = "{0,5:F0} GB" -f ( $disk.Size / 1GB ) $diskstatus = $disk.Status if ( $Debug ) { if ( $Verbose ) { Write-Host } Write-Host ( "{0}`t{1}`t{2}`t{3}" -f $diskindex, $diskstatus, $disksize, $diskmodel ) } $part_query = 'ASSOCIATORS OF {Win32_DiskDrive.DeviceID="' + $disk.DeviceID.replace('\','\\') + '"} WHERE AssocClass=Win32_DiskDriveToDiskPartition' $partitions = @( Get-WmiObject -Query $part_query | Sort-Object StartingOffset ) foreach ( $partition in $partitions ) { $vol_query = 'ASSOCIATORS OF {Win32_DiskPartition.DeviceID="' + $partition.DeviceID + '"} WHERE AssocClass=Win32_LogicalDiskToPartition' $volumes = @( Get-WmiObject -Query $vol_query ) foreach ( $volume in $volumes ) { # 0 = Unknown; 1 = No Root Directory; 2 = Removable Disk; 3 = Local Disk; 4 = Network Drive; 5 = Compact Disc; 6 = RAM Disk # DriveType 3 means harddisks only if ( $volume.DriveType -eq 3 ) { if ( $Debug -and $Verbose ) { Write-Host ( "{0}`t{1,2}`t{2}`t{3}" -f $diskindex, $volume.Name, $disksize, $diskmodel ) } if ( -not $volumedetails.Contains( $volume.Name ) ) { $volumedetails.Add( $volume.Name, "[Disk {0,2}] {1} {2}" -f ( $diskindex, $disksize, $diskmodel ) ) $volumestatus.Add( $volume.Name, $diskstatus ) } } } } } ################# # Dialog window # ################# Add-Type -AssemblyName System.Windows.Forms [System.Windows.Forms.Application]::EnableVisualStyles( ) $form = New-Object System.Windows.Forms.Form $form.Width = 640 $form.Height = 25 * $volumedetails.Count + 120 $form.Font = 'Courier New, 10' $form.BackColor = 'White' $form.MaximizeBox = $false; $form.FormBorderStyle = 'FixedSingle' $form.TopMost = $Modal $y = 0 $even = $false $volumedetails.Keys | ForEach-Object { $label = New-Object System.Windows.Forms.Label $label.Size = '35, 20' $label.Location = New-Object System.Drawing.Point( 10, ( 15 + $y ) ) $label.Text = "$_" if ( $even ) { $label.BackColor = 'ButtonFace' } $form.Controls.Add( $label ) $status = ( $volumestatus[$_] ) $label = New-Object System.Windows.Forms.Label $label.Size = '40, 20 ' $label.Location = New-Object System.Drawing.Point( 45, ( 15 + $y ) ) $label.Text = $status if ( $status -eq "OK" ) { $label.ForeColor = 'Green' } else { $label.ForeColor = 'Red' $warnings = $true } if ( $even ) { $label.BackColor = 'ButtonFace' } $form.Controls.Add( $label ) $label = New-Object System.Windows.Forms.Label $label.Size = '490, 20' $label.Location = New-Object System.Drawing.Point( 85, ( 15 + $y ) ) $label.Text = $volumedetails[$_] if ( $even ) { $label.BackColor = 'ButtonFace' } $form.Controls.Add( $label ) $y = $y + 25 $even = -not $even } if ( $warnings ) { $form.Text = "HDD Status Warning" } else { $form.Text = "HDD Status OK" } $buttonOK = New-Object System.Windows.Forms.Button $buttonOK.BackColor = 'ButtonFace' $buttonOK.Text = "OK" $buttonOK.Size = '60,24' $buttonOK.Location = New-Object System.Drawing.Point( 85, ( 30 + $y ) ) $form.Controls.Add( $buttonOK ) $form.AcceptButton = $buttonOK # Pressing Enter closes the dialog $form.CancelButton = $buttonOK # Pressing Escape closes the dialog ######################################## # System tray balloon tip notification # ######################################## [System.Windows.Forms.ToolTipIcon]$icon = [System.Windows.Forms.ToolTipIcon]::Info $title = "HDD Status OK" $systraymessage = "" $volumedetails.Keys | ForEach-Object { $status = ( $volumestatus[$_] ) if ( $status -eq "OK" ) { $systraymessage = $systraymessage + "$_`t$status`n" # list in alphabetical sort order } else { $systraymessage = "$_`t$status`n$systraymessage" # errors at top of list $icon = [System.Windows.Forms.ToolTipIcon]::Error $title = "Warning: HDD Errors" } } ################################################################ # Extract system tray icon from Shell32.dll # # C# code to extract icons from Shell32.dll by Thomas Levesque # # http://stackoverflow.com/questions/6873026 # ################################################################ $signature2 = @' [DllImport( "Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall )] private static extern int ExtractIconEx( string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons ); public static Icon Extract( string file, int number, bool largeIcon ) { IntPtr large; IntPtr small; ExtractIconEx( file, number, out large, out small, 1 ); try { return Icon.FromHandle( largeIcon ? large : small ); } catch { return null; } } '@ $iconextractor = Add-Type -MemberDefinition $signature2 -Name IconExtract -Namespace IconExtractor -ReferencedAssemblies System.Windows.Forms,System.Drawing -UsingNamespace System.Windows.Forms,System.Drawing -PassThru # System tray icon depends on status if( $title -eq "HDD Status OK" ) { $systrayicon = $iconextractor::Extract( "C:\Windows\System32\shell32.dll", 223, $true ) } else { $systrayicon = $iconextractor::Extract( "C:\Windows\System32\shell32.dll", 53, $true ) } # Show system tray icon and balloon tip $notify = New-Object System.windows.Forms.NotifyIcon $notify.BalloonTipText = $systraymessage $notify.BalloonTipTitle = $title $notify.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]$icon $notify.Icon = $systrayicon $notify.Visible = $true $notify.ShowBalloonTip( 30000 ) # Show dialog [void] $form.ShowDialog( ) ########################################## # Restore console minimized (2) # # Change to 1 to restore to normal state # ########################################## if ( $Hide ) { $hideconsole::ShowConsoleWindow( 2 ) } ################################# # Exit code 1 in case of errors # ################################# if ( $warnings ) { $false exit 1 } else { $true exit 0 }