😎

Windows電源断イベントログの簡易確認スクリプト

2023/03/27に公開

WindowsPCをつけっぱなしにしている間に自動で再起動されていることを発見した時、WindowsUpdateだろうと思いながら直近の再起動ログを確認するのが面倒だったので確認用PowerShellスクリプトを作成しました。同じような方の役に立てば幸いです。

動作確認環境

PS C:\> (Get-WmiObject Win32_OperatingSystem)['Version']
10.0.19045
PS C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.2673
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.2673
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

実装

適当さが目立ちますが気にしないでください。

confirm_poweroff_log.ps1
# Copyright (c) 2023 scirexs
# This software is released under the MIT License.
# See https://opensource.org/license/mit/
$EVNAME = @{Shutdown="Shutdown";ShutdownHS="ShutdownHS";Reboot="Reboot";Hibernation="Hibernation";Sleep="Sleep";WUFail="WUFail";Crash="CRASH"}
$LOG_PATH = "C:\Windows\system32\winevt\Logs\System.evtx"
$DATETIME_FORMAT = "yyyy-MM-ddTHH:mm:ss.000Z"
$XPATH_DEFAULT = "Event/System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=109] or Event/System[Provider[@Name='Microsoft-Windows-Kernel-Boot'] and EventID=27] or (Event/System[(Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=42)] and Event/EventData[Data[@Name='TargetState']=6])"
$XPATH_SHUTDOWN = "(Event/System[(Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=109)] and Event/EventData[Data[@Name='ShutdownActionType']=6]) or (Event/System[Provider[@Name='Microsoft-Windows-Kernel-Boot'] and EventID=27] and Event/EventData[Data[@Name='BootType']=0]) or "
$XPATH_SHUTDOWNHS = "(Event/System[(Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=42)] and Event/EventData[Data[@Name='TargetState']=6]) or (Event/System[Provider[@Name='Microsoft-Windows-Kernel-Boot'] and EventID=27] and Event/EventData[Data[@Name='BootType']=1]) or "
$XPATH_REBOOT = "(Event/System[(Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=109)] and Event/EventData[Data[@Name='ShutdownActionType']=5]) or (Event/System[Provider[@Name='Microsoft-Windows-Kernel-Boot'] and EventID=27] and Event/EventData[Data[@Name='BootType']=0]) or "
$XPATH_HIBERNATION = "(Event/System[(Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=42)] and Event/EventData[Data[@Name='TargetState']=5]) or (Event/System[Provider[@Name='Microsoft-Windows-Power-Troubleshooter'] and EventID=1] and Event/EventData[Data[@Name='TargetState']=5]) or "
$XPATH_SLEEP = "(Event/System[(Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=42)] and Event/EventData[Data[@Name='TargetState']=4]) or (Event/System[Provider[@Name='Microsoft-Windows-Power-Troubleshooter'] and EventID=1] and Event/EventData[Data[@Name='TargetState']=4]) or "
$XPATH_WUREBOOT = "Event/System[Provider[@Name='User32'] and EventID=1074 and TimeCreated[@SystemTime>='##FROM##' and @SystemTime<='##TO##']] and Event/EventData[Data[@Name='param4']='0x80020010' and Data[@Name='param1']='C:\WINDOWS\system32\svchost.exe (##PCNAME##)']"
$XPATH_ERROR = "Event/System[TimeCreated[@SystemTime>='##FROM##']] and (Event/System[Provider[@Name='Microsoft-Windows-WindowsUpdateClient'] and EventID=20] or Event/System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=41] or Event/System[Provider[@Name='eventlog'] and EventID=6008])"
$xpath = ""; $max_count = 0; $check_wureboot = $FALSE; $check_syserr = $FALSE; $omit_out = $TRUE

