【実務・中級編】【ファイル操作の基本】FileSystemObject (FSO) を使ったファイル・フォルダの存在確認と安全な作成・削除 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【VBScript極限攻略】FileSystemObjectを完全掌握する:安全なファイル・フォルダ操作と堅牢なエラー回避設計

Windowsインフラやレガシーシステムの業務自動化において、VBScript(Windows Script Host: WSH)はいまだに現役で稼働し続けています。しかし、現場で目にするスクリプトの多くは、単に「動けばいい」というレベルで書かれており、例外処理の欠如、リソースのリーク、不適切なオブジェクトハンドリングによって、本番環境で予期せぬ障害を引き起こしています。

素人が書く「動くだけのスクリプト」と、プロが書く「絶対に止まらない堅牢なコード」を分かつ最大の境界線。それこそが `Scripting.FileSystemObject` (FSO) の完璧な理解とハンドリング です。

本記事では、単なるリファレンスの引き写しではなく、アーキテクトの視点からFSOのライフサイクル、パフォーマンスの重み、そして実務でそのまま使えるプロダクションレベルのコードパターンを伝授します。

1. FileSystemObjectの構造と COM オブジェクトの真実

まず、VBScriptにおけるFSOの本質を理解してください。FSOはVBScriptの組み込み機能ではなく、`scrrun.dll`(Microsoft Scripting Runtime)によって提供されるCOM(Component Object Model)オブジェクトです。

Set fso = CreateObject(“Scripting.FileSystemObject”)

この一行が実行された瞬間、OSレベルでCOMコンポーネントがロードされ、メモリが割り当てられます。ここで理解すべき重要原則が3つあります。

鉄則1: インスタンス生成のオーバーヘッドを最小化せよ

ループ処理の中で `CreateObject(“Scripting.FileSystemObject”)` を何度も呼び出すようなコードは設計上の悪手です。COMオブジェクトの生成コストはVBScriptの内部演算に比べて非常に重いため、FSOインスタンスはスクリプトのスコープ内で生成・再利用し、処理終了時に解放するのが基本です。

鉄則2: 明示的なリソース解放(`Set object = Nothing`)

VBScriptにはガベージコレクションが存在しますが、WSH環境での参照カウントの開放は不安定な側面を持ちます。使用済みのオブジェクト変数は、必ず明示的に `Nothing` を代入して参照を破棄してください。

鉄則3: パス表現の「揺らぎ」を吸収せよ

