VBScriptの限界を突破する:CScriptマルチプロセス・ディスパッチャーによるFSO並列分散処理の極意
単一スレッドの制約。それがVBScriptという言語が抱える、逃れようのない宿命である。
数万、数十万という規模のファイルを `FileSystemObject` (FSO) で走査し、属性を書き換え、あるいは移動・リネームする。この単純なタスクにおいて、逐次処理(Sequential Processing)を選択することは、エンジニアリングの観点から言えば「時間の浪費」以外の何物でもない。VBScriptはシングルスレッドである。一つのループが完了するまで、CPUの他のコアは遊んでいる。
もし君が、レガシーなWindows環境において、大量のファイル操作を劇的に高速化させたいと願うなら、言語の制約を「アーキテクチャ」で突破しなければならない。その解が、CScriptプロセスのマルチプロセス・ディスパッチャー・パターンである。
本稿では、単一のVBScriptを「司令塔(Dispatcher)」へと昇華させ、複数の「実行体(Worker)」を非同期に叩き起こすことで、I/O待ち時間を並列化し、処理時間を極限まで短縮する設計思想を詳解する。
1. アーキテクチャ設計:分散処理のメカニズム
単一のスクリプトで全てを完結させようとする思考を捨てろ。我々が構築するのは、以下の二層構造である。
1. Dispatcher (Master Process):
対象ディレクトリのファイルリストを取得し、ワークロードを分割(Chunking)する。分割されたタスクを引数と共に `cscript.exe` を介して非同期に起動する。
2. Worker (Slave Process):
Dispatcherから受け取った特定のインデックス範囲、あるいはファイルリストのサブセットに対して、実際のFSO操作を実行する。
この設計の肝は、`WScript.Shell` オブジェクトの `Run` メソッドにおける `bWaitOnReturn` 引数を `False` に設定することにある。これにより、DispatcherはWorkerの終了を待たずに、即座に次のプロセスを起動できる。
2. 実装:極限の並列ディスパッチャー
以下に、実戦投入可能なレベルのコードを示す。単なるサンプルではない。メモリ管理とエラーハンドリング、そしてプロセスの非同期制御を組み込んだ、プロフェッショナルのための設計である。
Dispatcher.vbs (司令塔)
' ==============================================================================
' Name: Dispatcher.vbs
' Description: ファイルリストを分割し、複数のWorkerプロセスを非同期起動する
' ==============================================================================
Option Explicit
Dim objFSO, objShell, objFolder
Dim strTargetDir, strWorkerPath, intMaxProcesses
Dim arrFiles, intTotalFiles, intChunkSize, i, intStart, intEnd
Dim strCommand, intRunningProcesses
' --- 設定領域 ---
strTargetDir = "C:\Target\Files" ' 処理対象ディレクトリ
strWorkerPath = "C:\Scripts\Worker.vbs" ' Workerスクリプトのパス
intMaxProcesses = 4 ' 並列実行数 (CPUコア数やI/O性能に合わせて調整)
' ----------------
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("WScript.Shell")
If Not objFSO.FolderExists(strTargetDir) Then
WScript.Echo "Error: Target directory not found."
WScript.Quit 1
End If
' 1. ファイルリストの全取得
Set objFolder = objFSO.GetFolder(strTargetDir)
' 配列の動的確保が必要だが、ここでは簡略化のため一旦全ファイルを走査
' 実際の大規模環境では、ファイル数が多い場合はメモリ消費に注意が必要
Dim colFiles, file, fileList()
Set colFiles = objFolder.Files
intTotalFiles = colFiles.Count
If intTotalFiles = 0 Then
WScript.Echo "No files to process."
WScript.Quit 0
End If
' ファイル名のリスト化(インデックスによる分割を容易にするため)
ReDim fileList(intTotalFiles - 1)
i = 0
For Each file In colFiles
fileList(i) = file.Path
i = i + 1
Next
' 2. 分割計算
intChunkSize = Int(intTotalFiles / intMaxProcesses)
If intChunkSize = 0 Then intChunkSize = intTotalFiles
WScript.Echo "Total Files: " & intTotalFiles
WScript.Echo "Chunk Size: " & intChunkSize
' 3. 非同期プロセスの起動
For i = 0 To intMaxProcesses - 1
intStart = i * intChunkSize
' 最後のプロセスが残りの全ファイルをカバーするように調整
If i = intMaxProcesses - 1 Then
intEnd = intTotalFiles - 1
Else
intEnd = (i + 1) * intChunkSize - 1
End If
' 範囲外チェック
If intStart < intTotalFiles Then
' 引数として開始インデックスと終了インデックスを渡す
' ※本来はファイルリスト自体を渡すより、インデックス管理の方が軽量
' ここではデモとして、Workerに「開始番号」と「終了番号」を渡す設計とする
' 実際には、Dispatcherがファイルパスのリストを一時ファイルに書き出し、
' Workerがそれを読み込む構成が最も堅牢である。
' コマンドライン構築 (cscript.exe を使用)
' 引数: [対象ディレクトリ] [開始インデックス] [終了インデックス]
strCommand = "cscript //nologo """ & strWorkerPath & """ """ & _
strTargetDir & """ " & intStart & " " & intEnd
WScript.Echo "Launching Worker " & (i + 1) & ": Index " & intStart & " to " & intEnd
' Run(strCommand, intWindowStyle, bWaitOnReturn)
' bWaitOnReturn = False により非同期起動を実現
objShell.Run strCommand, 0, False
End If
Next
WScript.Echo "All workers dispatched. Monitor the logs for completion."
' オブジェクトの明示的解放
Set objFolder = Nothing
Set objFSO = Nothing
Set objShell = Nothing
Worker.vbs (実行体)
' ==============================================================================
' Name: Worker.vbs
' Description: 指定されたインデックス範囲のファイルを処理する
' ==============================================================================
Option Explicit
Dim objFSO, objFolder
Dim strTargetDir, intStart, intEnd, i
Dim colFiles, file, currentFileIndex
' 引数の取得
If WScript.Arguments.Count < 3 Then
WScript.Quit 1
End If
strTargetDir = WScript.Arguments(0)
intStart = CInt(WScript.Arguments(1))
intEnd = CInt(WScript.Arguments(2))
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(strTargetDir)
' ファイルリストの再構築(メモリ節約のため、インデックスで制御)
' 注意: 実際の運用では、Dispatcherが生成した「作業用リストファイル」を読み込む方が高速
Dim arrFiles()
Dim colFiles, file, count
Set colFiles = objFolder.Files
count = colFiles.Count
ReDim arrFiles(count - 1)
i = 0
For Each file In colFiles
arrFiles(i) = file.Path
i = i + 1
Next
' --- メイン処理 ---
On Error Resume Next ' エラーハンドリングの開始
For i = intStart To intEnd
If i < count Then
currentFileIndex = i
' 実際のファイル操作ロジック
ProcessFile arrFiles(i)
End If
' エラー発生時のログ出力
If Err.Number <> 0 Then
' ログファイルへの書き出し(実際の現場では重要)
' AppendText "Error at index " & i & ": " & Err.Description
Err.Clear
End If
Next
' --- 終了処理 ---
' オブジェクトの明示的解放(メモリリーク防止の鉄則)
Set objFolder = Nothing
Set objFSO = Nothing
' サブルーチン
Sub ProcessFile(strPath)
' ここに重い処理を記述する
' 例: ファイルのリネーム、移動、属性変更
' objFSO.GetFile(strPath).Attributes = 1 ' ReadOnly
' シミュレーション用のウェイト(実際には不要)
' WScript.Sleep 10
End Sub
Sub AppendText(strMsg)
' 簡易的なログ出力
Dim objLog
Set objLog = objFSO.OpenTextFile("C:\Scripts\worker_log.txt", 8, True)
objLog.WriteLine Now & " : " & strMsg
objLog.Close
Set objLog = Nothing
End Sub
3. 深淵なる知見:パフォーマンスとリソースの最適化
このアーキテクチャを運用するにあたり、シニアエンジニアが留意すべき「極限の知見」をここに記す。
I/OスロットリングとCPUコアの相関
`intMaxProcesses` を無闇に増やすことは、パフォーマンスの低下を招く。ファイル操作のボトルネックは通常、CPUではなくディスクI/Oにある。
- HDD(磁気ディスク)の場合: 並列数を増やすとヘッドのシーク待ち(Seek Time)が激増し、逆に遅くなる。並列数は `2` 程度に抑えるべきだ。
- SSD/NVMeの場合: 高い並列性能を持つ。CPUの物理コア数、あるいはその2倍程度まで増やすことで、I/O待ち時間を有効活用できる。
メモリ管理の規律
VBScriptにおける `Set obj = Nothing` は、単なる作法ではない。COMオブジェクトの参照カウントを適切に管理し、プロセスのメモリフットプリントを最小限に抑えるための生存戦略である。特に、ループ内で `FileSystemObject` や `ADODB.Stream` を生成する場合、明示的な解放を怠れば、Workerプロセスがメモリを食いつぶし、システム全体を不安定にする。
エラーの隔離(Isolation)
マルチプロセス構成の最大の利点は、「一つの失敗が全体を止めない」ことにある。
Workerプロセス内で `On Error Resume Next` を適切に使い、エラーをログに吐き出して処理を継続させる設計にせよ。DispatcherはWorkerの成否を追跡する必要はなく、個々のWorkerが「完遂したか」をログで事後検証するスタイルが、レガシー環境における保守性を最大化する。
結論
VBScriptは、確かに古く、制約の多い言語だ。しかし、その制約を理解し、OSレベルのプロセス管理を組み合わせることで、現代的な並列分散処理を実現することは可能である。
「言語ができないこと」を嘆くのではなく、「アーキテクチャでどう解決するか」を考える。それこそが、我々のようなプロフェッショナルに求められる思考の在り方である。