function getOutputDict($log, $event) {
  $ret = @{Type="N";Event=$event;TimeStamp=$log.TimeCreated;RecordID=$log.RecordID;Message="EventID:"+$log.Id+", Provider:"+$log.ProviderName}
  switch ($event) {
    $EVNAME.WUFail {$ret.Type="E"; $ret.Message=$log.Message; break}
    $EVNAME.Crash {$ret.Type="E"; $ret.Message=$log.Message; break}
  }
  return $ret
}

if (-not([string]::IsNullorEmpty($Args[0]))) {
  if ([int]::TryParse($Args[0],[ref]$max_count)) {
    if ($max_count -lt 1) {
      $max_count = 0
    }
  }
}
if (-not([string]::IsNullorEmpty($Args[1]))) {
  if ($Args[1].Contains('s')) {
    $xpath += $XPATH_SHUTDOWN
    $xpath += $XPATH_SHUTDOWNHS
  }
  if ($Args[1].Contains('r')) {
    $xpath += $XPATH_REBOOT
    $check_wureboot = $TRUE
  }
  if ($Args[1].Contains('h')) {
    $xpath += $XPATH_HIBERNATION
  }
  if ($Args[1].Contains('l')) {
    $xpath += $XPATH_SLEEP
  }
  if ($Args[1].Contains('e')) {
    $check_syserr = $TRUE
  }
}

if ([string]::IsNullorEmpty($xpath)) {
  #default condition
  $xpath = $XPATH_DEFAULT
  $check_wureboot = $TRUE
  $check_syserr = $TRUE
} else {
  $xpath = $xpath.Remove($xpath.Length-4,4)
}
if ($max_count -eq 0) {
  #default condition
  $max_count = 3
}

$out = New-Object System.Collections.ArrayList; $i = 0; $tmp = $NULL
$logs = (Get-WinEvent -Path $LOG_PATH -FilterXPath $xpath -MaxEvents $($max_count*3) | Sort-Object -Descending TimeCreated)
foreach ($log in $logs) {
  if ($tmp -eq $NULL) {
    if ($log.Id -eq 27 -or $log.Id -eq 1) {
      $tmp = $log
    }
  } else {
    if ($log.Id -eq 109 -or $log.Id -eq 42) {
      $xml = [XML]$log.ToXml()
      $line = @{}
      if ($tmp.Id -eq 27) {
        if ($log.Id -eq 109 -and ($xml.Event.EventData.Data | where Name -eq "ShutdownActionType")."#text" -eq "6") {
          $line = getOutputDict $log $EVNAME.Shutdown
        } elseif ($log.Id -eq 42 -and ($xml.Event.EventData.Data | where Name -eq "TargetState")."#text" -eq "6") {
          $line = getOutputDict $log $EVNAME.ShutdownHS
        } elseif ($log.Id -eq 109 -and ($xml.Event.EventData.Data | where Name -eq "ShutdownActionType")."#text" -eq "5") {
          $line = getOutputDict $log $EVNAME.Reboot
        }
      } elseif ($tmp.Id -eq 1) {
        if ($log.Id -eq 42 -and ($xml.Event.EventData.Data | where Name -eq "TargetState")."#text" -eq "5") {
          $line = getOutputDict $log $EVNAME.Hibernation
        } elseif ($log.Id -eq 42 -and ($xml.Event.EventData.Data | where Name -eq "TargetState")."#text" -eq "4") {
          $line = getOutputDict $log $EVNAME.Sleep
        }
      }
      [void]$out.Add($line)
      $i++
    }
    $tmp = $NULL
    if ($i -ge $max_count) {
      break
    }
  }
}

