[WMIは万能ではない] 大容量イベントログ取得時によくある問題と回避策 ~ Script からも wevtutil.exe が使える ~

皆さんごきげんよう。ういこです。今日はイベントログのとり方でよくある問題とその対処案についてのお話です。

運用管理をしていてあるある!なニーズとして、イベントログを定期的に保存して、出来ればアーカイブして保存しておきたいなんてことがあると思います。しかもバッチ処理にして、タスク スケジューラなんかで人間様がいなくてもサーバ内の小人さん動かして定期的に保存させちゃおうとか。そんなニーズ、ありませんか。

では、全国津々浦々のイベントログ大好き管理者様はどのようにしてイベントログの取得をされていますか。問い合わせに来るものを見る限りでは、WMI の Win32_NTLogEvent 、Win32_NTEventLog 、Win32_NTEventLogFile とか、その辺を使っていらっしゃる方が多い印象です。それも、VBScript が圧倒的に多く、PowerShell による対処などは殆どお問い合わせとしてあがってきていません。ただ、私は開発サポート部門ですので、OS の基盤(プラットフォーム)サポート部門では違うのかもしれませんね。

ではなぜ WMI が多く使われているのか。非常にカンタンに使うことが出来、かつ VBScript からも .NET Framework ベースのプログラムからもアンマネージ (C++) からも呼び出しやすいという、手軽さが訴求しているのだと思います。しかし、以前から WMI についてはカンタンな反面、RPC 、DCOM などに依存している、内部処理が複雑で遅い、重いといった不便な点についてご紹介してきたように、やはりすべていいことずくめというわけにはいきません。
特に問題なのは、WMI は内部でデータを扱うために持っている領域のサイズが少なく、ある程度サイズ拡張は出来るものの、数ギガバイトといった大容量のデータを取り扱う際にエラーが発生することが多いという点です。イベントログは扱うデータ量が半端なく大きいことが少なくないため、この「内部データ領域足りません問題」に直面することが非常に多いシナリオです。

WMI によるイベントログ保存時に発生しやすい問題
監査ログなどを仕掛けている場合、セキュリティ イベントログの肥大化が問題になることが多くあると思います。こうしたログに対し、WMI でイベントを取得しようとして以下のエラーが発生することが多くあります。また、これらのエラーは必ず発生するのではなく、WBEM_E_QUOTA_VIOLATION が発生したと思えば、次に取得処理を実行すると今度は WBEM_E_OUT_OF_MEMORY が発生するなど、揺らぎが良く見られます。

0x8004106c WBEM_E_QUOTA_VIOLATION   
0x80041006 WBEM_E_OUT_OF_MEMORY
0x80041032 WBEM_E_CALL_CANCELLED

(※ まれに 0x80041001 WBEM_E_FAILED という形で異常終了することもあり)

調整方法としては、WQL 文で取得してくるイベントログの時間帯を指定することで、一回に扱うデータサイズを減らすということしかありません。ただ、時間を指定しても、その時間帯にたまたまアクセスが集中し、ファイル監査のログが大量発生した場合などは、データサイズがスパイクするため結果としてエラーが発生する可能性があることは避けられません。

なぜこうしたエラーが起きるのか
0x8004106C (WBEM_E_QUOTA_VIOLATION) は、WMI サービスにより消費されるメモリが割り当てられたメモリの制限に達したことを意味します。また、このエラーが発生する状況下では、同じクエリを同じデータストアに対して実施した場合であってもリソースを多く消費しシステムが不安定になることを防ぐために処理をキャンセルすることがあり、その場合に 0x80041032 (WBEM_E_CALL_CANCELLED) をスローする可能性もあります。いずれも、WMI クライアントの応答が遅い場合、クライアントに返すデータを保存するバッファが足りなくなる状況を指します。よって、あらかじめ大容量のデータを扱うことが予想される際は、WMI のメモリの仕組みを考慮したうえで WMI を採用するか慎重に検討する必要があるということになります。

WMI のクエリの結果の保存に使うメモリ量に関しては WMI 自身が使用量を確認していますが、WMI を実行するプロセス全体のメモリ領域も考慮する必要があります。

a. 実行結果など、WMI 自身が使用量を管理するメモリ領域
b. OS が管理する a を含むプロセス全体のメモリ領域

a. に関しては、レジストリである程度拡張することは出来ますが、b. の領域はレジストリ設定は影響しません。
0x8004106C (WBEM_E_QUOTA_VIOLATION) は、WMI サービスにより消費されるメモリが制限に達したことを意味しており、上記の例では、b. にあたります。
レジストリの設定は、上記 a. の結果セットの保存領域のサイズに影響するものとなります。

一般的にこうした状況を回避するために下記フラグを設定してメモリ領域内のインデックスを扱わせないことで若干のメモリ領域に余裕をあけることはできますが、フラグ設定も取り扱いデータ全体のインデックス領域を取得しないだけですので、結局扱うメモリ量を劇的に減らすことはできません。よって、元々考慮された値以上のデータ全体からクエリするような大容量データを扱う場合は残念ながらインデックス領域を減らしても余りあるデータ量になってしまう場合はフラグを設定しても効果はありません。

