PowerShell: Get downtime between shutdown and startup

I have a requirement to provide the amount of time that servers are unavailable for, and the way I have been asked to calculate this is to measure time between when a server shuts down and when it has started back up again. Ideally you’d extract this information from a monitoring system, but in this case a suitable one is not available.

Thus the only easy way to do this that’ll work across multiple server configurations is to read the System Event Log and look for the events that get logged when Windows shuts down (6006 from EventLog) and when it starts back up again (6005 from EventLog).

However, there is another option that needs to be accounted for too: an unexpected shutdown. In these situations, the shutdown was unexpected so Windows wasn’t able to write an event during shutdown. Instead it writes event 6008 (from EventLog) during the next startup, and specifies the date and time of the unexpected shutdown in the event message text.

Extracting this date and time should have been a case of basic text processing and conversion to a datetime variable, but either the Event Log message, or how I’m retrieving it via PowerShell, causes some strange non-printable and non-visible on screen characters to be present around the date so I had to fiddle around a bit to strip these out so that I could then convert the text string into a datetime variable in order to do the downtime calculation. If you’re interested, do a Write-Host of the $MessageText variable once it has been populated from a 6008 event and step through the text with the cursor keys: you have to press the key twice to move one character forward before the first digit of each of the numbers that make up the date. Very strange and I’ve never seen anything like this before… Interestingly, if you run Get-WinEvent from a PowerShell window (as opposed to the ISE which I normally use) you get a question mark where the “hidden” character is, e.g. ?16/?06/?2015. I’ve logged this with Microsoft as a bug.

Anyway, here’s the script, just pass it a computer name and it’ll give you the duration of the last downtime on the computer. There’s no error handling, I’ll leave that as an exercise for the reader.

param([Parameter(Mandatory=$true)] [string]$Computer="")

#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.

$StoppedEvents = Get-WinEvent -FilterHashtable @{logname='System'; id=6006} -ComputerName $Computer -ErrorAction SilentlyContinue
$StoppedEvents += Get-WinEvent -FilterHashtable @{logname='System'; id=6008} -ComputerName $Computer -ErrorAction SilentlyContinue
$LastStoppedEvent = $StoppedEvents | Sort-Object -Property TimeCreated -Descending | Select-Object -First 1
if($LastStoppedEvent.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 = $LastStoppedEvent.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
    $MessageText = $MessageText -replace '[^\w -]', [string]::Empty
    $Array = $MessageText.Split("")
    $MessageText = ($Array[0]+" "+$Array[1])
    # now convert the "clean" text string to a DateTime
    $LastStopDateTime = [datetime]::ParseExact($MessageText,"HHmmss ddMMyyyy",[System.Globalization.CultureInfo]::InvariantCulture)
    Write-Host $LastStopDateTime -ForegroundColor Red
} else {
    $LastStopDateTime = $LastStoppedEvent.TimeCreated
    Write-Host ("Stopped: "+$LastStopDateTime+" (expected)") -ForegroundColor Yellow
}

$StartedEvents = Get-WinEvent -FilterHashtable @{logname='System'; id=6005} -ComputerName $Computer -ErrorAction SilentlyContinue
$LastStartedEvent = $StartedEvents | Sort-Object -Property TimeCreated -Descending | Select-Object -First 1
$LastStartDateTime = $LastStartedEvent.TimeCreated
Write-Host ("Started: "+$LastStartDateTime) -ForegroundColor Green

Write-Host ("Last downtime for $Computer lasted "+ ($LastStartDateTime-$LastStopDateTime))

I use Get-WinEvent because the filtering happens at the remote computer, which is more efficient than Get-EventLog which pulls all the events over the network and then filters them. The filter options can be found in this Scripting Guy blog post.

I then sort the events into descending order based on the time they were created, pick the most recent startup (printed to screen in green) and shutdown event (yellow for expected, red for unexpected) and subtract to get the downtime.

Note that 6005 is the first event Windows writes when it starts, and 6006 is the last it writes when it shuts down, so these are not going to accurately reflect how long the server was non-functional for from the point of view of whatever application or service runs on it, but they’re going to give some idea, especially if you have performance issues (e.g. slow disk) that are making what should be a fairly fast process take many minutes.

