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.
- 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
"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.
<#
.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
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-ComputerName | string[] | $env:COMPUTERNAME | One or more computers. Takes pipeline input, including from Get-ADComputer. |
-Days | int | 7 | How far back to look. |
-MaxEvents | int | 500 | Cap on events per computer, newest first. Keeps a flapping machine from burying you. |
-Credential | pscredential | — | Credentials for remote computers. |
Run it
The last week on this machine.
.\Get-RebootHistory.ps1A month on a server, oldest first, so it reads like a timeline.
.\Get-RebootHistory.ps1 -ComputerName SRV-APP01 -Days 30 | Sort-Object TimeCreatedOnly 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 -DescendingWhat you'll see
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
- One filter, five events.
Get-WinEvent -FilterHashtableasks the System log for events 1074, 6005, 6006, 6008 and 41 from the three providers that write them (User32,EventLogandMicrosoft-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. - 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.
- 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.
- Label everything. Each event becomes an object with a plain-English
Type: Restart or Power Off (from 1074), Clean shutdown, Startup, or Unexpected. - 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 1across your servers and send anything withType -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.