直近の起動・終了日時を取得するFunction(2)
直近のイベント日時を取得するFunctionの改良
前回
の続きです。
イベントコードを引数として受け取る
前回は、シャットダウン日時を取得するFunction、起動日時を取得するFunctionの2種類を作ったわけだが、記事中でも言及していたとおり、2つの違いはイベントコードだけなので、イベントコードを引数にして一本化すれば良いのであった。
ついでに、引数のイベントコードは限られたものなので、このときに教えてもらった
引数に列挙体型を指定する裏技
を使うことにした。
リスト1 標準モジュールの宣言セクション
Public Enum WindowsEventCode winStartUp = 6005 winShutDown = 6006 End Enum
こうしておくことで、Functionの引数に自作の列挙体型を指定することができる。これは便利だ。
リスト2 標準モジュール
Public Function getLastEventDateTime( _ ByVal eventCode As WindowsEventCode) As Date '……(1)' On Error GoTo errorHandler Dim strComputer As String Dim objWMIService As Object Dim colLoggedEvents As Object Dim objEvent As Object Dim retTimeWritten As Date strComputer = "." Set objWMIService = GetObject("winmgmts:" & _ "{impersonationLevel=impersonate}!\\" & _ strComputer & _ "\root\cimv2") Set colLoggedEvents = _ objWMIService.ExecQuery("SELECT * FROM Win32_NTLogEvent " & _ "WHERE Logfile = 'System' " & _ "AND EventCode = '" & eventCode & "'") '……(2)' If Not colLoggedEvents.itemIndex(0) Is Nothing Then '……(3)' Set objEvent = colLoggedEvents.itemIndex(0) '……(4)' retTimeWritten = convUTCtoJST(parseTimeWritten(objEvent.timeWritten)) getLastEventDateTime = retTimeWritten End If errorHandler: Set objWMIService = Nothing Set colLoggedEvents = Nothing Set objEvent = Nothing End Function Private Function parseTimeWritten(ByVal receiveData As Variant) As Date parseTimeWritten = _ CDate(Mid(receiveData, 1, 4) & "/" & _ Mid(receiveData, 5, 2) & "/" & _ Mid(receiveData, 7, 2) & _ " " & _ Mid(receiveData, 9, 2) & ":" & _ Mid(receiveData, 11, 2) & ":" & _ Mid(receiveData, 13, 2)) End Function Private Function convUTCtoJST(ByVal timeData As Date) As Date convUTCtoJST = DateAdd("h", 9, timeData) End Function
まず(1)の
Public Function getLastEventDateTime( _ ByVal eventCode As WindowsEventCode) As Date
WindowsEventCode型の引数を指定している。もちろん、「WindowsEventCode型」というのは自作の列挙体型。
こいつを指定しておくことで、
こんなふうにヒントが出るし、
インテリセンスで入力候補も表示される。メッチャ便利。
ま、イベントコードに該当する数字を渡したら、そのイベントの直近の日時を返してしまうんですがw(イベントコード13って何だったっけ?)
んで、(2)の
Set colLoggedEvents = _ objWMIService.ExecQuery("SELECT * FROM Win32_NTLogEvent " & _ "WHERE Logfile = 'System' " & _ "AND EventCode = '" & eventCode & "'")
ExecQueryメソッドの引数のクエリの「AND」以下の部分、
AND EventCode = '6005'
と、イベントコードを直接指定していたのを
AND EventCode = '" & eventCode & "'
と改めただけ。シングルクオートの中身を変数にしただけですな。
こうすることで、1つのプロシージャにまとめることができた。
イベントログコレクションのItemIndexプロパティを使う
さて、今度は、ムダにFor Each ~ Nextを使っていた箇所の改善。
前回、
たとえば、
Set objEvent = colLoggedEvents.Item(1)とかで行けると思ったが、こうするとエラーになった。
などと書いていたのだが、例によって id:imihito さんからの非常にありがたいご指摘が。まさに神の声。
Set objEvent = colLoggedEvents.Item(1)
は
Set objEvent = colLoggedEvents.ItemIndex(0)
とすると上手くいくと思います。
とのこと。
おおっ! 知らなかった! ……って、VBAに元からあるコレクションでも何でもないんだから、メンバの表記が違うのが当たり前ですよね。VBAの記法って、ちょっと独特ですから。
また、
ItemIndex の出所は「Microsoft WMI Scripting V 1.2 Library」を参照して、オブジェクトブラウザから「WbemScripting.SWbemObjectSet」を見て貰えれば
ということなので、VBEの「ツール」から「参照設定」を開くと、
ホントだ! ちゃんとオブジェクトライブラリがあったんだ。
……てことは、変数もObject型にしなくて済むってことか。ほう……。
ともあれ、Itemプロパティではなく、ItemIndexプロパティ、しかも「0」始まり、ということを知ったので、早速(3)のように
If Not colLoggedEvents.itemIndex(0) Is Nothing Then
と書いた。これで、コレクションの1つ目の要素が存在するかどうかで判定ができる。
んで、(4)の
Set objEvent = colLoggedEvents.itemIndex(0)
で変数objEventにコレクションの1つ目をぶち込んで、後は前回と同じ。
もちろん、
With colLoggedEvents
でまとめておいて、
With colLoggedEvents If Not .itemIndex(0) Is Nothing Then retTimeWritten = convUTCtoJST(parseTimeWritten(.itemIndex(0).timeWritten)) getLastEventDateTime = retTimeWritten End If End With
と書くことも出来たとは思うけれど、慣れ親しんだobjEventさんを抹殺するのも忍びないし、可読性という点でイマイチなので、objEventさんには続投願うことにした。
おわりに
まだまだ知らないことが多いけれど、プログラミングが本当に楽しくなってきたぞ。
id:imihito さん、毎度毎度、本当にありがとうございます!!!!!!!!