PowerShell でイベントログを JSON 出力する

# はじめに
PowerShell でイベントログを JSON 形式で出力するスクリプトの紹介です。
イベントログを収集して他のプログラムで読み込むことを想定して JSON 形式にしています。
本記事の構成は以下です。

* 実行環境
* イベントログを取得
* イベントログのフィルタリング
* 取得したイベントログを JSON 形式で出力
* 補足情報

# 実行環境
* Windows 10
* .NET Framework 4.7
* Windows PowerShell 5.0 (推奨 : 3.0 以上)

# イベントログを取得
PowerShell でイベントログを取得し、コンソールに表示してみます。
イベントログの取得には、__[Get-WinEvent](https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.diagnostics/get-winevent)__ コマンドを使用します。[^1] 
なお、管理者権限で PowerShell を起動しないと、セキュリティのイベントログを取得出来ないので注意して下さい。

```ps1
PS C:\powershell> Get-WinEvent -ListLog *

LogMode   MaximumSizeInBytes RecordCount LogName
-------   ------------------ ----------- -------
Circular            20971520       23091 Application
Circular            20971520           0 HardwareEvents
Circular            20971520       30119 Security
Circular            20971520       15717 System

# (中略)

Circular             1052672             Network Isolation Operational
Circular             1052672         188 Setup
Circular             1052672             Windows Networking Vpn Plugin Platform/Operational
Circular             1052672             Windows Networking Vpn Plugin Platform/OperationalVerbose
```

Get-WinEvent コマンドでイベントログを取得できる事を確認できました。
しかし、出力結果の通り__全イベントログが出力__されているため、欲しい情報へのアクセス性がよくありません。
次に、イベントログから任意の情報のみをフィルタリングして取得する方法を紹介します。

# イベントログのフィルタリング

Get-WinEvent で取得するイベントログにフィルタをかけたい場合は、__-FilterHashtable__ オプションを使います。
-FilterHashtable オプションでは、__連想配列 @{}__ を用いてフィルタ条件を指定します。

例として、以下のスクリプトにて取得するイベントログの__種類__と__レベル__をフィルタリングしてみます。

```GetFilteredEventLog.ps1
# (1) ログの種類を指定
$logName = "Security", "System", "Application"

# (2) ログレベルを指定 (1:Critical, 2:Error, 3:Warning)
$eventLevel = 1, 2, 3  

# (3) (1) と (2) の条件でイベントログ取得のフィルタを作成
$filter = @{}
$filter.Add("LogName", $logName)
$filter.Add("Level", $eventLevel )
$filter.Add("StartTime", (Get-Date).AddHours(-24))
$filter.Add("EndTime", (Get-Date))

# (4) イベントログを取得
$eventLogs = Get-WinEvent -FilterHashtable $filter
```


それでは早速、GetFilteredEventLog.ps1 を実行してみます。
.ps1 ファイルを PowerShell で実行するためには __[ExecutionPolicy](https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.security/set-executionpolicy)__ を __RemoteSigned__ に設定しなければなりません。

しかし、ExecutionPolicy の設定変更の影響はシステムワイドなので、本記事では、PowerShell 実行時に -ExecutionPolicy オプションをつけることで一時的に RemoteSigned に設定します。

```ps1
PS C:\powershell> PowerShell -ExecutionPolicy RemoteSigned .\GetFilteredEventLog.ps1

   ProviderName: Microsoft-Windows-DistributedCOM

TimeCreated                     Id LevelDisplayName Message
-----------                     -- ---------------- -------
2017/07/24 19:53:43          10016 エラー           アプリケーション固有 のアクセス許可の設定では、CLSID ...
2017/07/24 12:00:44          10016 エラー           アプリケーション固有 のアクセス許可の設定では、CLSID ...

   ProviderName: Microsoft-Windows-Kernel-Processor-Power

TimeCreated                     Id LevelDisplayName Message
-----------                     -- ---------------- -------
2017/07/24 15:54:51             37 警告             グループ 0 のプロセッサ 3 のスピードはシステム ファームウェアに...
```

