久しぶりのAccessのネタのわりには、ほんとたいしたことはなく使えそうなのかも よくわかりませんがとりあえず書きます。内容は表題のとおりですが、たいていのDBMSは 過去のログインは記録できるようになっていると思いますが、Accessの場合はサービスとして 監視しているわけではなく、ファイルベースなのでそういう機能はなかったはずです (もし、あったら教えてください)。 で、だれがそのデータベースを使っていたのかを 記録したいときは、mdb内にテーブルを作ってユーザ名や時間を記録するパターンが定番かなと 思います。ほかにメジャーな方法があったらこれも教えてください。これはこれでりっぱな 方法ですが、場合によっては記録されたレコードが勝手に書き換えることができてしまいます。 これから書く方法も消去できますが簡単にはいきません。
・イベントログの使用
スタート→ファイル名を指定して実行(R)から
eventvwr → OK
を実行すると、以下のようなイベントビューワが起動します(Windows 2000/XP)。
特定のmdbを起動した時に、このイベントログ
にユーザや時刻を記録していこうというのが今回のお題です。
ここに記録を残すにはWin32APIを使いますが、中心の関数は
HANDLE RegisterEventSource( LPCTSTR lpUNCServerName, // 操作を実行するサーバーの名前 LPCTSTR lpSourceName // 登録済みハンドルを取得するイベントソースの名前 );
と
BOOL ReportEvent( HANDLE hEventLog, // イベントログのハンドル WORD wType, // ログに書き込むイベントの種類 WORD wCategory, // イベントの分類 DWORD dwEventID, // イベント識別子 PSID lpUserSid, // ユーザーセキュリティ識別子(省略可能) WORD wNumStrings, // メッセージにマージする文字列の数 DWORD dwDataSize, // バイナリデータのサイズ(バイト数) LPCTSTR *lpStrings, // メッセージにマージする文字列の配列 LPVOID lpRawData // バイナリデータのアドレス );
です。RegisterEventSourceで取得した登録済みイベントログのハンドルを ReportEventの第一引数に指定して、イベントログに書き込むという具合です。イベントログに 何が書き込めるかというと、イベントビューワの1つのレコードを ダブルクリックするとわかりますが、
です。イベントログに関するテーマで別途書くつもりなので細かいことは省略しますが、
レジストリに登録したイベントソースと呼ばれるイベントを記録する
アプリケーション情報が書かれたエントリを元に上記の情報をReportEventの発行によって
書き込んでいきます。レジストリのイベントソースの情報には、サポートするイベントの種類、
イベントIDに対する説明やイベントの分類名を記録したDLLファイルへのパス
などが書き込まれています。
ということは、このDLLを作ってイベントソースを登録しないといけませんがVBAには
その機能がありません。無料のExpress版でもいいですがVisual StudioでDLLを作る必要がありますが、
今回はDLLも新たなイベントソースも作りません。作成しなかったらイベントログを
記録できないでしょ?と思われるかもしれませんが、これができるんです。ただし、
イベントソースの未登録とDLLの未作成の場合、制限が加わります。
イベントソースがないとアプリケーションに対応したイベントログは作成されず、既に存在する
"アプリケーションログファイル"が使われます。これはイベントログビューワで
見ている分にはそれほど変化ないですが、これ以外にもイベントの分類を表す値に対する分類名、
イベントIDに対応するイベントの解説が書き込まれているDLLが無いので、この項目は使えません。
かろうじてイベントIDに対応する説明にマージする文字列がReportEventのlpStrings引数で
指定できますがこれでは不完全です。MSもイベントソースを作成するように推奨していますが、
なしでも最低限のことはできますので省略します。
・実装
イベントログを記録するAPI
はC言語の宣言なのでVBAで使えるようにDeclareステートメントを使って定義したりして、
イベントログに記録できるような関数を作成します。なお、実装環境は
Windows 2000/XP + Access2000です。
MS-Access起動してイベントを記録しようとするmdbを開いた後、Alt + F11でVBエディタを
起動してください。ツールバーの挿入(I)→標準モジュール(M)で新しいモジュールを作成して
以下のコードを記述してください。
Option Compare Database Option Explicit '現在のログオンユーザ名の取得 Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" _ (ByVal lpBuffer As String, _ nSize As Long) As Long 'システム名/アカウント名からセキュリティID(SID)を取得 Private Declare Function LookupAccountName Lib "advapi32.dll" Alias "LookupAccountNameA" _ (ByVal lpSystemName As Long, _ ByVal lpAccountName As String, _ Sid As Byte, _ cbSid As Long, _ ByVal ReferencedDomainName As String, _ cbReferencedDomainName As Long, _ peUse As SID_NAME_USE) As Long '登録済みイベントログハンドル取得 Private Declare Function RegisterEventSource Lib "advapi32.dll" _ Alias "RegisterEventSourceA" _ (ByVal lpUNCServerName As Long, _ ByVal lpSourceName As String) As Long 'イベントログへの報告 Private Declare Function ReportEvent Lib "advapi32.dll" Alias "ReportEventA" _ (ByVal hEventLog As Long, _ ByVal wType As Long, _ ByVal wCategory As Long, _ ByVal dwEventID As Long, _ lpUserSid As Byte, _ ByVal wNumStrings As Long, _ ByVal dwDataSize As Long, _ lpString As String, _ lpRawData As Long) As Long 'アカウントのタイプ Private Enum SID_NAME_USE SidTypeUser = 1 SidTypeGroup SidTypeDomain SidTypeAlias SidTypeWellKnownGroup SidTypeDeletedAccount SidTypeInvalid SidTypeUnknown SidTypeComputer End Enum 'イベントの種類 Private Const EVENTLOG_SUCCESS As Long = &H0 Private Const EVENTLOG_ERROR_TYPE As Long = &H1 Private Const EVENTLOG_WARNING_TYPE As Long = &H2 Private Const EVENTLOG_INFORMATION_TYPE As Long = &H4 Private Const EVENTLOG_AUDIT_SUCCESS As Long = &H8 Private Const EVENTLOG_AUDIT_FAILURE As Long = &H10 '現在のログオンユーザのセキュリティID(SID)を取得 '引数 argcSid 現在のログオンユーザのSIDを受取る '戻値 SID取得成功時は argcSidにセキュリティIDが格納されTRUEを返却 '仕様 argcSidのサイズ(可変長)はこの関数内で計算後、領域確保される Private Function FnGetCurrentUserSid(argcSid() As Byte) As Boolean Dim vsUser As String * 256 'ログオンユーザ名 Dim vlUserSize As Long 'vsUserの文字数 Dim vsDomain As String * 256 'ドメイン名 Dim vlDomainSize As Long 'vsDomainの文字数 Dim vlSidSize As Long 'セキュリティID(SID)のサイズ(可変長) Dim veSnu As SID_NAME_USE 'SIDの種類 vlUserSize = Len(vsUser) '現在のログオンユーザ名の取得 If GetUserName(vsUser, vlUserSize) = 0 Then Exit Function vlDomainSize = Len(vsDomain) vlSidSize = 0 'セキュリティID(SID)のサイズ取得 LookupAccountName lpSystemName:=0, _ lpAccountName:=vsUser, _ Sid:=0, _ cbSid:=vlSidSize, _ ReferencedDomainName:=vsDomain, _ cbReferencedDomainName:=vlDomainSize, _ peUse:=veSnu '(C) If vlSidSize <= 0 Then Exit Function 'セキュリティID(SID)の領域確保 ReDim argcSid(vlSidSize - 1) 'セキュリティID(SID)・ドメイン名の取得 If LookupAccountName(lpSystemName:=0, _ lpAccountName:=vsUser, _ Sid:=argcSid(0), _ cbSid:=vlSidSize, _ ReferencedDomainName:=vsDomain, _ cbReferencedDomainName:=vlDomainSize, _ peUse:=veSnu) = 0 Then Exit Function End If FnGetCurrentUserSid = True End Function 'イベントログへ書込み '引数 argsMsg イベントログに書き込むメッセージ ' arglData イベントログに書き込むバイナリデータ '戻値 アプリケーションログに書き込まれた時True Public Function GFnReportEvent(argsMsg As String, _ arglData As Long) As Boolean Dim vcSid() As Byte 'SID Dim vlHandle As Long 'セキュリティIDの取得 If FnGetCurrentUserSid(argcSid:=vcSid) = False Then Exit Function '登録済みイベントソースのハンドル取得 '(レジストリにソースを作成してないのでアプリケーションログが使用される) vlHandle = RegisterEventSource(lpUNCServerName:=0, _ lpSourceName:="MSACCESS") '(A)イベントソース名(未登録) If vlHandle = 0 Then Exit Function 'イベントログに書込み If ReportEvent(hEventLog:=vlHandle, _ wType:=EVENTLOG_INFORMATION_TYPE, _ wCategory:=0, _ dwEventID:=0, _ lpUserSid:=vcSid(0), _ wNumStrings:=1, _ dwDataSize:=4, _ lpString:=argsMsg, _ lpRawData:=arglData) <> 0 Then '(B) GFnReportEvent = True End If End Function |
現在のWindowsのログイン名(ユーザ名)からSIDを求めているのが、 FnGetCurrentUserSid関数です。SIDは可変長なので取得はちょっとややこしいですが、 ユーザ名をGetUserNameで得た後、一度目のLookupAccountNameの発行でSIDのサイズ(バイト単位) を求めます(C)。Byte型の配列であるargcSidにサイズ分の領域を確保して、 再度LookupAccountNameを発行するとargcSidにバイトシーケンスとしてSID収められ、 ReportEventに渡しています。
ReportEventのlpStringにはイベントIDに対応する説明文にマージする文字列を 指定できますが、その説明文はなく、この文字列はエラー文の最後に付け加えられます。 (下図の説明(D)参照)
ReportEventのarglDataは今回はLong型のデータを渡していますが、サイズさえ dwDataSize引数に渡せばどんな型でもいいです。エラー時にイベントログを使う場合、 エラー番号をここに渡すことが多いです。
以上でできたので、mdbが開かれたらイベントログに 書き込まれるようにAutoExecマクロのプロシージャの実行アクションにGFnReportEvent関数を 登録します。
データベースウィンドウのマクロから新規のマクロを作り、AutoExec
という名前で保存して、上図のように「プロシージャの実行」アクションを加えてください。
「アクションの引数」に先ほど作成した関数を登録しますが、上図では途中で切れているので
GFnReportEvent ("EventLog.mdbを開きました", 1)
と記述してください。
以上ですべて終わりましたのでいったんmdbを閉じてください。再びmdbを開いた後、 イベントビューワを開いて上図のようにアプリケーションログの 最初のログをダブルクリックしてください。下図のようなイベントが登録されているはずです。