Note also that it does only retrieve the last downtime period, but you might find that your machine has been down more than once in quick succession. Consider a PC that is waiting for a reboot following update installs but hasn’t due to a logged on user. If it experiences an expected shutdown (maybe due to power failure) and then starts back up again, it’ll no longer have a user logged on, will retry the update installs and then reboot again cleanly. It is only this last reboot that’ll be reported even though the power outage may have lasted significantly longer. I am going to work on a more advanced version of this script that’ll report on total downtime over a specified period of time.

This entry was posted in PowerShell, Windows and tagged , , , , , , , , , , , , . Bookmark the permalink.

6 Responses to PowerShell: Get downtime between shutdown and startup

  1. Pingback: PowerShell: Get downtime between a range of dates/times | Robin CM's IT Blog

  2. Jed says:

    Hi this script isn’t working?
    I have tested a crash using notmyfault.
    On reboot i get 6008 event
    The previous system shutdown at 8:34:09 AM on ‎17/‎09/‎2015 was unexpected.
    You script then errors on this line of code
    $LastStopDateTime = [datetime]::ParseExact($MessageText,”HHmmss ddMMyyyy”,[System.Globalization.CultureInfo]::InvariantCulture)
    Any ideas how to fix this?

    • rcmtech says:

      Check the date format being used in $MessageText – it isn’t in the format I need to use in my location (UK). You’ll need to adjust the format of the string “HHmmss ddMMyyyy” to match what your system is using – i.e. 12 hour clock with AM/PM identifier rather than 24-hour clock.
      I suspect that means you need to use hh instead of HH, and add tt to represent the AM/PM indicator.

      • rcmtech says:

        Even better, try this:
        Replace the line:
        $LastStopDateTime = [datetime]::ParseExact($MessageText,”HHmmss ddMMyyyy”,[System.Globalization.CultureInfo]::InvariantCulture)
        with these two lines:
        $Culture = [globalization.cultureinfo]::GetCultureInfo((Get-Culture).Name)
        $LastStopDateTime = [datetime]::Parse($MessageText,$Culture)

        This will read in your “culture” and use it to convert the datetime (hopefully) correctly.

  3. Jed says:

    Thanks for your reply rcmtech
    OK i’m pretty close to getting this script to work now.
    The only problem i have is that on some random system’s when they crash with event 6008 the date that is being written to the event message is in the U.S. date format (MM/DD/YYYY) rather than the Australian date format (DD/MM/YYYY) which is breaking the script.
    Eg
    This is from a system in the right format
    The previous system shutdown at 2:14:43 PM on ‎14/‎01/‎2016 was unexpected.
    This is from a system in the wrong format
    The previous system shutdown at 4:34:41 AM on ‎1/‎14/‎2016 was unexpected.
    Does anyone know how to change this so it writes the event in the right date format?

    Note: When i run
    [globalization.cultureinfo]::GetCultureInfo((Get-Culture).Name)
    On both these system they both display exactly the same output
    Parent : en
    LCID : 3081
    KeyboardLayoutId : 3081
    Name : en-AU
    IetfLanguageTag : en-AU
    DisplayName : English (Australia)
    NativeName : English (Australia)
    EnglishName : English (Australia)
    TwoLetterISOLanguageName : en
    ThreeLetterISOLanguageName : eng
    ThreeLetterWindowsLanguageName : ENA
    CompareInfo : CompareInfo – en-AU
    TextInfo : TextInfo – en-AU
    IsNeutralCulture : False
    CultureTypes : SpecificCultures, InstalledWin32Cultures, FrameworkCultures
    NumberFormat : System.Globalization.NumberFormatInfo
    DateTimeFormat : System.Globalization.DateTimeFormatInfo
    Calendar : System.Globalization.GregorianCalendar
    OptionalCalendars : {System.Globalization.GregorianCalendar, System.Globalization.GregorianCalendar}
    UseUserOverride : False
    IsReadOnly : True

  4. Jed says:

    The previous system shutdown at 2:14:43 PM on ‎14/‎01/‎2016 was unexpected.
    The previous system shutdown at 4:34:41 AM on ‎1/‎14/‎2016 was unexpected.
    As well as above post where i have a system using U.S. date format (MM/DD/YYYY) i have now found another system that is using the Australian date format (DD/MM/YYYY) but it’s time format is in 24h format which is breaking this script. Eg
    The previous system shutdown at 17:01:45 on ‎30/‎01/‎2016 was unexpected.
    Does anyone know how to change this so it writes the event in the right date format?

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