はじめに
Windows Server が十数台あって、「エラーだけを一覧で見たい」という要望はよくあります。イベントビューアーを一台ずつ開くのは現実的ではなく、かといって全イベントをどこかに転送すると、量が多すぎて誰も読まなくなります。
まず 1 台で Get-WinEvent を使って Critical と Error だけを 300 文字の 1 行要約にする ところから始め、途中の落とし穴を潰し、最後に複数台になったとき何が変わるかを書きます。
対象は System と Application です。Security ログは外します。ログオン失敗 (4625) などはアカウント名の宝庫で、集約して外に出す対象としては別物だからです。
本文中の出力例は、記事用に 実在の出所・イベント ID で組み立てた例示 です。計算機名・日時・パスは架空で、特定のサーバから採取したものではありません。英語版 Windows の既定出力の形に合わせています(日本語版では
LevelDisplayNameとMessageが日本語になります — §3-3)。
1. まず 1 台で: Get-WinEvent
Get-EventLog ではなく Get-WinEvent を使います。前者は従来形式のログしか読めず、PowerShell 7 には存在しません。
$since = (Get-Date).AddHours(-24)
Get-WinEvent -FilterHashtable @{
LogName = 'System','Application'
Level = 1,2 # 1 = Critical, 2 = Error
StartTime = $since
} -MaxEvents 6 -ErrorAction SilentlyContinue
ポイントは三つです。
-
Levelは数字で指定します(1 Critical、2 Error、3 Warning、4 Information、5 Verbose、0 LogAlways)。LevelDisplayNameで絞ると全件取ってから捨てることになります。FilterHashtableのLogNameとLevelは配列を受けるので、二つのログと二つのレベルを一回のクエリにまとめられます。 - 該当イベントが 0 件だと
Get-WinEventは「No events were found that match the specified selection criteria.」というエラーを出します。ログが静かなだけなので、-ErrorAction SilentlyContinueで黙らせるか、-ErrorVariableに受けて後で見ます。 -
-MaxEventsは新しいほうから数えます。既定の並びが新→旧だからです。
例示用。実環境の出力ではありません。
ProviderName: Microsoft-Windows-Kernel-Power
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
10/4/2026 11:41:17 PM 41 Critical The system has rebooted without cleanly shutting down first. This error could be caused if the sys...
ProviderName: Service Control Manager
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
10/4/2026 11:40:02 PM 7034 Error The Print Spooler service terminated unexpectedly. It has done this 1 time(s).
10/4/2026 11:39:58 PM 7031 Error The Windows Update service terminated unexpectedly. It has done this 2 time(s). The following c...
ProviderName: disk
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
10/4/2026 9:12:44 PM 11 Error The driver detected a controller error on \Device\Harddisk1\DR1.
ProviderName: Schannel
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
10/4/2026 8:05:31 PM 36871 Error A fatal error occurred while creating a TLS client credential. The internal error state is 10013.
ProviderName: Microsoft-Windows-DistributedCOM
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
10/4/2026 7:58:10 PM 10016 Error The application-specific permission settings do not grant Local Activation permission for the C...
既定の表示は ProviderName ごとにまとまり、Message は画面幅で切れます。この形のまま読むのはつらいので、必要な列だけにします。
Get-WinEvent -FilterHashtable @{ LogName='System','Application'; Level=1,2; StartTime=$since } -ErrorAction SilentlyContinue |
Select-Object TimeCreated, LogName, ProviderName, Id, LevelDisplayName |
Format-Table -AutoSize
例示用。実環境の出力ではありません。
TimeCreated LogName ProviderName Id LevelDisplayName
----------- ------- ------------ -- ----------------
10/4/2026 11:41:17 PM System Microsoft-Windows-Kernel-Power 41 Critical
10/4/2026 11:40:02 PM System Service Control Manager 7034 Error
10/4/2026 11:39:58 PM System Service Control Manager 7031 Error
10/4/2026 10:17:05 PM Application Application Error 1000 Error
10/4/2026 10:17:04 PM Application .NET Runtime 1026 Error
10/4/2026 9:12:44 PM System disk 11 Error
10/4/2026 8:05:31 PM System Schannel 36871 Error
10/4/2026 7:58:10 PM System Microsoft-Windows-DistributedCOM 10016 Error
ついでに書いておくと、ディスク関連でよく目にする disk 51(ページング操作中のエラー)や 153(IO 再試行)は Warning です。上のフィルタには出ません。Warning まで拾うなら Level = 1,2,3 ですが、量が一桁増えます。
2. 1 行要約を作る
欲しいのは「どのログの、どの出所の、何番が、何と言っているか」が一行で分かる形です。
System · Service Control Manager 7034 — The Print Spooler service terminated unexpectedly. It has done this 1 time(s).
これを作る関数です。
function ConvertTo-Summary {
param(
[System.Diagnostics.Eventing.Reader.EventLogRecord]$Event,
[int]$Max = 300
)
$head = '{0} · {1} {2}' -f $Event.LogName, $Event.ProviderName, $Event.Id
$line = if ($Event.Message) {
($Event.Message -split "`r?`n" | Where-Object { $_.Trim() } | Select-Object -First 1)
} else {
# メッセージ DLL が無い出所 — EventData の値だけを並べる
'Event data: ' + (($Event.Properties | ForEach-Object Value) -join '; ')
}
$line = ($line -replace '[\x00-\x1f\x7f]', ' ') -replace '\s+', ' '
$line = $line.Trim()
$room = $Max - $head.Length - 3 # " — " の分
if ($line.Length -gt $room) { $line = $line.Substring(0, $room - 1) + '…' }
"$head — $line"
}
Get-WinEvent -FilterHashtable @{ LogName='System','Application'; Level=1,2; StartTime=$since } -ErrorAction SilentlyContinue |
ForEach-Object { ConvertTo-Summary $_ }
例示用。実環境の出力ではありません。
System · Microsoft-Windows-Kernel-Power 41 — The system has rebooted without cleanly shutting down first. This error could be caused if the system stopped responding, crashed, or lost power unexpectedly.
System · Service Control Manager 7034 — The Print Spooler service terminated unexpectedly. It has done this 1 time(s).
System · Service Control Manager 7031 — The Windows Update service terminated unexpectedly. It has done this 2 time(s). The following corrective action will be taken in 60000 milliseconds: Restart the service.
Application · Application Error 1000 — Faulting application name: w3wp.exe, version: 10.0.17763.1, time stamp: 0x5b7c3a5e
Application · .NET Runtime 1026 — Application: w3wp.exe
System · disk 11 — The driver detected a controller error on \Device\Harddisk1\DR1.
System · Schannel 36871 — A fatal error occurred while creating a TLS client credential. The internal error state is 10013.
Application · AcmeBackupSvc 4097 — Event data: C:\Program Files\AcmeBackup\agent.log; 0x80070005
Message の 最初の空でない行 だけを取るのが要点です。本文のあとには定型文や長い説明が続きますが、一覧で読む分には一行目で足ります。.NET Runtime 1026 のように一行目が「Application: w3wp.exe」だけの出所もありますが、それでも「どのプロセスが落ちたか」は分かります。
最後の行は、メッセージ DLL が登録されていない出所(アンインストール済みのバックアップエージェントを想定)の例です。次の §3-1 で説明します。
3. 落とし穴
3-1. Message が $null になる
アンインストール済みのアプリなど、メッセージ DLL が無い出所では Message が $null になります(イベントビューアーの「イベント ID xxx (ソース yyy) の説明が見つかりません」と同じ現象)。上の関数では Properties(EventData の値の配列)を並べる代替に落としています。落とさないと要約が空になり、何が起きたかが消えます。
3-2. XML を丸ごと送らない
$Event.ToXml() の中身には <Security UserID="S-1-5-21-…"/>、<Computer>SRV-APP01.corp.example</Computer>、<Execution ProcessID="…" ThreadID="…"/>、そして <EventData> に実行アカウント名やファイルパスが入っています。「XML ごと転送して向こうで整形」は、転送先に SID とアカウント名を蓄積する設計です。必要な列だけ取り出してから 送る。上の関数が LogName・ProviderName・Id・一行目しか使っていないのはそのためです。
ただし一行目そのものにアカウント情報が入る出所もあります。上の DistributedCOM 10016 の本文は … to the user NT AUTHORITY\LOCAL SERVICE SID (S-1-5-19) from address LocalHost … と続き、既知 SID とはいえ SID が文字列として含まれます。ドメインユーザー名が入る出所もあります。送り先が自分の管理下にない場合は、出所ごとに「一行目を送るか、ProviderName と Id だけにするか」を決める表が要ります。
3-3. メッセージの整形は OS の表示言語に依存する
Message は実行時にメッセージ DLL から組み立てられるので、日本語 Windows では日本語、英語 Windows では英語です。LevelDisplayName も同じで、日本語版では 重大・エラー になります。混在環境では要約の言語が混ざります。ProviderName と Id と Level(数値)は言語に依らないので、集計キーにはそちらを使います。
3-4. 300 文字の数え方
.Length と Substring は UTF-16 のコード単位で数えます。日本語は 1 文字 = 1 単位ですが、絵文字や一部の漢字(𠮷 など)は 2 単位で、境界で切ると壊れた文字が残ります。厳密には [System.Globalization.StringInfo] で書記素単位に数えます。
3-5. 書き出しの文字コード
Windows PowerShell 5.1 の Out-File と > は既定で UTF-16LE、Set-Content は ANSI(日本語環境では CP932)です。要約を Linux 側で読むつもりなら、明示します。
... | ForEach-Object { ConvertTo-Summary $_ } | Out-File -Encoding utf8 .\errors.txt
5.1 の -Encoding utf8 は BOM 付きです。BOM が邪魔なら [System.IO.File]::WriteAllLines に UTF8Encoding(false) を渡します。パイプで他プロセスに渡すなら [Console]::OutputEncoding も同じ話です。忘れると日本語の要約だけが ???? で届きます。
3-6. 同じイベントの連打
ディスクのエラーやサービスの再起動失敗は、同じ ID が数秒おきに数百件並びます。一行ずつ出すと一覧が埋まるので、(ログ・出所・ID)で畳んで回数を添えます。
Get-WinEvent -FilterHashtable @{ LogName='System','Application'; Level=1,2; StartTime=$since } -ErrorAction SilentlyContinue |
Group-Object { '{0}|{1}|{2}' -f $_.LogName, $_.ProviderName, $_.Id } |
ForEach-Object {
$s = ConvertTo-Summary $_.Group[0]
if ($_.Count -gt 1) { "$s (repeated $($_.Count) times)" } else { $s }
}
例示用。実環境の出力ではありません。
System · disk 11 — The driver detected a controller error on \Device\Harddisk1\DR1. (repeated 312 times)
System · Service Control Manager 7031 — The Windows Update service terminated unexpectedly. It has done this 2 time(s). The following corrective action will be taken in 60000 milliseconds: Restart the service. (repeated 2 times)
System · Microsoft-Windows-Kernel-Power 41 — The system has rebooted without cleanly shutting down first. This error could be caused if the system stopped responding, crashed, or lost power unexpectedly.
回数の表示は 切り詰めの対象から外す こと。300 文字に収めるときに (repeated 312 times) を削ると、連打が起きていること自体が見えなくなります。
3-7. 続きから読む
定期実行にすると「前回どこまで読んだか」が必要です。StartTime だと同じ秒のイベントを二重に取るか落とすかのどちらかになるので、ログごとの RecordId を保存して XPath で続きを取ります。
$lastId = Get-Content .\system.last -ErrorAction SilentlyContinue
if (-not $lastId) { $lastId = 0 } # 初回
$xpath = "*[System[(Level=1 or Level=2) and EventRecordID > $lastId]]"
$events = Get-WinEvent -LogName System -FilterXPath $xpath -Oldest -ErrorAction SilentlyContinue
if ($events) { ($events | Measure-Object RecordId -Maximum).Maximum | Set-Content .\system.last }
-Oldest を付けると古い順に返るので、途中で止まっても処理済みの最大 RecordId を保存すれば続きが取れます。RecordId はログごとに別の連番なので、System と Application で別々に持ちます。-FilterXPath は -LogName と組み合わせて使い、-FilterHashtable とは同時に指定できません。
4. 複数台になると何が変わるか
ここまでは 1 台の話です。十数台になると、Get-WinEvent -ComputerName や Invoke-Command で 集める側から取りに行く 形がまず思い浮かびますが、
- 各サーバに 受信側のポート を開ける(
-ComputerNameは RPC で、WinRM ではありません。135 + 動的ポート。Invoke-Commandなら WinRM 5985/5986) - 集める側が 全サーバに通る資格情報 を持つ(Event Log Readers 以上)
- 集める側が落ちている間、落ちていたことに誰も気づかない
-
-ComputerNameは一度に 1 台しか受けないので、ループで順に回る時間がそのまま遅延になる
という条件がつきます。全台に入れる資格情報を一か所に置く構造自体が、本来は避けたいものです。
逆向き — 各サーバに小さな常駐プロセスを置き、外向きの 1 本で要約だけを送る — にすると、開けるポートは受け側の 1 つ、資格情報は送信専用のトークン、受け側が落ちている間はサーバ側で溜めて後で送る、になります。PowerShell の定期タスクでも組めますが、本格的に作るなら Get-WinEvent のポーリングではなく EvtSubscribe + ブックマークのほうが、取りこぼしと二重送信を避けられます。
5. 最後に — これをエージェントに組み込んだ
この記事の内容は、自分たちが作っている Windows / Linux サーバ監視の MeshServerWatch のエージェントで、そのまま実装になっています。
-
wevtapi.dllのEvtSubscribe+ ブックマークで System / Application の Level 1・2 だけを受け取り、EvtFormatMessageで組み立てたメッセージの一行目を使う - 要約は同じ
ログ · 出所 ID — 一行目の形で上限 300 文字、連打は 10 分単位で(repeated N times in 10 min)に畳む - 送るのは要約・重大度・ログ名・出所・ID・時刻だけ。メッセージ全文、イベント XML、SID、ユーザー名、プロセス一覧、コマンドラインは送らない。Security ログは対象外
- 送り先は自分で立てる Manager(HTTPS、証明書は SHA-256 フィンガープリントで固定)。クラウドには何も行かない
- Linux 側は journald の
err以上を同じ形にする
サーバ単位の有料製品(30 日の試用あり)なので、合わないかたは §1〜§3 のスクリプトを定期タスクにするだけでも一覧は手に入ります。
開示: この記事は MeshServerWatch の開発元である MeshWatch が書いています。