・WBEM_FLAG_FORWARD_ONLY
クエリ結果セットの列挙を前方方向のみに制限することで、メモリ消費を抑える事ができます。ただ、もともと扱う内部データ領域のサイズを変えることはできないため、大容量イベントログなどの巨大データを取り扱う際は効果がないことがほとんどです。

・WBEM_FLAG_RETURN_IMMEDIATELY
メソッド (ExecQuery) 実行後でも必要に応じて Background でオブジェクトの取得を試みる事ができるようになるため、大量のオブジェクトの取得も可能になる場合があります。ただし、あまりにも大量のデータの場合は効果がありません。

対処方法
問題の対処としては、以下の二つが挙げられます。

・wevtutil.exe を使う
・新しくなった EventLog API を使う

.NET Framework にて、内部的に EventLog API を使うクラスを扱えますので、PowerShell で使用することができます。ただ、VBScript でなんとかしたいという場合はこれでは対処ができません。そうした場合は、wevtutil.exe をスクリプト内でコールすることで対処することができます。しかも高速です。

' <<< サンプルここから Dim strDate Dim strDate2

strDate = Now

Set objShell = WScript.CreateObject("WScript.Shell")

if Len(strDate) <= 10 Then ' VBScript Now() 関数の制限。0 時ジャストの場合、時間が Drop されることに対する対策 strDate2 = strDate & " " & "_0時00分00秒" Else strDate2 = FormatDateTime (strDate,1) & "_" & Right("0" & Hour(strDate) , 2) & "時" & Right("0" & Minute(strDate) , 2) & "分" & Right("0" & Second(strDate) , 2)& "秒" End If

' *** Debug ' WScript.Echo strDate2 ' *** 引数の作成 teststr = "wevtutil epl Security" & " " & strDate2 & ".evtx"

Set objExec = objShell.Exec(teststr) Do While objExec.Status = 0 WScript.Sleep 1000 ' <<< 処理終了まで待機。実際のシステム上での動作に合わせて調整ください Loop ret = objExec.ExitCode if ret = 0 Then WScript.Echo "処理が成功しました" ElseIf ret = 1 Then WScript.Echo "処理が失敗しました" Else WScript.Echo "予期せぬエラーが発生しました : コード " & ret End If ' <<< サンプルここまで

wevtutil について
wevtutil は、テキスト、バイナリ、XML で保存ができます。この保存形式によって、以下のようにファイルサイズに影響が発生します。

・フォーマット形式 : テキストの場合
wevtutil qe Security /f:TEXT > mylog.txt
-> バイナリ evtx 3.0 GB に対し txt 3.6 GB まで増加

・XML 形式保存の場合
wevtutil qe Security /f:XML > hoge.xml
-> バイナリ evtx 3.0 GB に対し XML 4.8 GB まで増加

バイナリでそのまま保存いただいたほうがよいかもしれません。なお、以下のように指定することで、時間指定でのアーカイブを実行することも出来ます。(情報提供くださった M 様、ありがとうございました)

wevtutil epl Security MySecurityLog.evtx "/q:*[System[TimeCreated[@SystemTime>='2011-02-14T11:00:00Z' and @SystemTime<'2011-02-15T13:00:00Z']]]"

上記例では、2/14 日の 11:00 から、2/14 日の 13 時までの間を指定しています。

参考資料
Article ID: 957662 - Last Review: October 29, 2010 - Revision: 4.0
Recommended settings for event log sizes in Windows Server 2003, Windows XP, Windows Server 2008 and Windows Vista
https://support.microsoft.com/kb/957662/en-us
(※ Windows Server 2008 / Vista までの記述ですが、Windows 7 / Windows Server 2008 R2 も対象になります。)

[WMI基礎] Remote で動作する仕組み + ExecQuery と ExecQueryAsync の違い ~ “wbemFlagForwardOnly” は Count プロパティが取れない ~
https://blogs.technet.com/b/jpilmblg/archive/2010/03/29/wmi-remote-execquery-execqueryasync-wbemflagforwardonly-count.aspx

Memory and Handle Quotas in the WMI Provider Service
https://blogs.technet.com/b/askperf/archive/2008/09/16/memory-and-handle-quotas-in-the-wmi-provider-service.aspx

[Info] VBScript / ADO RecordSet : 時刻を文字列として扱う際 “0:00:00” の場合は文字列が Drop されてしまう
https://blogs.technet.com/b/jpilmblg/archive/2010/09/06/info-vbscript-ado-recordset-0-00-00-drop.aspx
(※ Now() 関数の制限。0 時ジャストの場合、時間が Drop されることについて)

それでは皆様ごきげんよう。

ういこう@Dance Evolution 楽しいですよ。