【極限の知見】FSOにおける「最終アクセス日時」の罠と、真に信頼できるファイル監査ロジックの構築
エンタープライズ環境のレガシーシステムを保守し、あるいは大規模なファイルサーバーの運用を担うエンジニアにとって、ファイルのメタデータ操作は日常的な業務だ。しかし、多くの者が `FileSystemObject` (FSO) の `DateLastAccessed` を、その名の通り「最後にファイルに触れた時刻」として盲信し、重大な監査ミスを犯している。
本稿では、VBScriptにおけるファイル操作の真髄に触れ、なぜ `DateLastAccessed` が「嘘をつく」のか、そしてプロフェッショナルとしてどのように正確なファイル監査ロジックを構築すべきかを、アーキテクトの視点から詳説する。
1. メタデータの真実:Modified と Accessed の決定的な違い
FSOの `File` オブジェクトが提供するプロパティには、一見すると単純な違いしかないように見える。しかし、その背後にあるファイルシステム(NTFS)の挙動を理解していなければ、設計段階で致命的な欠陥を抱え込むことになる。
DateLastModified(最終更新日時)
これはファイルの内容(データストリーム)が書き換えられた瞬間のタイムスタンプだ。
- 信頼性: 極めて高い。
- 用途: バージョン管理、データの整合性チェック、変更検知。
- 挙動: ファイルの書き込み、または一部の属性変更を伴う保存操作によって更新される。
DateLastAccessed(最終アクセス日時)
これが問題の種だ。理論上は「ファイルの内容を読み取った(Read)時刻」を指す。
- 信頼性: 極めて低い(環境に依存する)。
- 用途: 未使用ファイルの特定、クリーンアップ対象の選定。
- 挙動: ファイルのオープン、読み取り操作によって更新される。
なぜ Accessed は「嘘」をつくのか?
現代のWindows OS(Windows 7以降、特にWindows 10/11やWindows Serverシリーズ)において、NTFSのパフォーマンス最適化のため、「Last Access Time の更新」はデフォルトで無効化、あるいは更新間隔が大幅に遅延される設定になっている。
レジストリの `NtfsDisableLastAccessUpdate` が `1` に設定されている環境下では、どれほどファイルを読み取ろうとも `DateLastAccessed` は更新されない。つまり、「アクセス日時が古いからといって、そのファイルが長期間使われていないとは限らない」 というのが、システム管理者が知っておくべき冷酷な真実である。
2. 実践的実装:メモリ効率と堅牢性を両立したファイルスキャン
大量のファイルを走査する監査スクリプトを記述する際、初心者は「動けば良い」というコードを書く。しかし、シニアエンジニアは「リソースの枯渇」と「例外処理」を設計に組み込む。
以下に、FSOを用いた、メモリ最適化済みのファイル監査コードのテンプレートを示す。
'**
' Script: AuditFileMetadata.vbs
' Description: ファイルの更新日時とアクセス日時を比較し、監査ログを出力する。
' メモリリークを防ぐためのオブジェクト解放を徹底している。
'**
Option Explicit
Dim fso, targetFolder, file, logFile
Dim strFolderPath, strLogPath
Dim diffDays
' 設定領域
strFolderPath = "C:\AuditTarget" ' 監査対象ディレクトリ
strLogPath = "C:\Logs\AuditLog_" & Replace(Replace(Now, ":", "-"), " ", "_") & ".txt"
Set fso = CreateObject("Scripting.FileSystemObject")
' 1. 存在確認とエラーハンドリング
If Not fso.FolderExists(strFolderPath) Then
WScript.Echo "Error: Target folder does not exist: " & strFolderPath
WScript.Quit
End If
On Error Resume Next
Set logFile = fso.CreateTextFile(strLogPath, True)
If Err.Number <> 0 Then
WScript.Echo "Error: Cannot create log file. Check permissions."
WScript.Quit
End If
On Error GoTo 0
WScript.Echo "Audit started. Logging to: " & strLogPath
' 2. フォルダ走査の開始
Set targetFolder = fso.GetFolder(strFolderPath)
' ファイルオブジェクトのライフサイクルを管理するためのループ
For Each file In targetFolder.Files
' 個別のファイル処理において例外が発生しても、ループ全体を止めない設計
On Error Resume Next
Dim lastMod, lastAcc
lastMod = file.DateLastModified
lastAcc = file.DateLastAccessed
If Err.Number <> 0 Then
logFile.WriteLine "[" & Now & "] ERROR: Could not access " & file.Path & " - " & Err.Description
Err.Clear
Else
' 差分の計算 (更新日時とアクセス日時の乖離をチェック)
' ここでは「更新されていないのにアクセスだけされている」またはその逆を検知する
diffDays = DateDiff("d", lastMod, lastAcc)
' ログ出力フォーマット
logFile.WriteLine "File: " & file.Path
logFile.WriteLine " - Modified: " & lastMod
logFile.WriteLine " - Accessed: " & lastAcc
logFile.WriteLine " - Diff(Days): " & diffDays
' 【重要】アクセス日時が更新されていない可能性があるため、
' 運用上は Modified を主軸に判断することを推奨する。
If diffDays < 0 Then
logFile.WriteLine " - Note: File accessed after last modification."
End If
logFile.WriteLine "--------------------------------------------------"
End If
' エラーをクリアして次のファイルへ
On Error GoTo 0
Next
' 3. オブジェクトの明示的解放 (メモリ管理の極意)
' VBScriptではGC(ガベージコレクション)に頼りすぎるのは危険だ。
' 大規模なループや長時間稼働するプロセスでは、明示的なNothing代入が鉄則である。
logFile.Close
Set logFile = Nothing
Set targetFolder = Nothing
Set fso = Nothing
WScript.Echo "Audit completed successfully."
アーキテクトの解説:コードの急所
1. 明示的なオブジェクト解放 (`Set obj = Nothing`):
VBScriptはCOMオブジェクトを扱う。ループ内で大量のファイルオブジェクトが生成される場合、適切に参照を解除しなければメモリ使用量が指数関数的に増大し、システム全体のパフォーマンスを低下させる。
2. `On Error Resume Next` の局所化:
スクリプト全体を `On Error Resume Next` で覆うのは、プロフェッショナルとしては三流だ。エラーが発生しうる「ファイルへのアクセス」部分にのみ限定して適用し、即座に `Err.Clear` でリセットする。これにより、エラーの隠蔽を防ぎつつ、堅牢性を確保する。
3. `DateDiff` による差分分析:
単に日時を表示するのではなく、`DateDiff` 関数を用いて「更新からどれだけ時間が経過しているか」を数値化することで、後続の自動処理(例:30日以上更新がないファイルをアーカイブするなど)への拡張性を確保している。
3. 高度な要件へのアプローチ:FSOの限界を超えて
もし、君が「本当にアクセスされたかどうか」を、OSの設定に左右されずに判定しなければならない状況に置かれたなら、FSOを捨て、Windows APIの領域へと足を踏み入れる必要がある。
- Windows Event Logs: ファイルアクセス監査ポリシーを有効にしていれば、セキュリティイベントログから正確なアクセス履歴を抽出できる。
- Kernel-mode drivers / File System Filter Drivers: リアルタイムでの監視が必要な場合は、ファイルシステムへのI/Oリクエストをインターセプトする設計が求められる。
FSOはあくまで「手軽な自動化」のためのツールだ。その限界を理解し、「メタデータは常に不完全である可能性がある」 という前提に立って設計すること。それが、真のエンジニアリングである。
結論:
`DateLastModified` は「事実」を語るが、`DateLastAccessed` は「環境」を語る。
監査ログを作成する際は、常に環境設定(NTFSの挙動)を疑え。
