作成 2009.12.28
更新 2010.01.09
ユーザーのログオン、ログオフをデータベースに記録するときのメモ
目次
基本情報
DBインストール時の追加設定
グループポリシーに設定する項目
データベース テーブル
その他の仕様
database.sql
logonoff.vbs
基本情報
項目 内容
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

©2004-2017 UPKEN IPv4