直近の起動・終了日時を取得するFunction(2)

直近のイベント日時を取得するFunctionの改良

前回

akashi-keirin.hatenablog.com

の続きです。

イベントコードを引数として受け取る

前回は、シャットダウン日時を取得する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型」というのは自作の列挙体型。

こいつを指定しておくことで、

f:id:akashi_keirin:20171105204421j:plain

こんなふうにヒントが出るし、

f:id:akashi_keirin:20171105204429j:plain

インテリセンスで入力候補も表示される。メッチャ便利。

f:id:akashi_keirin:20171105204439j:plain

ま、イベントコードに該当する数字を渡したら、そのイベントの直近の日時を返してしまうんですが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の「ツール」から「参照設定」を開くと、

f:id:akashi_keirin:20171105204412j:plain

ホントだ! ちゃんとオブジェクトライブラリがあったんだ。

……てことは、変数も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 さん、毎度毎度、本当にありがとうございます!!!!!!!!