if ($check_wureboot) {
  $ErrorActionPreference = "Stop"
  for ($i=0;$i -lt $out.Count;$i++) {
    if ($out[$i].Event -eq $EVNAME.Reboot) {
      $from = $out[$i].TimeStamp.ToUniversalTime().AddMinutes(-2).ToString($DATETIME_FORMAT.Replace("s","0"))
      $to = $out[$i].TimeStamp.ToUniversalTime().ToString($DATETIME_FORMAT)
      $xpath = $XPATH_WUREBOOT.Replace("##FROM##",$from).Replace("##TO##",$to).Replace("##PCNAME##",$Env:Computername)
      $logs = ""
      try {
        $logs = (Get-WinEvent -Path $LOG_PATH -FilterXPath $xpath)
      } catch {
        if ($Global:Error[0].CategoryInfo.Category.ToString() -ne "ObjectNotFound") {
          Write-Host "Unknown error occurred while getting User32 log."
          $ErrorActionPreference = "Continue"
          exit
        }
      }
      if ($logs.Length -ne 0) {
        $out[$i].Message = "[Auto reboot by windows update] " + $out[$i].Message
      }
    }
  }
  $ErrorActionPreference = "Continue"
}

if ($check_syserr) {
  $ErrorActionPreference = "Stop"
  $from = $out[$out.Count-1].TimeStamp.ToUniversalTime().ToString($DATETIME_FORMAT)
  $xpath = $XPATH_ERROR.Replace("##FROM##",$from)
  $logs = ""
  try {
    $logs = (Get-WinEvent -Path $LOG_PATH -FilterXPath $xpath)
  } catch {
    if ($Global:Error[0].CategoryInfo.Category.ToString() -ne "ObjectNotFound") {
      Write-Host "Unknown error occurred while getting system error log."
      $ErrorActionPreference = "Continue"
      exit
    }
  }
  if ($logs.Length -ne 0) {
    $line = @{}
    foreach ($log in $logs) {
      if ($log.Id -eq 20) {
        $line = getOutputDict $log $EVNAME.WUFail
      } elseif ($log.Id -eq 41) {
        $line = getOutputDict $log $EVNAME.Crash
      } elseif ($log.Id -eq 6008) {
        $line = getOutputDict $log $EVNAME.Crash
      }
      [void]$out.Add($line)
    }
    $omit_out = $FALSE
  }
  $ErrorActionPreference = "Continue"
}

if ($omit_out) {
  Write-Output $out.ToArray() | % {New-Object PSCustomObject -Property $_} | Format-Table -Property Event,TimeStamp,Message
} else {
  Write-Output $out.ToArray() | % {New-Object PSCustomObject -Property $_} | Sort-Object -Descending TimeStamp | Format-Table -Property Type,RecordID,Event,TimeStamp,Message
}
pause

使用方法

Get-WinEventコマンドを使用しているため管理者権限で実行する必要があります。
エラーログはWindowsUpdateの失敗ログと予期せぬ電源断と思われるログのみが出力対象です。

一番シンプルな使い方

引数無しで実行すると直近の必要十分だろうと思われる情報が出力されます。

実行例

PS C:\> .\confirm_poweroff_log.ps1

Event    TimeStamp           Message
-----    ---------           -------
Shutdown 2023-03-20 17:41:40 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-03-15 03:17:46 [Auto reboot by windows update] EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-02-15 12:03:59 EventID:109, Provider:Microsoft-Windows-Kernel-Power


続行するには、Enter キーを押してください...:
PS C:\> .\confirm_poweroff_log.ps1 7

Type RecordID Event    TimeStamp           Message
---- -------- -----    ---------           -------
N       45361 Shutdown 2023-03-20 17:41:40 EventID:109, Provider:Microsoft-Windows-Kernel-Power
N       44954 Reboot   2023-03-15 03:17:46 [Auto reboot by windows update] EventID:109, Provider:Microsoft-Windows-K...
N       43752 Shutdown 2023-02-15 12:03:59 EventID:109, Provider:Microsoft-Windows-Kernel-Power
N       43591 Reboot   2023-02-15 11:58:34 EventID:109, Provider:Microsoft-Windows-Kernel-Power
N       43385 Reboot   2023-02-15 11:38:21 EventID:109, Provider:Microsoft-Windows-Kernel-Power
E       43349 WUFail   2023-02-14 22:43:23 インストールの失敗: エラー 0x80073D02 で次の更新プログラムのインストール...
E       43260 WUFail   2023-02-13 05:43:19 インストールの失敗: エラー 0x80073D02 で次の更新プログラムのインストール...
E       43203 WUFail   2023-02-11 00:37:48 インストールの失敗: エラー 0x80073D02 で次の更新プログラムのインストール...
N       42800 Reboot   2023-02-05 15:41:30 EventID:109, Provider:Microsoft-Windows-Kernel-Power
N       42037 Reboot   2023-01-21 14:10:41 EventID:109, Provider:Microsoft-Windows-Kernel-Power