フィルタに設定した通り、エラーと警告に関するイベントログを取得できました。
しかし、このままではログの可読性に欠けるので、JSON 形式で出力してみます。

# 取得したイベントログを JSON 形式で出力
## JSON 形式で出力
JSON 形式での出力には、__[ConvertTo-Json](https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.utility/convertto-json)__ コマンド を使用します。
(※) ConvertTo-Json は __Windows PowerShell 3.0 以降__でしか動作しません。


先程のスクリプトに、以下のコメント #(5) 以降を追記します。

```GetFilteredEventLog.ps1
# ((1) ~ (3) 省略)

# (4) イベントログを取得
$eventLogs = Get-WinEvent -FilterHashtable $filter

# (これより下を追記)
# (5) イベントログを JSON 形式で出力
$eventLogs | ConvertTo-Json
```

同様に、追記した GetFilteredEventLog.ps1 を実行してみたいのですが、ここで 1 点問題があります。

それは、上記のスクリプトを実行すると Get-WinEvent コマンドで取得できる__イベントログの全項目が出力__されてしまうことです。
そこで、欲しい項目のみ出力できるようにイベントログをフィルタリングします。

## 任意の項目を抽出して JSON 形式で出力
任意の項目を抽出するには、__[Select-Object](https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.utility/select-object)__ コマンドを利用します。

