
TechNet コラムへようこそ。このコラムでは、よく寄せられるシステム管理スクリプトに関する質問に Scripting Guys がお答えします。システム管理スクリプトについて質問がある場合は、scripter@microsoft.com (英語のみ) までお送りください。すべての質問に回答することはできないかもしれませんが、可能な限り対応いたします。
詳細情報
| • | |
| • | |
| • |
![]()
Scripting Guy さん、よろしくお願いします。イベント ログをテキスト ファイルにバックアップする方法はありますか。
-- IMBDS

IMBDS さん、こんにちは。Scripting Guys は、他の人を手助けすることに人生をささげてきました (そうですね。最初は、他の人に手助けしてもらうことに人生をささげていました。しかし、あまりこのことに興味を持っている人がいないと思い、考えを変えたのです)。つまり、私たちが何かを学ぶというめったにない機会を得た場合は、その内容を他の人にも知らせるよう心がけています。
たとえば、つい昨日、米国の Do Not Call registry (勧誘や販売目的の電話が次々とかかってこないように自分の電話番号を登録する、公的な勧誘販売電話拒否リスト) には、驚いたことに有効期限があることを知りました。電話番号を登録しても、その登録が有効なのは 5 年間だけです (というのも、ほとんどの人は 5 年もしないうちに、電話勧誘販売業者から電話がかかってくるようなんです)。たとえば、このコラムを執筆している Scripting Guy が自宅の電話番号をオンライン検証ツールに入力したところ、あと 1 年で登録が期限切れになることがわかりました。
すばらしい情報ですね。
注 : 正直なところ、Do Not Call registry がそれほど効果的なのかどうかもわかりません。たとえば、このコラムを執筆している Scripting Guy は、電話番号を登録しているにもかかわらず、金銭を要求する電話が 1 日に数回かかってきます。 Scripting Guy の息子からの電話です。 |
とにかく、私たちは Do Not Call registry に有効期限があることを、皆さんにお知らせしたいと思いました。また、イベント ログをテキスト ファイルにバックアップする簡単でわかりやすい方法がないこともお知らせしたいと思いました。実は、WMI の BackupEventLog メソッドには、データをイベント ログのバイナリ形式でデータを保存する機能しかありません。もちろん、イベント ログのデータをテキスト ファイルに保存できないわけではありません。ただ、次のようなスクリプトを使用してこの作業を行う必要があります。
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colEvents = objWMIService.ExecQuery _
("Select * from Win32_NTLogEvent Where LogFile='Application'")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("C:\Scripts\Events.txt")
For Each objEvent in colEvents
strTimeWritten = objEvent.TimeWritten
dtmTimeWritten = CDate(Mid(strTimeWritten, 5, 2) & "/" & _
Mid(strTimeWritten, 7, 2) & "/" & Left(strTimeWritten, 4) _
& " " & Mid (strTimeWritten, 9, 2) & ":" & _
Mid(strTimeWritten, 11, 2) & ":" & Mid(strTimeWritten, 13, 2))
dtmDate = FormatDateTime(dtmTimeWritten, vbShortDate)
dtmTime = FormatDateTime(dtmTimeWritten, vbLongTime)
strEvent = dtmDate & vbTab
strEvent = strEvent & dtmTime & vbTab
strEvent = strEvent & objEvent.SourceName & vbTab
strEvent = strEvent & objEvent.Type & vbTab
strEvent = strEvent & objEvent.Category & vbTab
strEvent = strEvent & objEvent.EventCode & vbTab
strEvent = strEvent & objEvent.User & vbTab
strEvent = strEvent & objEvent.ComputerName & vbTab
strDescription = objEvent.Message
If IsNull(strDescription) Then
strDescription = "The event description cannot be found."
End If
strDescription = Replace(strDescription, vbCrLf, " ")
strEvent = strEvent & strDescription
objFile.WriteLine strEvent
Next
objFile.Close
確かに、このスクリプトは少し複雑に見えます。これにはもっともな理由があります。このスクリプトで実行する必要がある処理の一部が少し複雑だからです。でも心配しないでください。このスクリプトを最初から最後まで順を追って、しくみをご説明しましょう。
この "不明な通話元" からの電話に出たらすぐに説明しますから。
スクリプトの冒頭部分はとても簡単で、ローカル コンピュータの WMI サービスに接続しています。私たちはマイクロソフトの Scripting Guys ですから、皆さんが考えていることはわかっていますよ。その質問にお答えしましょう。そのとおり、このスクリプトはリモート コンピュータに対しても実行できます。必要な作業は、次のようにリモート コンピュータの名前を strComputer 変数に代入するだけです。
strComputer = "atl-dc-01"
たいしたことではありませんね。Scripting Guy なら、こんなことくらい知っていて当然です。
WMI サービスに接続したら、次のコード行を実行します。このコード行は、アプリケーション イベント ログに含まれるすべてのイベントのコレクションを返します。
Set colEvents = objWMIService.ExecQuery _
("Select * from Win32_NTLogEvent Where LogFile='Application'")
注 : 言うまでもなく、バックアップできるのはアプリケーション イベント ログだけではありません。必要に応じて、あらゆる (すべての) イベント ログをバックアップできます。(特にセキュリティ イベント ログの操作の) 詳細については、「Microsoft Windows 2000 Scripting Guide」(英語) を参照してください。 |
次は、この 2 行のコードです。
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("C:\Scripts\Events.txt")
既にご説明したように、イベント ログをテキスト ファイルとしてバックアップする組み込みの方法はありません。つまり、BackupAsTextFile のような WMI メソッドはありません。このため、プログラムでイベント ログの内容をテキスト ファイルとして保存する方法は 1 つだけです。つまり、自分で情報をテキスト ファイルに書き込む必要があります。では、どのような方法で実行するのでしょうか。まず、上記の 2 行のコードを使用して、Scripting.FileSystemObject オブジェクトのインスタンスを作成し、C:\Scripts\Events.txt という名前の新しいテキスト ファイルを作成します。
ここからが本題です。イベント ログをテキスト ファイルとして保存することは実際に可能だと、お知らせした方がよいでしょう。ただし、イベント ビューアを開いてすべてを手作業で実行することが条件です。この手順を踏めば、イベント ビューアでイベント ログがタブ区切りファイルとして保存されます。私たちはこれと同じ形式を使用することにしました。つまり、アプリケーション イベント ログのすべてのイベントをタブ区切り形式で保存します (つまり、イベント コードやイベントの説明など、すべてのイベントの各フィールドをタブで区切ります)。
そのためには、まず、コレクション内のすべてのイベントをループ処理する For Each ループを設定します。このループでは、TimeWritten プロパティの値を取得して、strTimeWritten という名前の変数に格納します。
strTimeWritten = objEvent.TimeWritten
ここからが、ちょっと複雑になる部分です。おそらくご存知でしょうが、WMI では日付と日時が UTC (Universal Time Coordinate) 形式で格納されます。つまり、TimeWritten プロパティの値は次のような形式になります。
20070905121045.578000-420
すごいですよね。UTC 日時値の解読方法については説明しませんが、「Scripting Guide」(英語) に非常にわかりやすい説明が載っています。ここでは、20070905121045.578000-420 のような値は次のような情報ほどわかりやすくないと言えば十分でしょう。
9/5/2007 12:10 PM
しかし、一体どうすれば UTC 値を人間が読んで理解できる形式に変換できるのでしょうか。もちろん、次のようにします。
dtmTimeWritten = CDate(Mid(strTimeWritten, 5, 2) & "/" & _
Mid(strTimeWritten, 7, 2) & "/" & Left(strTimeWritten, 4) _
& " " & Mid (strTimeWritten, 9, 2) & ":" & _
Mid(strTimeWritten, 11, 2) & ":" & Mid(strTimeWritten, 13, 2))
この処理についても、Scripting Guide に完全な説明が載っています。簡単にまとめると、ここでは、UTC 値の各部分を取得して独自の日時値を構成しています。たとえば、UTC 値の最初の 4 桁は現在の年を表しています。したがって、20070905121045.578000-420 は、2007 年を扱っていることを表します。つまり、次のような UTC 値の最初の 4 桁を取得するコードを使用することでも、2007 年を扱っていることがわかります。
Left(strTimeWritten, 4)
上記のスクリプトのロジックを最後までたどれば、最終的に dtmTimeWritten 変数に次のような値が代入されることがおわかりになるでしょう。
9/5/2007 12:10:45 PM
この形式なら扱うことができますね。
実は、イベント ビューアで使用されるタブ区切りファイルの形式では、日付 (05.09.07) と時刻 (12:10:45 PM) が区切られています。問題ありません。このため、次は FormatDateTime 関数、および VBScript の定数 vbShortDate と vbLongTime を使用して、日付を dtmDate という名前の変数に、時刻を dtmTime という名前の変数に代入します。
dtmDate = FormatDateTime(dtmTimeWritten, vbShortDate) dtmTime = FormatDateTime(dtmTimeWritten, vbLongTime)
日付と時刻を処理したら、テキスト ファイルの最初の行の構成に取り掛かります (これはイベント ログに記録されている最初のイベントの情報に対応しています)。これを行うのが次のコード ブロックです。
strEvent = dtmDate & vbTab strEvent = strEvent & dtmTime & vbTab strEvent = strEvent & objEvent.SourceName & vbTab strEvent = strEvent & objEvent.Type & vbTab strEvent = strEvent & objEvent.Category & vbTab strEvent = strEvent & objEvent.EventCode & vbTab strEvent = strEvent & objEvent.User & vbTab strEvent = strEvent & objEvent.ComputerName & vbTab
ご覧のとおり、それほど変わったことはしていません。1 行目では、(dtmDate 変数で表される) 日付と (VBScript の vbTab 定数で表される) タブ文字を strEvent という名前の変数に代入します (タブ文字を代入する理由は、各フィールドをタブで区切るためです)。2 行目では、strEvent 変数の現在の値に dtmTime 変数の値とタブ文字をもう 1 つ追加したものを、strEvent 変数に代入します。このような処理を数回繰り返して、SourceName、Type などの WMI プロパティの値を strEvent 変数の値に追加します。
次は、このコード ブロックです。
strDescription = objEvent.Message
If IsNull(strDescription) Then
strDescription = "The event description cannot be found."
End If
strDescription = Replace(strDescription, vbCrLf, " ")
strEvent = strEvent & strDescription
イベント ログのイベントの最も重要なプロパティは、Message プロパティでしょう。これは間違いなくご存知だと思いますが、Message プロパティはイベントの (いくらか) 詳細な説明を提供します。ただあいにく、このスクリプトで Message プロパティを使用すると大問題が発生する可能性があります。では、どうしたらよいのでしょうか。簡単なことです。対策を講じて、この問題が発生する前に除去します。
この問題とはどのようなものかをご説明しましょう。まず、Message プロパティには、1 つ以上の復帰改行文字が含まれていることがよくあります。つまり、Message プロパティには、次のような値が含まれていることがあります。
グループ ポリシー オブジェクト セキュリティ ポリシーは正しく適用されました。 詳細な情報は、http://go.microsoft.com/fwlink/events.asp の [ヘルプとサポート センター] を参照してください。
これのどこに問題があるのでしょうか。タブ区切りファイルでは、1 つのイベントに関するすべての情報が 1 行で記述される必要がありますが、Message プロパティの値に復帰改行文字が含まれていると、この条件が満たされません。したがって、Replace 関数を使用して、すべての復帰改行文字 (vbCrLf) を空白スペースに置き換えます。
strDescription = Replace(strDescription, vbCrLf, " ")
これはすばらしいことですが、この結果、別の問題が発生します。イベントによっては、Message プロパティに値が含まれていないことがあります。イベント ビューアでは、Message プロパティに値が含まれていなくても問題ありません。このような場合、イベント ビューアには次のようなメッセージが表示されます。
イベント ID (0) (ソース iPod Service 内) に関する説明が見つかりませんでした。リモート コンピュータから メッセージを表示するために必要なレジストリ情報またはメッセージ DLL ファイルがローカル コンピュータにない可能性があります。 この説明を取得するために /AUXSOURCE= フラグを使用することができる可能性があります。詳細については、 ヘルプとサポートを参照してください。次の情報はイベントの一部です: サービスを開始しました。
しかし、この機能はイベント ビューアでのみ動作します。上記の情報は Message プロパティには渡されません。その代わり、Message プロパティの値は Null として返されます。
これは問題なのか、ですって。実は問題です。Null 値に対して Replace 関数を実行すると、大惨事が発生します (もちろん、クラッシュするスクリプトが大惨事だと考えるならばの話ですが)。このため、Replace 関数を呼び出す前に、次のコード ブロックを実行します。
If IsNull(strDescription) Then
strDescription = "The event description cannot be found."
End If
ここでは、IsNull 関数を使用して (strDescription 変数に格納されている) Message プロパティの値が Null かどうかを確認しています。Null でない場合は、問題ありません。Null の場合は、strDescription 変数に "The event description cannot be found"(イベントの説明が見つかりませんでした) という汎用的なメッセージを代入します。
潜在的な Null 値を処理したら、Replace 関数を呼び出します。そして、strDescription 変数の値をイベント文字列の末尾に追加します。
strEvent = strEvent & strDescription
後は、WriteLine メソッドを呼び出してこのイベントをテキスト ファイルに書き込むだけです。
objFile.WriteLine strEvent
ここからは、For Each ループの先頭にすばやく戻り、コレクション内の次のイベントについても同じ処理を繰り返します。すべてのイベントをテキスト ファイルに追加したら、Close メソッドを実行してファイルを閉じて、作業完了です。
これでご質問の回答になっているとよいのですが、IMBDS さん。ご不満でしたら、わざわざ電子メールをお送りいただくことはありません。お電話で結構です。というのも、他の皆さんがそうしているのですからね (やれやれ・・・)。おっと、今日はこの辺で失礼します。電話が鳴っていますので。
注 : 実に商魂たくましいことに、企業の中には実際に電話をかけてきて、お宅の電話番号を Do Not Call registry に (わずかな手数料で) 登録しましょうと申し出る企業があります。米国限定ですよ。 |