PowerShell: Get downtime between a range of dates/times

This is an enhancement of my previous script which found the duration of the most recent downtime of a Windows machine. As with that script, by “downtime” I mean the time between the machine shutting down (cleanly or unexpectedly) and starting up again, as found from the System Event Log entries with IDs 6005 (startup), 6006 (normal shutdown), and 6008 (unexpected shutdown).

The script by default searches the local machine’s event log, and looks for all events. It returns a table of the downtime periods, the durations of each, whether or not each downtime period was normal (i.e. a clean shutdown) or unexpected (i.e. Windows did not shut down normally), and the total downtime from all the downtime periods found. As with the previous script, this does not take account of the downtime of the applications or services on the server, just Windows itself. The functionality loss caused by the downtime will probably be longer but would need monitoring in a much more complicated, application-specific, and probably external way.

You can specify a few optional parameters:

  • -Computer <computer name> will retrieve the data from a remote machine
  • -FromDate <datetime> will only retrieve downtime starting from the specified date and/or time. Tip: always use ISO 8601 format with Powershell, especially if you don’t live in the USA. You don’t have to specify the time if you don’t need to, 2015-06-16 will do – the time will default to midnight.
  • -ToDate <datetime> retrieves downtime up until the specified date and time.

Note that the Event Logs by default remove older events to make room for newer ones, so you may not have any start, stop (or both) events presents. The script needs at least one start and one stop event to be able to calculate the downtime and will tell you if it hasn’t found any.

Example output:

PS DFSScripts:\robin> \\rcmtech\Scripts\Robin\GetDowntimeHistory.ps1
Searching events on computer RCM01, range 1970-01-01T00:00:00 - 2015-06-19T13:35:05

Started             Stopped             Duration ShutdownType
-------             -------             -------- ------------
16/06/2015 15:05:24 16/06/2015 15:04:47 00:00:37 Normal      
16/06/2015 15:04:28 16/06/2015 15:03:04 00:01:24 Unexpected  
16/06/2015 08:09:01 16/06/2015 08:07:36 00:01:25 Unexpected  
15/06/2015 09:43:23 15/06/2015 09:42:37 00:00:46 Normal      
21/05/2015 03:40:43 21/05/2015 03:39:52 00:00:51 Normal      
19/05/2015 13:43:25 19/05/2015 13:42:39 00:00:46 Normal      
19/05/2015 07:54:38 19/05/2015 07:53:54 00:00:44 Normal      
18/05/2015 13:00:59 18/05/2015 13:00:28 00:00:31 Normal      
18/05/2015 12:51:03 18/05/2015 12:50:20 00:00:43 Normal      

Total downtime in selected period is 467 seconds

Here’s the script:

param(
    [string]$Computer=$env:COMPUTERNAME,
    [datetime]$FromDate="1970-01-01T00:00:00",
    [datetime]$ToDate = (Get-Date)
)
# Note: Please specify dates/times in either US format, or, preferably, ISO 8601 format: 2015-06-01 or 2015-06-01T13:45:00
# If dates are specified in UK format, e.g. 16/06/2015 they will be converted to US format by PowerShell and confusion will reign.

Write-Host ("Searching events on computer $Computer, range "+($FromDate | Get-Date -Format s)+" - "+($ToDate | Get-Date -Format s))

# Event IDs for EventLog:
# 6005 = The Event log service was started.
# 6006 = The Event log service was stopped.
# 6008 = The previous system shutdown at  on  was unexpected.

# Flags so we know if we've found any stop events at all (we might not, and if that's the case we can't compute the downtime)
$Found6006 = $false
$Found6008 = $false
# Create arrays for holding the event log data. We have to force the data to be added to an array even if there's only
# one result as otherwise we have potential issues trying to add a System.Diagnostics.Eventing.Reader.EventRecord (if only a single stop event of one type is found)
# to a System.Array (if multiple stop events of a different type are found). We also need to be able to step through an array later when pairing up starts and stops.
$NormalStopEvents=@()
$UnexpectedStopEvents=@()
$StartEvents = @()
try{
    # Rebuild the array in place adding any events found
    $NormalStopEvents += Get-WinEvent -FilterHashtable @{logname='System'; id=6006; starttime=$FromDate; endtime=$ToDate} -ComputerName $Computer -ErrorAction Stop
    $Found6006 = $true
}catch{ # No 6006 events found
}
try{
    $UnexpectedStopEvents += Get-WinEvent -FilterHashtable @{logname='System'; id=6008; starttime=$FromDate; endtime=$ToDate} -ComputerName $Computer -ErrorAction Stop
    $Found6008 = $true
}catch{ # No 6008 events found
}
if($Found6006 -eq $false -and $Found6008 -eq $false){
    Write-Host "Could not find any stop events (6006 or 6008) during time specified, unable to continue" -ForegroundColor Red
    return
}
$StopEvents = $NormalStopEvents + $UnexpectedStopEvents | Sort-Object -Property TimeCreated -Descending
try{
    $StartEvents += Get-WinEvent -FilterHashtable @{logname='System'; id=6005; starttime=$FromDate; endtime=$ToDate} -ComputerName $Computer -ErrorAction Stop
}catch{
    Write-Host "Could not find any start events (6005) during time specified, unable to continue" -ForegroundColor Red
    return
}
$StartEvents += $StartEvents | Sort-Object -Property TimeCreated -Descending