続行するには、Enter キーを押してください...:
PS C:\> .\confirm_poweroff_log.ps1 20 srl

Event    TimeStamp           Message
-----    ---------           -------
Shutdown 2023-03-20 17:41:40 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-03-15 03:17:46 [Auto reboot by windows update] EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-02-15 12:03:59 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-02-15 11:58:34 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-02-15 11:38:21 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-02-05 15:41:30 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-01-21 14:10:41 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-01-13 19:13:51 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-01-13 14:41:19 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-01-11 12:26:38 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-01-11 00:15:00 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-01-03 23:01:11 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-01-02 02:36:48 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-01-01 17:02:26 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2022-12-31 16:54:34 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2022-12-27 01:54:59 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Sleep    2022-12-23 23:16:12 EventID:42, Provider:Microsoft-Windows-Kernel-Power
Sleep    2022-12-23 23:15:04 EventID:42, Provider:Microsoft-Windows-Kernel-Power
Reboot   2022-12-23 22:50:30 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Sleep    2022-12-23 22:30:34 EventID:42, Provider:Microsoft-Windows-Kernel-Power


続行するには、Enter キーを押してください...:
PS C:\> .\confirm_poweroff_log.ps1 5 srhle

Event    TimeStamp           Message
-----    ---------           -------
Shutdown 2023-03-20 17:41:40 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-03-15 03:17:46 [Auto reboot by windows update] EventID:109, Provider:Microsoft-Windows-Kernel-Power
Shutdown 2023-02-15 12:03:59 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-02-15 11:58:34 EventID:109, Provider:Microsoft-Windows-Kernel-Power
Reboot   2023-02-15 11:38:21 EventID:109, Provider:Microsoft-Windows-Kernel-Power


続行するには、Enter キーを押してください...:

引数

第一引数

出力する直近ログの件数を指定可能です。...が、怠慢によりエラーログはこの件数に含まれません。

第二引数

出力するログの種類を以下の文字列で指定可能です。

  • s : 's'hutdown (ShutdownHSとして出力されるログはWindows高速スタートアップのログ)
  • r : 'r'eboot
  • h : 'h'ibernation
  • l : s'l'eep
  • e : 'e'rror

使い分け

  • エラーログのみいらない -> 第二引数にsrhlを指定します。
  • 再起動ログだけでいい -> 第二引数にrを指定します。
  • 全部出力したい -> 第二引数にsrhleを指定します。

仕様です

  • 引数無しだとどうなるの
    • 第一引数に3, 第二引数にsreを指定した場合と同じ結果となります。
  • 再起動のログが多いとレスポンスが遅すぎる
    • 再起動ログ毎にWindowsUpdateによるものかどうかのログ検索が実行されるためです。
  • WindowsUpdateによる再起動なのにそのように出力されない
    • 再起動ログの2分前までにUser32による特有の1074ログが存在するかどうかで判別しています。特有のログから再起動に2分以上かかっている場合は通常の再起動として出力されます。
  • Type NとかEとか出てくる
    • エラーログがある場合のみ見分けるために出力されます。N=Normal, E=Error
  • 終了時刻と開始時刻の両方が知りたい
    • 裏では開始ログも取得して判定していますが、簡素化のため終了時刻のみ出力するようにしています。
  • これも出力したい
    • You can modify the code under the MIT License.

参考文献

Discussion