タイムスタンプの罠を回避せよ:FSOにおける「更新日時」と「アクセス日時」の真実
業務自動化の世界において、ファイル操作は最も基本的でありながら、最も「設計の甘さ」が露呈しやすい領域だ。
多くの開発者が、File System Object (FSO) を使って「古いファイルを削除する」「特定の期間のログを抽出する」といったスクリプトを、リファレンス通りに、何も考えずに書き始める。しかし、その設計には致命的な欠陥が潜んでいることが多い。
特に、`DateLastModified`(最終更新日時)と `DateLastAccessed`(最終アクセス日時)の使い分けを誤ると、「必要なデータを誤って削除する」、あるいは「監査ログに虚偽の記録を残す」という、業務システムとしてあってはならない事態を招く。
今日は、プロフェッショナルとして、この「時間のプロパティ」をどう制御し、どう設計すべきかを伝授する。
1. 属性情報の正体:何が「変化」を意味するのか
FSOの `File` オブジェクトが提供する日時プロパティは、単なる数字ではない。それらはファイルに対する「操作の履歴」である。
DateLastModified(最終更新日時)
これは、「ファイルの中身(データ)が書き換えられた瞬間」を指す。
テキストファイルに一行追記した、Excelのセルを書き換えた、バイナリを更新した――これらがトリガーだ。業務ロジックにおいて、「いつデータが最新になったか」を知るための唯一の信頼できる指標である。
DateLastAccessed(最終アクセス日時)
これは、「ファイルの内容が読み取られた、あるいは属性が参照された瞬間」を指す。
ここが初心者の陥る最大の罠だ。
「ファイルを開いて中身を見た」ときだけではなく、以下のケースでも更新される可能性がある。
- アンチウイルスソフトによるスキャン
- Windows Searchインデックス作成サービスによる巡回
- バックアップソフトによる読み取り
つまり、「人間がそのファイルを利用したかどうか」は、`DateLastAccessed` だけでは担保できないのだ。
2. 設計思想:どのプロパティを「真実」とするか
実務における設計指針を明確に定義しておく。
ケースA:監査ログ・履歴管理
採用すべき指標:`DateLastModified`
「いつ、誰が、どのデータを更新したか」を記録する場合、アクセス日時はノイズでしかない。インデックス作成サービスがファイルを触っただけで「アクセスがあった」と記録されては、監査の意味をなさない。
ケースB:不要ファイルの自動削除(クリーンアップ)
採用すべき指標:`DateLastModified` + 余裕を持たせた判定
「一定期間使われていないファイルを消す」という要件に対し、安易に `DateLastAccessed` を使うのは危険だ。上述の通り、システムプロセスによる「誤検知」が頻発するからだ。
もし「本当に誰も見ていないファイル」を特定したいのであれば、`DateLastAccessed` を利用するにしても、「システムによるスキャンが頻繁に行われる環境では、アクセス日時の判定は行わない」という判断を下すのが、アーキテクトとしての正しい設計である。
3. プロダクション・コード:堅牢なファイル判定エンジン
では、実際に現場で使えるコードを示そう。単なる比較ではなく、エラーハンドリングと、オブジェクトのライフサイクルを考慮した、保守性の高い実装だ。
以下のスクリプトは、指定したフォルダ内のファイルを走査し、「更新日時」に基づいて「更新が必要なファイル」と「古いファイル」を分類する。
'**
' Script Name: FileTimestampAnalyzer.vbs
' Description: FSOを用いた堅牢なファイル日時判定スクリプト
' Author: Chief Architect
'**
Option Explicit
' 定数の定義(マジックナンバーを排除し、保守性を高める)
Const DAYS_THRESHOLD = 30 ' 30日以上更新がないものを「古い」と判定
Dim fso, targetFolder, file
Dim targetPath
' 実行対象のパス(実際の実務では引数や設定ファイルから取得すべき)
targetPath = "C:\Your\Target\Folder"
Set fso = CreateObject("Scripting.FileSystemObject")
' メインロジックの呼び出し
ExecuteFileAnalysis targetPath
' --- 関数定義 ---
Sub ExecuteFileAnalysis(path)
On Error Resume Next ' エラーハンドリングの開始
' 1. ディレクトリの存在確認
If Not fso.FolderExists(path) Then
WScript.Echo "Error: 指定されたパスが存在しません: " & path
Exit Sub
End If
Dim targetFolder
Set targetFolder = fso.GetFolder(path)
WScript.Echo "--- 分析開始: " & targetFolder.Path & " ---"
WScript.Echo "判定基準: 更新日時が " & DAYS_THRESHOLD & " 日以上経過しているか"
WScript.Echo "--------------------------------------------------"
' 2. ファイルのループ処理
For Each file In targetFolder.Files
' ファイルへのアクセスエラー(使用中のファイル等)を考慮
If Err.Number <> 0 Then
WScript.Echo "[SKIP] アクセス失敗: " & file.Name & " (Error: " & Err.Description & ")"
Err.Clear
Else
AnalyzeFile file
End If
Next
WScript.Echo "--------------------------------------------------"
WScript.Echo "分析完了。"
' オブジェクトの解放
Set targetFolder = Nothing
On Error GoTo 0
End Sub
Sub AnalyzeFile(fileObj)
' プロパティの取得
Dim lastModified, lastAccessed, diffDays
lastModified = fileObj.DateLastModified
lastAccessed = fileObj.DateLastAccessed
' 差分計算(DateDiff関数を使用)
' "d" は日単位の差分
diffDays = DateDiff("d", lastModified, Now())
' 判定ロジック
' ここでは「業務上の重要度」に基づき、DateLastModifiedを主軸に据える
If diffDays >= DAYS_THRESHOLD Then
WScript.Echo "[OLD] " & fileObj.Name & _
" | 更新: " & lastModified & _
" | (経過: " & diffDays & "日)"
Else
WScript.Echo "[OK ] " & fileObj.Name & _
" | 更新: " & lastModified & _
" | (アクセス: " & lastAccessed & ")"
End If
End Sub
' オブジェクトのクリーンアップ
Set fso = Nothing
このコードの設計ポイント:
1. Error Handling (`On Error Resume Next`):
ファイル操作において、ファイルが他のプロセスによってロックされているケースは日常茶飯事だ。エラーを無視して止まるのではなく、エラーを検知して「スキップ」し、処理を継続させるのが業務ツールの鉄則である。
2. Separation of Concerns (関心の分離):
`ExecuteFileAnalysis`(走査)と `AnalyzeFile`(個別判定)を分けることで、判定ロジックの変更(例:更新日時ではなくファイルサイズも見るなど)に強い構造にしている。
3. Avoid Magic Numbers:
`30` という数値を直接ロジック内に書かず、定数 `DAYS_THRESHOLD` として定義している。これにより、将来の仕様変更時に修正箇所が一点で済む。
4. Resource Management:
スクリプトの終端で `Set fso = Nothing` を行い、オブジェクトのライフサイクルを明示的に管理している。大規模なループを回す際、メモリリークを防ぐための基本動作だ。
結論
`DateLastAccessed` は、OSという巨大な機構が管理する「目安」に過ぎない。
業務自動化エンジニアが信頼すべきは、常に `DateLastModified` である。
「なぜこのファイルは、触っていないはずなのにアクセス日時が変わっているのか?」
この問いに答えられるようになれば、君の書くスクリプトは、単なる「動くコード」から、「信頼に値するシステム」へと昇華するだろう。