$Results = @()
for($i=0;$i -lt $StartEvents.Count;$i++){
    $ThisStopDateTime = $null
    if($StopEvents[$i].Id -eq 6008){
        # Last shutdown was unexpected, this event is logged as the computer starts so we
        # have to extract the date & time of the stop from the event message text
        [string]$MessageText = $StopEvents[$i].Message
        $MessageText = $MessageText.Replace("The previous system shutdown at ","")
        $MessageText = $MessageText.Replace("was unexpected.","")
        $MessageText = $MessageText.Replace("on ","")
        # There are some weird non-printable characters in the text string so now we have
        # to faff around to clean them out. if you try and convert $MessageText to a DateTime
        # without doing this it will fail, even though the format string looks correct in the ISE (it shows ? in a PowerShell window)
        $MessageText = $MessageText -replace '[^\w -]', [string]::Empty
        $Array = $MessageText.Split("")
        $MessageText = ($Array[0]+" "+$Array[1])
        # Now convert the "clean" text string to a DateTime
        $ThisStopDateTime = [datetime]::ParseExact($MessageText,"HHmmss ddMMyyyy",[System.Globalization.CultureInfo]::InvariantCulture)
        $ShutdownType = "Unexpected"
    } else {
        $ThisStopDateTime = $StopEvents[$i].TimeCreated
        $ShutdownType = "Normal"
    }

    if($ThisStopDateTime -ne $null){
        $ThisResult = New-Object System.Object
        $ThisResult | Add-Member -MemberType NoteProperty -Name "Started" -Value $StartEvents[$i].TimeCreated
        $ThisResult | Add-Member -MemberType NoteProperty -Name "Stopped" -Value $ThisStopDateTime
        $ThisResult | Add-Member -MemberType NoteProperty -Name "Duration" -Value ($StartEvents[$i].TimeCreated - $ThisStopDateTime)
        $ThisResult | Add-Member -MemberType NoteProperty -Name "ShutdownType" -Value $ShutdownType
        $Results += $ThisResult
    }
}

# Display results
$Results | Format-Table -AutoSize
foreach($Result in $Results){
    $TotalDowntime = $TotalDowntime + $Result.Duration
}
Write-Host ("Total downtime in selected period is "+$TotalDowntime.TotalSeconds+" seconds")
    
This entry was posted in PowerShell, Windows and tagged , , , , , , , , , , , . Bookmark the permalink.

One Response to PowerShell: Get downtime between a range of dates/times

  1. Caleb says:

    awesome script, however I made a slight adjustment as I was having issues with the formatting on unexpected shutdowns… I found it was easier to just restructure the $MessageText text do be a format that you can simple use a get-date on, though I understand why you split it into an array as those spaces in the date were a pain. Here is my code change below.

    # Last shutdown was unexpected, this event is logged as the computer starts so we
    # have to extract the date & time of the stop from the event message text
    [string]$MessageText = $StopEvents[$i].Message
    $MessageText = $MessageText.Replace(“The previous system shutdown at “,””)
    $MessageText = $MessageText.Replace(“was unexpected.”,””)
    $MessageText = $MessageText.Replace(“/ “,””)
    $pos = $MessageText.IndexOf(“on”)
    $leftPart = $MessageText.Substring(0, $pos-1)
    $rightPart = $MessageText.Substring($pos+4)
    $MessageText = $rightpart + $leftPart

    $messagetext = $MessageText -replace “[^\u0020-\u007F]”, [string]::Empty
    # Now convert the “clean” text string to a DateTime
    $ThisStopDateTime = get-date $MessageText

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s