Using Powershell to Monitor Windows Reliability Data

There's always a lot of talk about monitoring when stuff gets installed and uninstalled on a Windows machine, or when "configuration changes" take place on a system, or even when unplanned reboots (crashes) take place... how do we audit that? As awesome as the Windows event logs are, they can be a bit unwieldy to sift through all the noise and cryptic messages.

There are lots of third-party tools for auditing software changes. Those tools can cost a lot of money. But did you know Windows already does this for you? If you run perfmon /rel on your Vista/7/2008/R2 machine, you will be greeted with this pretty picture:

Notice that you can even export all the data as a nice little XML file. So that's pretty neat. You can see all the application crashes, system crashes, when software was installed and uninstalled, etc... but that's all GUI stuff. I know what really you want is something more programmatic, customizable and automatable. It just so happens that there's a WMI class called Win32_ReliabilityRecords. Let's use Powershell to take a peek:

# Looking at Windows software installations and uninstallations and other reliability data
# Ryan Ries, Jan 5 2012
#
# Usage: .\ReliabilityData.ps1 <argument>
# Valid arguments are "ShowAll", "ShowSystemCrashes", "ShowWhateverYourImaginationIsTheLimit", ...
# Arguments are not case sensitive.

param([parameter(Mandatory=$true)]
      [string]$Argument)

Function WMIDateStringToDateTime([String] $strWmiDate) 
{ 
    $strWmiDate.Trim() > $null 
    $iYear   = [Int32]::Parse($strWmiDate.SubString( 0, 4)) 
    $iMonth  = [Int32]::Parse($strWmiDate.SubString( 4, 2)) 
    $iDay    = [Int32]::Parse($strWmiDate.SubString( 6, 2)) 
    $iHour   = [Int32]::Parse($strWmiDate.SubString( 8, 2)) 
    $iMinute = [Int32]::Parse($strWmiDate.SubString(10, 2)) 
    $iSecond = [Int32]::Parse($strWmiDate.SubString(12, 2)) 
    $iMicroseconds = [Int32]::Parse($strWmiDate.Substring(15, 6)) 
    $iMilliseconds = $iMicroseconds / 1000 
    $iUtcOffsetMinutes = [Int32]::Parse($strWmiDate.Substring(21, 4)) 
    if ( $iUtcOffsetMinutes -ne 0 ) 
    { 
        $dtkind = [DateTimeKind]::Local 
    } 
    else 
    { 
        $dtkind = [DateTimeKind]::Utc 
    } 
    return New-Object -TypeName DateTime -ArgumentList $iYear, $iMonth, $iDay, $iHour, $iMinute, $iSecond, $iMilliseconds, $dtkind 
} 

If($Argument -eq "ShowAll")
{
       $reliabilityData = Get-WmiObject Win32_ReliabilityRecords
       ForEach ($entry in $reliabilityData)
       {
              Write-Host "Computer Name: " $entry.ComputerName
              Write-Host "Event ID:      " $entry.EventIdentifier
              Write-Host "Record Number: " $entry.RecordNumber
              Write-Host "Date and Time: " $(WMIDateStringToDateTime($entry.TimeGenerated))
              Write-Host "Source:        " $entry.SourceName
              Write-Host "Product Name:  " $entry.ProductName
              Write-Host "User:          " $entry.User
              Write-Host "Message:       " $entry.Message
              Write-Host " "
       }
}

If($Argument -eq "ShowSystemCrashes")
{
       $reliabilityData = Get-WmiObject Win32_ReliabilityRecords
       ForEach ($entry in $reliabilityData)
       {
              If($entry.Message.StartsWith("The previous system shutdown") -And $entry.Message.EndsWith("was unexpected."))
              {
                     Write-Host "Computer Name: " $entry.ComputerName
                     Write-Host "Event ID:      " $entry.EventIdentifier
                     Write-Host "Record Number: " $entry.RecordNumber
                     Write-Host "Date and Time: " $(WMIDateStringToDateTime($entry.TimeGenerated))
                     Write-Host "Source:        " $entry.SourceName
                     Write-Host "Product Name:  " $entry.ProductName
                     Write-Host "User:          " $entry.User
                     Write-Host "Message:       " $entry.Message
                     Write-Host " "             
              }
       }
}

If($Argument -eq "ShowApplicationInstalls")
{
       $reliabilityData = Get-WmiObject Win32_ReliabilityRecords
       ForEach ($entry in $reliabilityData)
       {
              If($entry.Message.StartsWith("Windows Installer installed the product."))
              {
                     Write-Host "Computer Name: " $entry.ComputerName
                     Write-Host "Event ID:      " $entry.EventIdentifier
                     Write-Host "Record Number: " $entry.RecordNumber
                     Write-Host "Date and Time: " $(WMIDateStringToDateTime($entry.TimeGenerated))
                     Write-Host "Source:        " $entry.SourceName
                     Write-Host "Product Name:  " $entry.ProductName
                     Write-Host "User:          " $entry.User
                     Write-Host "Message:       " $entry.Message
                     Write-Host " "             
              }
       }
}

So those are just some ideas that I threw together, but is by no means a complete solution. Use that as a starting point, play with the script, expand on it and make it even better! And one last thing, ideally I should not be using Write-Host here but instead be preserving the objects, that way I could combine this script with other commandlets on the pipeline, etc. I'll put that in as an enhancement request...

Pingbacks and trackbacks (1)+

Comments are closed