Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Find Out Why a Windows Machine Rebooted with PowerShell

Pull shutdown, startup and crash events from the System log so you can tell a planned restart from a power cut, and see who or what asked for it.

AT A GLANCEGet-RebootHistory.ps1
What it does
Reads the System event log on one or more computers and returns every shutdown, restart, startup and unexpected power-off in the window you choose, with the user, process and reason where Windows recorded them.
Requires
  • Windows PowerShell 5.1 or PowerShell 7+
  • No modules
  • For remote computers, the Remote Event Log Management firewall rules enabled
Permissions
Reading the System log locally works for most users. Remote reads need an account in the target's Event Log Readers group or local Administrators.
Runs on
Windows 10/11, Windows Server 2016+
Tested
Parse-checked and dry-run with mocked cmdlets in PowerShell 7.4

Part 9 of the thread Windows Update, untangled

"The server rebooted overnight and nobody touched it." Maybe. Maybe not. Windows keeps a pretty good record of every shutdown and startup, and nine times out of ten it'll tell you exactly who restarted it, with which program, and what reason they picked from the drop-down.

The trick is knowing which events matter. There are five: 1074 when a user or process asks for a shutdown or restart, 6006 when the event log stops cleanly on the way down, 6005 when it starts on the way back up, 6008 when Windows notices the last shutdown wasn't clean, and 41 from Kernel-Power when the machine came back without shutting down properly at all. Put them in order and you've got the story.

The original post described this script without actually including most of it, and only looked at two of those events. This is the full thing, rewritten around Get-WinEvent with a proper filter so it's quick even on servers with huge logs.

Get-RebootHistory.ps1Download
<#
.SYNOPSIS
    Shows when a computer shut down or restarted, who or what asked for it, and whether it was clean.
.DESCRIPTION
    Reads the System event log with Get-WinEvent and returns one object per event:
      1074  User32                          A process or user asked for a shutdown or restart (with reason)
      6006  EventLog                        The event log service stopped: a clean shutdown
      6005  EventLog                        The event log service started: the machine booted
      6008  EventLog                        The previous shutdown was unexpected
      41    Microsoft-Windows-Kernel-Power  The system rebooted without shutting down cleanly
    Works against remote computers over the Remote Event Log Management firewall rules.
.PARAMETER ComputerName
    One or more computers. Defaults to this one.
.PARAMETER Days
    How far back to look. Default: 7.
.PARAMETER MaxEvents
    Cap on events per computer. Default: 500.
.PARAMETER Credential
    Credentials for remote computers.
.EXAMPLE
    .\Get-RebootHistory.ps1 -Days 30
.EXAMPLE
    .\Get-RebootHistory.ps1 -ComputerName PC-0142, SRV-APP01 | Where-Object Type -eq 'Unexpected'
#>
[CmdletBinding()]
param(
    [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('CN', 'DNSHostName')]
    [string[]]$ComputerName = $env:COMPUTERNAME,
    [ValidateRange(1, 3650)][int]$Days = 7,
    [ValidateRange(1, 100000)][int]$MaxEvents = 500,
    [pscredential]$Credential
)

begin {
    $filter = @{
        LogName      = 'System'
        ProviderName = 'User32', 'EventLog', 'Microsoft-Windows-Kernel-Power'
        Id           = 1074, 6005, 6006, 6008, 41
        StartTime    = (Get-Date).AddDays(-$Days)
    }
    # The hashtable matches any provider with any ID, so double-check the pairs we actually want.
    $wanted = @{ 1074 = 'User32'; 6005 = 'EventLog'; 6006 = 'EventLog'; 6008 = 'EventLog'; 41 = 'Microsoft-Windows-Kernel-Power' }
}

process {
    foreach ($computer in $ComputerName) {
        $params = @{ FilterHashtable = $filter; MaxEvents = $MaxEvents; ErrorAction = 'Stop' }
        if ($computer -notin @('.', 'localhost', $env:COMPUTERNAME)) { $params.ComputerName = $computer }
        if ($Credential) { $params.Credential = $Credential }

        try {
            $events = Get-WinEvent @params
        }
        catch {
            if ($_.FullyQualifiedErrorId -like 'NoMatchingEventsFound*') {
                Write-Verbose "$computer : no shutdown or startup events in the last $Days day(s)"
            }
            else {
                Write-Warning "$computer : $($_.Exception.Message)"
            }
            continue
        }

        foreach ($e in $events) {
            if ($wanted[$e.Id] -ne $e.ProviderName) { continue }

            $type = $null; $user = $null; $process = $null; $reason = $null
            switch ($e.Id) {
                1074 {
                    # Properties: 0 process, 1 computer, 2 reason, 3 reason code, 4 shutdown type, 5 comment, 6 user
                    $p       = $e.Properties.Value
                    $type    = (Get-Culture).TextInfo.ToTitleCase([string]$p[4])
                    $process = ($p[0] -replace '\s*\(.*\)$', '')
                    $reason  = (@($p[2], $p[5]) | Where-Object { $_ }) -join ' | '
                    $user    = $p[6]
                }
                6005 { $type = 'Startup' }
                6006 { $type = 'Clean shutdown' }
                6008 { $type = 'Unexpected'; $reason = 'Previous shutdown was unexpected' }
                41   { $type = 'Unexpected'; $reason = 'Kernel-Power 41: rebooted without a clean shutdown' }
            }

            [pscustomobject]@{
                ComputerName = $e.MachineName
                TimeCreated  = $e.TimeCreated
                EventId      = $e.Id
                Type         = $type
                User         = $user
                Process      = $process
                Reason       = $reason
            }
        }
    }
}

