はじめに
自分のPCの調子が悪く決まった時間帯に動作が停止しているようです。
そこで怪しい時間帯に何が起こっているかPowerShellを使ってタスクスケジューラのログとイベントログを取得することにしました。
環境
OS
システムの種類 64 ビット オペレーティング システム、x64 ベース プロセッサ
エディション Windows 10 Pro
バージョン 20H2
インストール日 2020/12/27
OS ビルド 19042.870
エクスペリエンス Windows Feature Experience Pack 120.2212.551.0
$PSVersionTable
Name Value
---- -----
PSVersion 7.1.3
PSEdition Core
GitCommitId 7.1.3
OS Microsoft Windows 10.0.19042
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
One Liner
取り合えず結果から、1行で書いた場合のコマンドを示します。(改行入れていますが)
管理者権限で実行してください。
そうしないと、タスクスケジューラの方は問題ないようですがイベントログの方は取得できないログが出てきます。
Task
タスクスケジューラのログを取得するコマンドです。
Get-ScheduledTask |
Get-ScheduledTaskInfo |
Where-Object { $_.LastRunTime -ge (Get-Date "2021-04-03T04:30:00") -and $_.LastRunTime -lt (Get-Date "2021-04-03T05:00:00") } |
Sort-Object -Property LastRuntime, LastTaskResult |
Format-Table TaskPath, TaskName, LastTaskResult, LastRunTime
変数で渡すなりして日付けを修正すれば任意の期間のログを取得できます。
日付けの形式は、ISO 8601の形式で指定していますが"2021/04/04 12:00:00"などでも認識してくれます。
またわざわざGet-Date
を呼び出して変換していますが文字列をそのまま渡しても認識してくれます。
EventLog
イベントログを取得するコマンドです。
Get-WinEvent -ListLog * |
Where-Object RecordCount -gt 0 |
ForEach-Object { Get-WinEvent -FilterHashtable @{LogName = $_.LogName; StartTime = (Get-Date "2021-04-03T04:30:00"); EndTime = (Get-Date "2021-04-03T09:30:00") } -ErrorAction SilentlyContinue } |
Sort-Object -Property TimeCreated |
Format-Table ProviderName, LogName, TimeCreated, Id, LevelDisplayName, Message
日付けに関してはタスクスケジューラと同様になります。
Windows10ですのでGet-WinEvent
を使用していますが、Vista以前の古いOSならばGet-EventLog
を使用した方が良いかもしれません。
詳細はdocs.microsoft.comを見ていただければと思います。
そこには以下のように記述されています。
PowerShell cmdlets that contain the EventLog noun work only on Windows classic event logs such as Application, System, or Security. To get logs that use the Windows Event Log technology in Windows Vista and later Windows versions, use Get-WinEvent.
Get-EventLog uses a Win32 API that is deprecated. The results may not be accurate. Use the Get-WinEvent cmdlet instead.
またGet-WinEvent
に-ErrorAction SilentlyContinue
を渡しているのは該当するログが見つからなかった場合のエラーメッセージを表示されないためです。
そのため、Get-WinEvent
で何らかのエラーが発生しても出力されないので注意してください。
Script File
後々の使い勝手を考えてスクリプトファイルも用意しておきます。
こちらも管理者権限で実行してください。
蛇足かもしれませんが追加の処理を入れています。
メッセージから改行コードを削除しています。
元のままのメッセージが必要ならば該当処理をコメントアウトなりしてください。
param ([parameter(mandatory)][String] $start, [String] $end, [Int] $width, [String] $csv)
function GetLogByTime {
param ([parameter(mandatory)][String] $start, [String] $end, [Int] $width, [String] $csv)
$start = Get-Date $start -Format "yyyy-MM-ddThh:mm:ss"
if ($end -eq "") {
$end = Get-Date -Format "yyyy-MM-ddThh:mm:ss"
}
else {
$end = Get-Date $end -Format "yyyy-MM-ddThh:mm:ss"
}
# Format-Table | Out-String -Width にWindowSize.Widthを渡しても思惑通りに出力されない
# if ($width -eq 0) {
# $width = (Get-Host).UI.RawUI.WindowSize.Width
# }
Write-Output "${start} to ${end}, ${width}, ${csv}"
$task = Get-ScheduledTask | Get-ScheduledTaskInfo |
Where-Object { $_.LastRunTime -ge (Get-Date $start) -and $_.LastRunTime -lt (Get-Date $end) } |
Sort-Object -Property LastRuntime, LastTaskResult
$eventLog = Get-WinEvent -ListLog * |
Where-Object RecordCount -gt 0 |
ForEach-Object { Get-WinEvent -FilterHashtable @{LogName = $_.LogName; StartTime = (Get-Date $start); EndTime = (Get-Date $end) } -ErrorAction SilentlyContinue } |
Sort-Object -Property TimeCreated
# Messageから改行を削除する
for ($i = 0; $i -lt $eventLog.Count; $i++) {
$eventLog[$i].Message = $eventLog[$i].Message -replace "`n", " "
$eventLog[$i].Message = $eventLog[$i].Message -replace "`r", ""
}
$formattedTask = Format-Table TaskPath, TaskName, LastTaskResult, LastRunTime -AutoSize -InputObject $task
$formattedEventLog = Format-Table ProviderName, LogName, TimeCreated, Id, LevelDisplayName, Message -AutoSize -InputObject $eventLog
if ($width -gt 2) {
$formattedTask = Out-String -Width $width -InputObject $formattedTask
$formattedEventLog = Out-String -Width $width -InputObject $formattedEventLog
}
Write-Output $formattedTask
Write-Output $formattedEventLog
if ($csv -ne "") {
$task | Select-Object TaskPath, TaskName, LastTaskResult, LastRunTime |
Export-Csv -Path "${csv}1.csv"
$eventLog | Select-Object ProviderName, LogName, TimeCreated, Id, LevelDisplayName, Message |
Export-Csv -Path "${csv}2.csv"
}
}
GetLogByTime $start $end $width $csv
これを例えば、GetLogByTime.ps1
と保存して、
./GetLogByTime.ps1 -start "2021-04-04T12:00:00" -end "2021-04-05T12:00:00"
./GetLogByTime.ps1 "2021-04-04T12:00:00" "2021-04-05T12:00:00"
のように使用してください。
メッセージを省略させたくなければ、
./GetLogByTime.ps1 "2021-04-04T12:00:00" "2021-04-05T12:00:00" -width 1000
のように-width
に大きな数値を指定してください。
また、出力をCSVで出力させたければ、
./GetLogByTime.ps1 "2021-04-04T12:00:00" "2021-04-05T12:00:00" -csv "logFileName"
のように指定してください。
最後に
これで調べたいログが簡単に取得できるようになりましたが、問題を解決できるかはまた別の話だったりします。
普段からログを見ているわけではないので何が怪しいのか分かりませんし、ハードウェアの問題の可能性も大きいので辛いです。
まあ、この記事がどなたかの一助になれば幸いです。
備考
Select-Object
Select-Object
にオブジェクトのコレクションを-InputObject
で渡した場合は1つのオブジェクトとして扱われるので注意すること。
基本的にSelect-Object
には、パイプでオブジェクトを渡す。
-InputObject
Specifies objects to send to the cmdlet through the pipeline.
This parameter enables you to pipe objects to Select-Object.
When you pass objects to the InputObject parameter, instead of using the pipeline, Select-Object treats the InputObject as a single object, even if the value is a collection.
It is recommended that you use the pipeline when passing collections to Select-Object.