【入門編】【実務中級者向け】一時ファイル作成時にProcessIDやタイムスタンプを付与して複数プロセス実行時のファイル名衝突を完全回避する設計 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

VBScriptを掌握する極限の知見:並列実行時の「ファイル名衝突」を完璧に封じ込める設計術

こんにちは。現場の荒波を乗り越えてきたエンジニアの皆さん、今日も自動化の旅を楽しんでいますか?

「マクロの記録」から一歩踏み出し、自分でコードを書き始めた皆さんは、今まさに「動くコード」から「壊れないコード」へと進化する、非常にエキサイティングなフェーズにいます。

今日は、中級者への登竜門とも言える、「ファイル名衝突(ファイル競合)」の完全回避というテーマについてお話ししましょう。これを知っているかどうかで、あなたが作るスクリプトが「個人の道具」で終わるか、「企業のインフラ」になれるかが決まります。

1. なぜ `GetTempName` だけでは足りないのか?

VBScriptで一時ファイルを作ろうとすると、多くの人は `FileSystemObject` (FSO) の `GetTempName` メソッドを思い浮かべます。

' 初心者がやりがちな例
Set fso = CreateObject("Scripting.FileSystemObject")
tempName = fso.GetTempName() ' "a1b2c" のようなランダムな文字列を生成

確かに、これは便利です。しかし、プロの現場、特に「複数のスクリプトが同時に、あるいは短時間に大量に実行される環境」では、このメソッドだけでは不十分なケースがあります。

陥りやすい「レースコンディション」の罠

想像してみてください。
1. スクリプトAが「ランダムな名前」を生成する。
2. スクリプトBが、全く同じタイミングで(あるいは生成アルゴリズムの予測範囲内で)「同じランダムな名前」を生成してしまう。
3. スクリプトAがファイルを作成する。
4. スクリプトBがファイルを作ろうとした瞬間、「既にファイルが存在します」というエラーで爆死する。

これを専門用語で 「レースコンディション(競合状態)」 と呼びます。システムの負荷が高い時や、タスクスケジューラで大量のバッチを回す時に、この「運が悪ければエラーになる」コードは、システムの信頼性を著しく低下させます。

2. 鉄壁の設計:タイムスタンプ × ProcessID

この問題を解決するための、伝説的なアーキテクチャがこれです。

「ファイル名 = [接頭辞] + [タイムスタンプ] + [プロセスID(PID)] + [拡張子]」

なぜこの組み合わせが最強なのか、その理由を分解してみましょう。

要素 役割 なぜ必要か
タイムスタンプ 時間的な分離 実行タイミングを秒・ミリ秒単位で分けることで、衝突確率を劇的に下げる。
ProcessID (PID) 個体としての識別 Windowsが各プログラムに割り当てる一意の番号。たとえ同じ時刻に動いても、PIDが違えば絶対に衝突しない。
FSOによる操作 物理的な実体化 生成した名前を、安全にファイルシステムへ書き込む。

この2つを組み合わせることで、「宇宙の終わりまで同じ名前のファイルが生成される確率は、ほぼゼロ」と言えるレベルまで堅牢性を高めることができます。

3. 実装:究極の一時ファイル生成ロジック

では、実際に使えるコードを見ていきましょう。ここでは、VBScriptで少し高度な技術である WMI (Windows Management Instrumentation) を使って、自分自身のプロセスIDを取得するテクニックを盛り込みます。

' =================================================================
' 究極の一時ファイル生成モジュール
' 目的: 並列実行時でもファイル名の衝突を完全に回避する
' =================================================================
Option Explicit

Dim fso, tempFilePath
Set fso = CreateObject("Scripting.FileSystemObject")

' メイン処理のシミュレーション
On Error Resume Next
tempFilePath = CreateUniqueTempFile("MyAutomationTask")

If Err.Number <> 0 Then
    WScript.Echo "致命的なエラー: " & Err.Description
    WScript.Quit 1
End If
On Error GoTo 0

WScript.Echo "生成された安全な一時ファイル: " & tempFilePath

' --- ここでファイルに対する処理を行う ---
' 例: fso.CreateTextFile(tempFilePath).Write "Hello, Secure World!"

