こんにちは!現場の自動化を進めていくと、やぶさかではない壁にぶつかりますよね。そう、「複数のバッチ処理(VBScript)を同時に動かしたら、ログファイルへの書き込みでエラーが起きて止まった!」というトラブルです。
Windowsのタスクスケジューラーなどで複数のVBScriptを同時に走らせると、全員が一斉に同じログファイルを書き換えようとして、OS側から「アクセスが拒否されました(Permission Denied)」と冷たく突き放されてしまいます。
今回は、そんな現場の修羅場をスマートに切り抜けるための「FSO(FileSystemObject)を使った簡易排他ロック機構」の極意を伝授します。ここをクリアすれば、VBScriptのファイル操作で恐いものはなくなりますよ。しっかりついてきてくださいね!
—
なぜログファイルの書き込み競合は起きるのか?
VBScriptでよく使われる `FileSystemObject` の `OpenTextFile` メソッド。これは非常に強力ですが、ファイルの「占有」に関する制御を自分で優しくケアしてあげないと、複数プロセス環境ではあっさり破綻します。
イメージとしては、こんな感じです。
[VBScript A] ──┐
├──> 同時に 「log.txt」 を開いて書き込もうとする! 💥 [衝突・エラー発生]
[VBScript B] ──┘
同時にファイルを読み書きしようとするから喧嘩になるわけです。
ならば、どうするか? 「順番待ちの行列(ロック)」を作ってあげればいいのです。
—
解決策:ファイル存在を利用した「簡易排他ロック機構」
大掛かりなデータベースやミドルウェアを導入する必要はありません。VBScriptとファイルシステム(FSO)の基本機能だけで、堅牢なロック機構は作れます。
仕組みはシンプルです。
1. ログに書き込む前に、「lock.lck」のような目印のファイル(ロックファイル)を生成しようとする。
2. すでにロックファイルが存在していれば、他のプロセスが作業中ということなので、少し待って(スリープして)からリトライする。
3. ロックファイルの生成に成功したら、自分だけの独占権をゲット! 安心してログを書き込む。
4. 書き込みが終わったら、ロックファイルを削除して次の人に道を譲る。
これを図解するとこうなります。
[VBScript A] ──> [lock.lck 生成トライ] ──> 【成功】 ──> ログ書き込み ──> [lock.lck 削除]
[VBScript B] ──> [lock.lck 生成トライ] ──> 【失敗(他が使用中)】 ──> 少し待つ(Sleep) ──> リトライ
美しく、そして非常に泥臭く確実なアプローチですね。
—
実装コード:安全なログ書き込み関数
それでは、実際の現場でそのままコピペして使える実践的なコードを公開します。
この `WriteLogWithLock` 関数をあなたのスクリプトに組み込むだけで、マルチプロセス環境でも安全にログを残せるようになります。
Option Explicit
‘ —————————————————————–
‘ メイン処理のシミュレーション
‘ —————————————————————–
Dim logPath, lockPath
logPath = “C:\Logs\operation.log”
lockPath = “C:\Logs\operation.lck”
‘ ログ書き込み関数を呼び出す
Call WriteLogWithLock(logPath, lockPath, “処理を開始しました。”)
‘ (ここに実際のバッチ処理を書く)
Call WriteLogWithLock(logPath, lockPath, “処理が正常に終了しました。”)
‘ —————————————————————–
‘ 【核心】排他制御付きログ書き込み関数
‘ —————————————————————–
Sub WriteLogWithLock(ByVal strLogPath, ByVal strLockPath, ByVal strMessage)
Dim fso, ts
Dim maxRetry, retryInterval, i
Dim isLocked
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ログを保存するフォルダが存在しない場合は自動作成する優しさ
Dim parentFolder
parentFolder = fso.GetParentFolderName(strLogPath)
If Not fso.FolderExists(parentFolder) Then
fso.CreateFolder(parentFolder)
End If
maxRetry = 10 ‘ 最大リトライ回数(例: 10回)
retryInterval = 500 ‘ リトライ間隔(ミリ秒単位。例: 500ms = 0.5秒)
isLocked = False
‘ — 1. 排他ロックの獲得ループ —
For i = 1 to maxRetry
On Error Resume Next
‘ CreateTextFileの第2引数(False)により、ファイルが既に存在する場合はエラーになる特性を利用
Set ts = fso.CreateTextFile(strLockPath, False)
If Err.Number = 0 Then
‘ エラーなく作成できたらロック獲得成功!
isLocked = True
On Error GoTo 0
Exit For
End If
On Error GoTo 0
‘ ロック獲得失敗時は少し待つ(WScript.Sleepはミリ秒指定)
WScript.Sleep retryInterval
Next
‘ — 2. ロックが取れなかった場合の強制脱出(タイムアウト) —
If Not isLocked Then
‘ 現場でデバッグしやすいよう、最低限コンソールに泣き言を残す
WScript.Echo “【警告】ログファイルのロック取得に失敗しました(タイムアウト): ” & strMessage
Exit Sub
End If
‘ — 3. 安全にログファイルへ追記 —
On Error Resume Next
‘ ForAppending (8) でファイルを開く。存在しない場合は新規作成(True)
Const ForAppending = 8
Set ts = fso.OpenTextFile(strLogPath, ForAppending, True)
If Err.Number = 0 Then
ts.WriteLine “[” & Now() & “] ” & strMessage
ts.Close
End If
On Error GoTo 0
‘ — 4. ロックファイルの解放(必ず実行する) —
If fso.FileExists(strLockPath) Then
fso.DeleteFile strLockPath, True
End If
Set fso = Nothing
End Sub
—
コードの重要なポイントとエンジニアの知見
このコードには、現場で培った「VBScriptを安定稼働させるための知見」がいくつか詰まっています。
1. `CreateTextFile(path, False)` のエラーを逆手に取る
FSOの `CreateTextFile` メソッドは、第2引数を `False` にすると、「すでに同名のファイルが存在している場合にエラーを発生させる」という挙動をします。
これを逆手に取り、「エラーが起きなかった=誰もロックファイルを持っていない(=ロック獲得成功)」と判定しています。非常にシンプルでスマートな排他制御の常套手段です。
2. `On Error Resume Next` による堅牢性
ファイルシステムを扱うコードでは、他のプロセスとのタイミング次第で予期せぬ瞬間的エラー(ファイルが瞬間的にロックされている等)が起きます。
エラーでスクリプト全体が停止(クラッシュ)しないよう、適切に `On Error` でガードしつつ、リトライ処理へ回すのがプロの作法です。
3. ロックファイルの「消し忘れ」対策
もしバッチの途中でスクリプトが異常終了した場合、ロックファイル(`1.lck`)が残しっぱなしになり、次のバッチが永遠に動かなくなる「デッドロック」の危険性があります。
今回は解説をシンプルにするため省いていますが、実務では「ロックファイルの更新日時が5分以上前なら強制削除する」といったセーフティネットを組み込んでおくと、さらに夜間バッチなどが鉄壁になります。
—
まとめ
今回は、複数バッチの同時実行時におけるログ競合を回避する、FSOを使った簡易ロック機構について解説しました。
- 複数プロセスが同時に書き込むと `Permission Denied` が起きる
- 専用のロックファイル(`.lck`)の存在有無で排他制御を行う
- 取れなかった場合は `WScript.Sleep` で行儀よく待たせる
ここをクリアできれば、VBScriptを使った本格的な業務自動化システムの構築も怖くありません。ぜひ、あなたの現場のバッチ処理に組み込んで、安定した自動化ライフを手に入れてくださいね。
それでは、また次の知見でお会いしましょう!バッチリ決めていきましょう!