以下のスクリプトの通り、__$queryItems__ のリストにイベントログから抽出したい項目だけを設定しておきます。(#(3'))[^2] 
そして、Get-WinEvent でイベントログを取得する際に、先に述べたフィルタ条件に加え __Select-Object の処理を連結__します。(#(4))

例として、イベントログから以下の項目を抽出してみます。

* ID（id）
* 種類（logName）
* レベル（levelDisplayName）
* イベントの発生時刻（datetime）
* プロバイダ名（providerName）
* メッセージ（message）

```GetFilteredEventLog.ps1
# ((1) ~ (3) 省略)

# (3') イベントログから抽出したい必要な項目だけを設定
$queryItems = New-Object System.Collections.ArrayList
[void]$queryItems.Add(@{Name="id";Expression={$_.id}})
[void]$queryItems.Add(@{Name="logName";Expression={$_.LogName}})
[void]$queryItems.Add(@{Name="levelDisplayName";Expression={$_.LevelDisplayName}})
[void]$queryItems.Add(@{Name="datetime";Expression={$_.TimeCreated.datetime}})
[void]$queryItems.Add(@{Name="providerName";Expression={$_.ProviderName}})
[void]$queryItems.Add(@{Name="message";Expression={$_.Message}})

# (4) イベントログを取得
$eventLogs = Get-WinEvent -FilterHashtable $filter | Select-Object $queryItems

# (5) イベントログを JSON 形式で出力
$eventLogs  | ConvertTo-Json
```

それでは最後に、完成した GetFilteredEventLog.ps1 を実行してみましょう。

```ps1
PS C:\powershell> PowerShell -ExecutionPolicy RemoteSigned .\GetFilteredEventLog.ps1
[
    {
        "id":  10016,
        "logName":  "System",
        "levelDisplayName":  "エラー",
        "datetime":  "2017年7月24日 19:53:43",
        "providerName":  "Microsoft-Windows-DistributedCOM",
        "message":  "アプリケーション固有 のアクセス許可の設定では、CLSID \r\n{D63B10C5-BB46-4990-A94F-E40B9D520160}\r\n および APPID \r\n{9CA88EE3-ACB7-47C8-AFC4-AB702511C276}\r\n の COM サーバー アプリケーションに対するローカルアクティブ化のアクセス許可を、アプリケーション コンテナー 利用不可 SID (利用不可) で実行中のアドレス LocalHost (LRPC 使用) のユーザー NT AUTHORITY\\SYSTEM SID (S-1-5-18) に与えることはできません。このセキュリティ アクセス許可は、コンポーネント サービス管理ツールを使って変更できます。"
    },
    {
        "id":  37,
        "logName":  "System",
        "levelDisplayName":  "警告",
        "datetime":  "2017年7月24日 15:54:51",
        "providerName":  "Microsoft-Windows-Kernel-Processor-Power",
        "message":  "グループ 0 のプロセッサ 3 のスピードはシステム ファームウェアによって制限されます。プロセッサは、最後のレポート後 602804 秒間このパフォーマンスが制限された状態にあります。"
    },
    {
        "id":  10016,
        "logName":  "System",
        "levelDisplayName":  "エラー",
        "datetime":  "2017年7月24日 12:00:44",
        "providerName":  "Microsoft-Windows-DistributedCOM",
        "message":  "アプリケーション固有 のアクセス許可の設定では、CLSID \r\n{D63B10C5-BB46-4990-A94F-E40B9D520160}\r\n および APPID \r\n{9CA88EE3-ACB7-47C8-AFC4-AB702511C276}\r\n の COM サーバー アプリケーションに対するローカルアクティブ化のアクセス許可を、アプリケーション コンテナー 利用不可 SID (利用不可) で実行中のアドレス LocalHost (LRPC 使用) のユーザー NT AUTHORITY\\SYSTEM SID (S-1-5-18) に与えることはできません。このセキュリティ アクセス許可は、コンポーネント サービス管理ツールを使って変更できます。"
    }
]
```

$queryItems で設定した項目だけが抽出されて JSON 形式でイベントログが出力出来ました。

# おわりに
今回は以下の 3 つを紹介しました。

* イベントログを取得
* イベントログのフィルタリング
* 取得したイベントログを JSON 形式で出力

今後は、JSON 形式で出力したイベントログを他のプログラムで読み込み、定期的にイベントログを取得してレポートを表示する仕組みを整えてみようと思います。


## 補足情報
フィルタで設定したログレベル値の対応関係を補足しておきます。

ログレベルと値の対応関係は、Windows のイベントビューアーの __現在のログをフィルター__ から確認することが出来ます。


例として、ログレベル __"情報"__ のみを選択してみます。
![フィルタ条件_Qiita.PNG](https://qiita-image-store.s3.amazonaws.com/0/147552/e8d1807e-2658-397c-c18d-c3de502a2f10.png)


XMLタブの Query 内を確認すると、 __"Level = 4 or Level = 0"__ と記載があります。
これより、情報レベルのイベントログを PowerShell から取得したい場合は、上記スクリプトの __$eventLevel__ に __0, 4__ を設定すれば良いです。



---

### 参考
* [Visual Studioなしでmingw\-w64だけでWindowsのイベントログに出力する機能を実現する方法 \- ククログ\(2015\-09\-10\)](http://www.clear-code.com/blog/2015/9/10.html)

* [Get\-WinEvent](https://msdn.microsoft.com/ja-jp/powershell/reference/5.1/microsoft.powershell.diagnostics/get-winevent)

* [LogLevel Enumeration \(Microsoft\.WindowsAzure\.Diagnostics\)](https://msdn.microsoft.com/library/en-us/Ee773154.aspx)

* [PowerShell Windows Event Log Monitoring \- Feature Requests \- LogicMonitor Communities](https://communities.logicmonitor.com/topic/657-powershell-windows-event-log-monitoring/)

* [PowerShell: ◆イベントログを取得\(抽出\)する\(Get\-WinEvent\)](http://mtgpowershell.blogspot.jp/2011/03/get-winevent.html)

* [\[PowerShell\] HEY YOU WHAT’S YOUR NAME？アクセスしてるのはお前さBABY\!アカウント名をセキュリティログから取るぜ！ – 管理者は見た！～AD と ILM 一家の秘密～](https://blogs.technet.microsoft.com/jpilmblg/2011/06/17/powershell-hey-you-whats-your-namebaby1243/)


[^1]: PowerShell でイベントログを取得するコマンドには、Get-EventLog もありますが、 Windows Vista / Windows Server 2008 以降のイベントログの新形式に対応していないとのことで使用を避けました。
[^2]: ここでは、POJO に格納しやすいように敢えて各項目名の頭文字を小文字に設定しています。