絶対パス、相対パス、UNCパス(`\\server\share`)、そして末尾のバックスラッシュ(`\`)の有無。これらを曖昧にしたまま文字列結合でパスを生成すると、バグの温床になります。パスの結合にはFSOのメソッド(`BuildPath`)を使用するか、正規化されたパス文字列を保証する関数を噛ませるべきです。

2. 破綻しない設計「存在チェック&安全な操作」の鉄則

FSO操作で最も頻繁に発生する例外は以下の3点です。

1. アクセス拒否 (Permission Denied / Error: 70): 権限不足、またはファイルが別プロセスでロックされている。
2. ファイル/フォルダが見つからない (File/Path Not Found / Error: 53 or 76): 存在しないパスへの操作、または親ディレクトリが存在しない状態でのファイル作成。
3. 既に存在する (File Already Exists): 上書き不可オプション時の不適切な作成。

これらを防止するためには、「操作の前に必ず存在を確認する(Look Before You Leap)」、および「多層防御(Nested Exception Handling)」を徹底します。

階層構造作成の罠:`CreateFolder` の限界

FSOの `CreateFolder` メソッドは、中間ディレクトリを再帰的に作成してくれません
例えば、`C:\Data` が存在しない状態で `C:\Data\Logs\2026` を作成しようとすると、エラー番号76「パスが見つかりません」で即座にクラッシュします。実務では、パスを分解して親フォルダから順番に存在チェックを行い、順次作成していく再帰的(またはループによる)ディレクトリ作成関数が必須となります。

3. 【完全保存版】プロダクション環境に耐えうる堅牢コード

以下に、実務でそのままコピー&ペーストして使用できる、極めて堅牢なファイル・フォルダ操作モジュールを示します。

コードの設計思想

  • `Option Explicit` の徹底: 変数の暗黙の宣言を排除し、タイポによるバグを根絶。
  • カプセル化: 存在チェック、再帰的フォルダ作成、安全なファイル削除・作成を独立した関数として実装。
  • 局所的なエラーハンドリング: `On Error Resume Next` をスクリプト全体にかけるのは悪手。エラー制御は「例外が発生し得る最小限の行」に限定し、直後に `Err.Number` を評価してクリア(`Err.Clear`)する。

‘ ==============================================================================
‘ [モジュール名] SafeFileOperations.vbs
‘ [概要] FileSystemObjectを利用した堅牢なファイル・フォルダ操作スクリプト
‘ [設計原則] 堅牢性、再帰的リソース確保、局所的エラーハンドリング、明示的リソース解放
‘ ==============================================================================
Option Explicit

‘ 実行メイン処理
Call Main()

Sub Main()
‘ FSOのインスタンス生成(スクリプト全体で一度だけ行う)
Dim fso
Set fso = CreateObject(“Scripting.FileSystemObject”)

On Error Resume Next

‘ テスト用のターゲットパス定義
Dim targetDir, targetFile
targetDir = “C:\AppLogs\Production\Batch\Work”
targetFile = fso.BuildPath(targetDir, “Execution_” & Year(Now) & Month(Now) & Day(Now) & “.log”)

WScript.Echo “[INFO] 処理を開始します…”

‘ 1. 安全なフォルダ階層の作成(親フォルダがなくても再帰的に作成)
If EnsureDirectoryExists(fso, targetDir) Then
WScript.Echo “[SUCCESS] ディレクトリが確保されました: ” & targetDir
Else
WScript.Echo “[ERROR] ディレクトリの作成に失敗しました: ” & targetDir
Set fso = Nothing
WScript.Quit 1
End If

‘ 2. 既存ファイルの安全な削除(読み取り専用属性も考慮して強制削除)
If fso.FileExists(targetFile) Then
WScript.Echo “[INFO] 既存のファイルを検出しました。安全に削除します…”
If SafeDeleteFile(fso, targetFile, True) Then
WScript.Echo “[SUCCESS] 既存ファイルを削除しました。”
Else
WScript.Echo “[ERROR] ファイルの削除に失敗しました(ロックされている可能性があります)。”
Set fso = Nothing
WScript.Quit 1
End If
End If

‘ 3. 安全なファイル作成と書き込み
Dim textStream
Set textStream = SafeCreateTextFile(fso, targetFile, True, False) ‘ Overwrite=True, Unicode=False(ASCII/ANSI)

If Not textStream Is Nothing Then
textStream.WriteLine “[” & Now & “] バッチ処理を開始しました。”
textStream.WriteLine “[” & Now & “] データベース連携モジュールを呼び出し中…”
textStream.WriteLine “[” & Now & “] 処理が正常に完了しました。”

‘ ストリームは即座にクローズする(リソース保持時間を最少化)
textStream.Close()
Set textStream = Nothing
WScript.Echo “[SUCCESS] ファイルにログを出力しました: ” & targetFile
Else
WScript.Echo “[ERROR] ファイルの作成に失敗しました。”
Set fso = Nothing
WScript.Quit 1
End If

‘ リソースの明示的解放
Set fso = Nothing
WScript.Echo “[INFO] すべての処理が正常に完了しました。”
End Sub

‘ ==============================================================================
‘ [関数名] EnsureDirectoryExists
‘ [概要] 指定されたフォルダパスが存在しない場合、親階層から再帰的に作成する
‘ [引数] fso : FileSystemObject インスタンス
‘ path : 作成したいフォルダのフルパス
‘ [戻り値] Boolean : 成功時 True, 失敗時 False
‘ ==============================================================================
Function EnsureDirectoryExists(ByRef fso, ByVal path)
EnsureDirectoryExists = False

‘ 空文字チェック
If Trim(path) = “” Then Exit Function

‘ 既に存在していればTrueを返して終了
If fso.FolderExists(path) Then
EnsureDirectoryExists = True
Exit Function
End If

‘ 親フォルダのパスを取得
Dim parentPath
parentPath = fso.GetParentFolderName(path)

‘ 親フォルダが存在せず、かつ親パスが空でなければ、再帰呼び出しで親を作成
If parentPath <> “” And Not fso.FolderExists(parentPath) Then
If Not EnsureDirectoryExists(fso, parentPath) Then
Exit Function ‘ 親の作成に失敗した場合は中断
End If
End If

‘ 局所的エラーハンドリングを開始
On Error Resume Next
fso.CreateFolder(path)
If Err.Number = 0 Then
EnsureDirectoryExists = True
Else
‘ エラー情報をデバッグログ用に出力(実務ではログファイル等へ)
WScript.Echo “[EXCEPT] Folder Create Failed (” & Err.Number & “: ” & Err.Description & “) -> ” & path
Err.Clear
End If
On Error GoTo 0
End Function

‘ ==============================================================================
‘ [関数名] SafeDeleteFile
‘ [概要] 指定されたファイルを安全に削除する(属性解除対応)
‘ [引数] fso : FileSystemObject インスタンス
‘ filePath : 削除対象ファイルのフルパス
‘ force : Trueの場合、読み取り専用ファイルも強制削除
‘ [戻り値] Boolean : 成功時 True, 失敗時 False
‘ ==============================================================================
Function SafeDeleteFile(ByRef fso, ByVal filePath, ByVal force)
SafeDeleteFile = False

If Not fso.FileExists(filePath) Then
SafeDeleteFile = True ‘ そもそも存在しなければ削除成功とみなす
Exit Function
End If

On Error Resume Next

‘ 強制削除フラグが立っている場合、属性を標準(0)に戻す補正を実施
If force Then
Dim fileObj
Set fileObj = fso.GetFile(filePath)
If (fileObj.Attributes And 1) = 1 Then ‘ 1 = ReadOnly
fileObj.Attributes = fileObj.Attributes – 1
End If
Set fileObj = Nothing
End If

‘ 削除実行
fso.DeleteFile filePath, force

If Err.Number = 0 Then
SafeDeleteFile = True
Else
WScript.Echo “[EXCEPT] File Delete Failed (” & Err.Number & “: ” & Err.Description & “) -> ” & filePath
Err.Clear
End If
On Error GoTo 0
End Function

‘ ==============================================================================
‘ [関数名] SafeCreateTextFile
‘ [概要] 指定されたパスに安全にテキストファイルを作成し、TextStreamを返す
‘ [引数] fso : FileSystemObject インスタンス
‘ filePath : 作成するファイルのフルパス
‘ overwrite : 上書きを許可するか (True/False)
‘ isUnicode : Unicode形式で作成するか (True=Unicode, False=ASCII)
‘ [戻り値] TextStream オブジェクト(失敗時は Nothing)
‘ ==============================================================================
Function SafeCreateTextFile(ByRef fso, ByVal filePath, ByVal overwrite, ByVal isUnicode)
Set SafeCreateTextFile = Nothing

On Error Resume Next

‘ 親ディレクトリの存在を事前に絶対保証する
Dim parentDir
parentDir = fso.GetParentFolderName(filePath)
If Not fso.FolderExists(parentDir) Then
If Not EnsureDirectoryExists(fso, parentDir) Then
On Error GoTo 0
Exit Function
End If
End If

‘ ファイル作成
Dim stream
Set stream = fso.CreateTextFile(filePath, overwrite, isUnicode)

If Err.Number = 0 Then
Set SafeCreateTextFile = stream
Else
WScript.Echo “[EXCEPT] File Creation Failed (” & Err.Number & “: ” & Err.Description & “) -> ” & filePath
Err.Clear
End If
On Error GoTo 0
End Function

4. 業務自動化における罠とパフォーマンス最適化

上記のコードパターンを実装することに加え、データベース連携や大規模ネットワーク環境でFSOを使用する際には、以下のアーキテクチャ上の注意点が存在します。

① ネットワーク共有(UNCパス)における遅延トラップ

`fso.FileExists(“\\Server\Share\file.csv”)` や `fso.FolderExists` は、ネットワーク遅延(レイテンシ)の影響をダイレクトに受けます。特に、存在しないSMBパスに対して `Exists` を呼び出すと、OSのタイムアウト待ちが発生し、スクリプトが数十秒間フリーズする現象(ブロッキング)が起こり得ます。

  • 対策: ネットワークドライブ上のファイルを頻繁にチェックする場合は、1回のセッションで存在チェックを済ませるか、ローカルに一度コピーしてから処理を行う設計に切り替えてください。

② 文字コードの「UTF-8問題」

FSOの `CreateTextFile` や `OpenTextFile` が扱える文字コードは、実質的に ASCII (ANSI / Shift-JIS) または UTF-16 (LE) のみです。
現代のWebAPI連携やデータベースのCSVエクスポートで標準となっている 「BOMなし UTF-8」をFSOで扱うことは不可能 です。

  • 対策: BOMなしUTF-8のファイルを読み書きする必要がある場合は、FSOではなく `ADODB.Stream` オブジェクトを採用してください。FSOでUTF-8を無理やり扱おうとすると、文字化けや不細工なBOM(`EF BB BF`)の付与によるシステム連携エラーを引き起こします。

③ ファイルロック(競合)とRetryパターン

データベースのインポート処理や、他プロセスが書き込み中のログファイルをFSOで開こうとすると、即座に Error 70 (`Permission Denied`) が発生します。

堅牢なシステムを作るなら、以下のような 「リトライ・ループ」 を組み込むのがプロの設計です。

‘ ファイルの排他ロック解除を待つリトライパターンの例
Dim attempts, maxAttempts, isOpened, stream
attempts = 0
maxAttempts = 5
isOpened = False

Do While attempts < maxAttempts And Not isOpened On Error Resume Next ' 開く試み(ForAppending = 8) Set stream = fso.OpenTextFile(targetFile, 8, False) If Err.Number = 0 Then isOpened = True Else Err.Clear attempts = attempts + 1 WScript.Sleep(1000) ' 1秒待機して再試行 End If On Error GoTo 0 Loop If Not isOpened Then WScript.Echo "[FATAL] ファイルが開けませんでした。プロセスがロックされています。" End If ---

5. まとめ:チーフアーキテクトからの助言

VBScriptは古くから存在する言語ですが、だからこそ書く人間の「設計思想の差」が如実に現れる言語でもあります。

  • `On Error Resume Next` でエラーを揉み消して進むだけのコードは、障害発生時にデータを破損させる悪魔のスクリプトです。
  • COMオブジェクトの構造を理解し、明示的なリソース管理(`Nothing` の代入)を行う。
  • 親ディレクトリの存在保証、属性解除、文字コード選択、排他制御のリトライまで考慮して初めて「プロの現場で耐えうるWSHツール」となります。

本記事で提示したモジュールおよび設計パターンを標準化し、あなたのプロジェクトにおける自動化ツールの堅牢性を圧倒的なレベルまで引き上げてください。

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