作成 2009.12.28
更新 2010.01.09
更新 2010.01.09
ユーザーのログオン、ログオフをデータベースに記録するときのメモ
基本情報
項目 | 内容 |
DNSドメイン名 | test.lan |
NTドメイン名 | TEST |
目的 | ユーザーのログオン/ログオフの記録をデータベースに残す |
サーバーOS | Windows Server 2008 R2 Standard |
DBソフト | SQL Server 2008 Express |
DBサーバー名 | TEST-DC1 (DCと兼用。セキュリティ警告が出るけど気にしない) |
DBポート | TCP 1433 |
DB名 | logdb |
イベントログ | DB側のエラーのみ通知 |
DBインストール時の追加設定
項目 | 内容 |
インスタンスID | Logonoff |
インスタンス ルート ディレクトリ | C:\Program Files\Microsoft SQL Server\ |
セキュリティ モード | Windows 認証モード |
DB管理者アカウント | TEST\Administrator |
SQL Server 構成マネージャ | SQL Server ネットワークの構成 TCP/IP が無効になっているので有効にする。 既定では動的ポートが設定されており、NICの各項目は無効になっているため有効にしたうえで、ポートを1433に設定した。 動的ポートを利用するには、SQL Server Browserサービスが起動している必要がある。今回は無効のまま。 IPv6を使用する場合は、固定IPに設定したうえでをrouterdiscoveryを無効化しないとアクセス障害を起こす要因になる。 セキュリティが強化された Windows ファイアウォール TCP 1433 を解放 実行プログラムで解放したほうがいいかな |
データベース構築 |
database.sqlの最下行あたりにドメイン名があるので適宜修正してください。
sqlcmd -E -i database.sql |
グループポリシーに設定する項目
logonoff.vbs の上段あたりに dbServer の設定項目があるので適宜修正してください。
設定 | スクリプト | 引数 |
ログオン スクリプト | logonoff.vbs | logon |
ログオフ スクリプト | logonoff.vbs | logoff |
データベース テーブル
テーブルは1つのみです。
user_log
カラム | データ型 | 用途 |
logtime | datetime2 | データベース側の登録時間。一意になると思っていたがそんなことはなかったぜ。logonretryが0のときはログオン時間。DEFAULT SYSDATETIME() |
updatetime | datetime2 | データベース側の更新時間。logoffretryが0のときはログオフ時間。NULL許可 |
username | nvarchar(128) | ユーザー名 SUESR_NAME()を使用している。 NOT NULL |
computer | nvarchar(128) | コンピュータ名 HOST_NAME()を使用している。NOT NULL |
server | nvarchar(128) | クライアントPCの %LOGONSERVER% 環境変数。この情報は正しくない可能性があるため参考値程度に使用してください。NOT NULL |
logontime | datetime | ログオンした時間。クライアントPCの時刻を使用しています。5分以上のずれはないはずです。NULL許可 |
logofftime | datetime | ログオフした時間。クライアントPCの時刻を使用しています。5分以上のずれはないはずです。NULL許可 |
logonretry | bit | ログオン時のDB登録に失敗し、後から登録すると1。端末の利用状況の参考になるかもしれない。NULL許可 |
logoffretry | bit | ログオフ時のDB登録に失敗し、後から登録すると1。端末の利用状況の参考になるかもしれない。NULL許可 |
その他の仕様
項目 | 動作 |
接続タイムアウト | 5秒 |
ログオン/ログオフ時刻の精度 | ±5分。クライアントPCに依存します。DCの時刻が正しく、kerberos認証の設定が既定値の前提。 |
バックアップ、リストア | DBの機能を使ってください。 |
スクリプトの暗号化 | VBScript Encoder を使って変換してください。 |
クライアントOS | Windows XP Pro SP3, Windows Server 2008 R2 で確認済み。 たぶんほかのOSでも動きます。 |
データベースのセキュリティについて | ドメイン ユーザーはデータベースへ接続でき、ストアド プロシージャのuser_logonoffのみ実行できます。 テーブルの操作は管理者以外できません。 また、データベースに記録されるユーザー名とホスト名はデータベース側の認証情報を使用するため捏造が困難な仕様になっています。 |
ログオン、ログオフ時にネットワークに接続していない。 またはDBが停止していた場合 | ローカルにテキスト形式で蓄積し、接続できたときにまとめて送信します。 |
エンドユーザーに通知されるものはあるか? | スクリプトが動作していることはわかりますがエラーが出ても通知されません。 |
スリープからシャットダウンした場合 | 未確認。たぶん動作しません。 |
クライアント端末をずーっとスリープで使った場合 | 記録されません。 |
ディスク使用量 | 基本容量は350MB弱。 10000ログオン、10000ログオフで2MB増加します。 10000ユーザーが1年間毎日1回ログオン/ログオフしたとして730MB増加。 10年間運用するとしても 10GB空き容量があれば十分です。 |
処理性能 | Core 2 Quad Q6600 中の1コア、RAM 1GB、SATA 仮想環境上で検証した結果、シーケンシャルで1秒間あたり109回(毎回接続)でした。 また、データ量が2倍になると10%性能が落ちます。 10000ユーザーが毎日1回ログオン/ログオフをした場合、半年後 48回/秒、1年後 43回/秒、5年後 35回/秒、10年後 31回/秒になります。 定期的に消したほうがいいかもしれませんが、性能は期待したほど改善しないのではないでしょうか。 |
database.sql
CREATE DATABASE logdb; GO USE logdb; GO CREATE TABLE user_log ( logtime datetime2 DEFAULT SYSDATETIME() ,updatetime datetime2 NULL ,username nvarchar(128) NOT NULL ,computer nvarchar(128) NOT NULL ,server nvarchar(128) NOT NULL ,logontime datetime NULL ,logofftime datetime NULL ,logonretry bit NULL ,logoffretry bit NULL ); GO CREATE INDEX user_log_idx ON user_log(username,computer,logtime); CREATE INDEX user_logtime_idx ON user_log(logtime); GO EXECUTE sp_addmessage 50001, 16, 'logon script error. username = %s, computer = %s, server = %s, pctime = %s', 'us_english', 'TRUE'; EXECUTE sp_addmessage 50002, 16, 'logoff script error. username = %s, computer = %s, server = %s, pctime = %s', 'us_english', 'TRUE'; GO CREATE PROCEDURE user_logonoff @server nvarchar(128) ,@pctime datetime ,@islogon bit = 1 ,@isretry bit = 0 AS DECLARE @maxtime datetime2 = NULL DECLARE @lastlogoff datetime = NULL DECLARE @update datetime2 = SYSDATETIME() DECLARE @chartime char(23) = CONVERT ( char, @pctime, 121 ) DECLARE @username nvarchar(128) = SUSER_NAME() DECLARE @computer nvarchar(128) = HOST_NAME() IF @server IS NULL OR @pctime IS NULL BEGIN IF @islogon = 1 BEGIN RAISERROR (50001, 16, 1, @username, @computer, @server, @chartime) END ELSE BEGIN RAISERROR (50002, 16, 1, @username, @computer, @server, @chartime) END RETURN END IF @islogon = 1 BEGIN INSERT INTO user_log(username,computer,server,logontime,logonretry) VALUES (@username, @computer, @server, @pctime, @isretry) END ELSE BEGIN SELECT TOP(1) @maxtime = logtime, @lastlogoff = logofftime FROM user_log WHERE username = @username AND computer = @computer AND logtime > DATEADD(year, -1, SYSDATETIME()) ORDER BY logtime DESC IF @maxtime IS NULL OR @lastlogoff IS NOT NULL BEGIN INSERT INTO user_log(updatetime,username,computer,server,logofftime,logoffretry) VALUES (@update, @username, @computer, @server, @pctime, @isretry) END ELSE BEGIN UPDATE user_log SET updatetime = @update, logofftime = @pctime, logoffretry = @isretry WHERE logtime = @maxtime AND username = @username AND computer = @computer END END; GO GRANT CONNECT TO [TEST\Domain Users]; GRANT EXECUTE ON user_logonoff TO [TEST\Domain Users]; GO
logonoff.vbs
' ログオン、ログオフの記録をデータベースに保存するスクリプト ' logonoff.vbs (logon|logoff) ' ログオン スクリプトとログオフ スクリプトに追加して使用する ' データベースは SQL Server 2008 を想定(Express でも可能) ' ログオン時またはログオフ時にネットワークにつながっていない場合や ' DBサーバーが停止していた場合はローカルに保存し、次回接続時に登録する Option Explicit Const saveName = "\logonoff.txt" Const dbServer = "TEST-DC1" Const dbPort = 1433 Const dbName = "logdb" Const ForReading = 1, ForWriting = 2, ForAppending = 8 Dim strServer, isLogon, strDateTime, strExec Dim objDB Dim WshShell, FSO Dim time, tempTimer Dim strFileName, objFile, strLine time = Now() tempTimer = Timer strDateTime = Year(time) & "/" & Right("0" & Month(time), 2) & "/" _ & Right("0" & Day(time), 2) & " " & Right("0" & Hour(time), 2) & ":" _ & Right("0" & Minute(time), 2) & ":" & Right("0" & Second(time), 2) & "." _ & Right("000" & Int((tempTimer - Int(tempTimer)) * 1000), 3) If WScript.Arguments.Count > 0 Then If WScript.Arguments(0) = "logon" Then isLogon = 1 Else isLogon = 0 End If Else WScript.Echo "logon または logoff の引数をつけて実行してください。" WScript.Quit End If Set WshShell = CreateObject("WScript.Shell") strServer = Mid(WshShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3) strFileName = WshShell.ExpandEnvironmentStrings("%TEMP%") & saveName Set FSO = CreateObject("Scripting.FileSystemObject") strExec = "EXECUTE user_logonoff N'" & strServer & "','" & strDateTime & "'," & isLogon Set objDB = CreateObject("ADODB.Connection") objDB.Provider = "SQLOLEDB" objDB.ConnectionTimeout = 5 objDB.ConnectionString = "Server=" & dbServer & "," & dbPort & ";Database=" & dbName _ & ";Integrated Security=sspi" On Error Resume Next objDB.Open If Err.Number <> 0 Then ' 接続できなかったのでテキストに保存 On Error Goto 0 Set objFile = FSO.OpenTextFile( strFileName, ForAppending, True ) objFile.WriteLine strExec & ",1" objFile.Close Else ' 接続できたら登録 On Error Goto 0 ' 前回登録できなかった分がテキストに保存されている場合は先にそれを登録 If FSO.FileExists( strFileName ) Then Set objFile = FSO.OpenTextFile( strFileName, ForReading ) Do While objFile.AtEndOfLine <> True strLine = Trim( objFile.ReadLine ) If Len(strLine) > 10 Then objDB.Execute strLine Loop objFile.Close FSO.DeleteFile( strFileName ) End If ' 今回の分を登録 objDB.Execute strExec & ",0" objDB.Close End If