Parameters

ParameterTypeDefaultWhat it's for
-ComputerNamestring[]$env:COMPUTERNAMEOne or more computers. Takes pipeline input, including from Get-ADComputer.
-Daysint7How far back to look.
-MaxEventsint500Cap on events per computer, newest first. Keeps a flapping machine from burying you.
-Credentialpscredential—Credentials for remote computers.

Run it

The last week on this machine.

.\Get-RebootHistory.ps1

A month on a server, oldest first, so it reads like a timeline.

.\Get-RebootHistory.ps1 -ComputerName SRV-APP01 -Days 30 | Sort-Object TimeCreated

Only the crashes and power losses across a few machines.

.\Get-RebootHistory.ps1 -ComputerName SRV-APP01, SRV-APP02, SRV-SQL01 -Days 90 | Where-Object Type -eq 'Unexpected'

Who's been restarting things, and how often?

.\Get-RebootHistory.ps1 -ComputerName SRV-APP01 -Days 90 | Where-Object EventId -eq 1074 | Group-Object User | Sort-Object Count -Descending

What you'll see

Example outputvalues are illustrative
TimeCreated          EventId Type           User           Process                              Reason
-----------          ------- ----           ----           -------                              ------
9/28/2026 3:02:14 AM    6005 Startup
9/28/2026 3:01:02 AM    6006 Clean shutdown
9/28/2026 3:00:47 AM    1074 Restart        NT AUTHORITY\SYSTEM C:\Windows\servicing\TrustedInstaller.exe Operating System: Upgrade (Planned)
9/24/2026 2:41:09 PM    6005 Startup
9/24/2026 2:41:07 PM      41 Unexpected                                                         Kernel-Power 41: rebooted without a clean shutdown
9/24/2026 2:41:07 PM    6008 Unexpected                                                         Previous shutdown was unexpected

How it works

  1. One filter, five events. Get-WinEvent -FilterHashtable asks the System log for events 1074, 6005, 6006, 6008 and 41 from the three providers that write them (User32, EventLog and Microsoft-Windows-Kernel-Power), newer than your start time. The filtering happens on the target machine, so it's fast even when the log is enormous.
  2. Double-check the pairs. The hashtable matches any of those IDs from any of those providers, so the script throws away the odd combination nobody wanted, like an ID 41 from the wrong provider.
  3. Pull the details out of 1074. Event 1074 carries its data as properties: the process, the reason text, the shutdown type and the user. The script reads them by position instead of scraping the message text, which holds up much better across Windows versions and languages.
  4. Label everything. Each event becomes an object with a plain-English Type: Restart or Power Off (from 1074), Clean shutdown, Startup, or Unexpected.
  5. Keep going on errors. An unreachable machine gets a warning. A machine with no matching events in the window gets a quiet verbose message instead of a red error, because "nothing happened" is a perfectly good answer.

Take it further

  • Measure downtime. Pair each shutdown (6006 or 6008) with the next 6005 and subtract the times. Now you've got downtime per reboot.
  • Alert on unexpected reboots. Schedule it daily with -Days 1 across your servers and send anything with Type -eq 'Unexpected' to your chat channel.
  • Catch the reasons nobody fills in. Group 1074 events by Reason. A lot of "Other (Unplanned)" is a good excuse to turn on the Shutdown Event Tracker prompt on servers.

Things that'll trip you up

  • Event 41 doesn't mean bad hardware. It just means Windows came back without a clean shutdown first. Someone holding the power button, a UPS running flat, a hypervisor host crash and an actual bugcheck all look the same here. Check for a minidump and the hardware logs before you order a new power supply.
  • Remote reads need the firewall open. Get-WinEvent -ComputerName uses the Remote Event Log Management rules, not WinRM. If you get "The RPC server is unavailable", enable that rule group on the target, or run the script on the machine itself with Invoke-Command.
  • Old events roll off. The System log has a size limit, and a chatty server can overwrite a month of history in a week. If the oldest event is newer than your -Days window, the log wrapped. Bump the log size if you need longer history.
  • Fast startup blurs the picture. On Windows 10 and 11 clients with Fast Startup on, "Shut down" is really a hibernate, so you won't always see a 6006/6005 pair. A restart still does a full shutdown and logs normally.