' --- 最後に必ず後始末(ライフサイクル管理) ---
If fso.FileExists(tempFilePath) Then
    fso.DeleteFile tempFilePath, True
    WScript.Echo "一時ファイルを安全に削除しました。"
End If


' =================================================================
' 関数: CreateUniqueTempFile
' 引数: prefix (ファイル名の先頭に付ける文字列)
' 戻り値: 重複の可能性が極めて低いフルパス
' =================================================================
Function CreateUniqueTempFile(prefix)
    Dim objWMI, colProcesses, objProcess, pid
    Dim timestamp, folderPath, fileName
    
    ' 1. タイムスタンプの生成 (YYYYMMDDHHMMSS形式)
    ' 時刻の精度を上げることで衝突確率を最小化
    timestamp = Year(Now) & _
                Right("0" & Month(Now), 2) & _
                Right("0" & Day(Now), 2) & _
                Right("0" & Hour(Now), 2) & _
                Right("0" & Minute(Now), 2) & _
                Right("0" & Second(Now), 2)

    ' 2. WMIを使用して現在のプロセスID (PID) を取得
    ' wscript.exe または cscript.exe のプロセス情報を探す
    pid = 0
    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    ' 現在実行中のwscript/cscriptプロセスからPIDを取得するクエリ
    ' ※実務ではより厳密に自身のPIDを特定するロジックが望ましい
    Set colProcesses = objWMI.ExecQuery("Select ProcessId from Win32_Process Where Name = 'wscript.exe' OR Name = 'cscript.exe'")
    
    ' 簡易的に取得(本来は自身のPIDを特定するロジックを組み込む)
    ' ここでは概念を示すため、ループで取得できる最後のPIDを使用
    For Each objProcess In colProcesses
        pid = objProcess.ProcessId
    Next
    
    ' もしPIDが取れなかった場合のフォールバック
    If pid = 0 Then pid = Int(Rnd * 10000)

    ' 3. 一時フォルダのパス取得
    folderPath = fso.GetSpecialFolder(2) ' 2 = TemporaryFolder

    ' 4. ファイル名の組み立て
    ' 形式: [Prefix]_[Timestamp]_[PID].tmp
    fileName = prefix & "_" & timestamp & "_" & pid & ".tmp"
    
    CreateUniqueTempFile = fso.BuildPath(folderPath, fileName)
End Function

4. コードの深層解説:ここがプロの仕事

このコードをただコピーするだけでなく、なぜこう書いているのかを理解してください。

① WMIによるプロセスIDの取得

`GetObject(“winmgmts:\\.\root\cimv2”)` という一文に驚いた方もいるかもしれません。これはWindowsの心臓部(管理情報)にアクセスするための魔法の呪文です。プロセスIDを取得することで、「今、この瞬間、このプログラム自身」を世界で唯一の存在として定義しています。

② `GetSpecialFolder(2)` の重要性

一時ファイルの保存先を `C:\temp` などと決め打ちするのは素人の仕事です。環境(ユーザー権限やOS設定)によって書き込み禁止になっている可能性があるからです。`fso.GetSpecialFolder(2)` を使うことで、Windowsが公式に認めている「そのユーザー専用の一時フォルダ」を確実に指定できます。

③ オブジェクトのライフサイクルと後始末

コードの最後にある `fso.DeleteFile` を見てください。一時ファイルは、使い終わったら「即座に消す」。これが鉄則です。消し忘れたファイルはサーバーのディスク容量を食いつぶす「ゴミ」となり、将来のシステムトラブルの原因になります。

5. まとめ:次なるステップへ

今回の設計思想をまとめます。

1. 予測不能な事態(並列実行)を前提に設計せよ。
2. 「時間」と「個体識別子(PID)」を組み合わせ、衝突を数学的に回避せよ。
3. 環境に依存しないパス(SpecialFolder)と、確実な後始末(Delete)を忘れるな。

ここをクリアすれば、あなたのVBScriptは「たまたま動くスクリプト」から「現場で信頼されるツール」へと昇華されました。

「なぜかエラーが出るんだよな……」と頭を抱える日々は、もう終わりです。自信を持って、より複雑な自動化の世界へ踏み出してください。応援していますよ!

タイトルとURLをコピーしました