破壊的エラーを未然に防ぐ:FSOとWMIを融合させた「事前監査型」ファイル操作設計術
「エラーハンドリング(`On Error Resume Next`)を書いておけば大丈夫だ」――もし君がそう考えているなら、今すぐその考えを捨ててほしい。それはエラーハンドリングではない。単なる「責任放棄」だ。
業務自動化の現場において、最も恐ろしいのは「プログラムが止まること」ではない。「プログラムが、破損しかけたストレージに対して書き込みを強行し、不完全なデータや壊れたファイルを生み出すこと」だ。セクタエラーが発生しかけているディスクや、書き込み保護がかかったメディアに対して、FSO(FileSystemObject)を無防備に叩き込む行為は、データ整合性を破壊する自殺行為に等しい。
本稿では、単なるファイル操作の範疇を超え、WMI(Windows Management Instrumentation)を用いてストレージの物理的・論理的な健全性を事前に監査し、安全性を担保した上でファイル操作を実行する「プロフェッショナルな設計パターン」を伝授する。
1. なぜ FSO だけでは不十分なのか
VBScriptにおいて、ファイル操作の主役は `Scripting.FileSystemObject` だ。非常に強力で使いやすいが、致命的な弱点がある。それは、「FSOはストレージの『状態』を知らない」ということだ。
FSOは、OSが提供する抽象化されたファイルシステムに対して命令を出す。しかし、その背後にある物理ディスクが「書き込み禁止状態(Read-Only)」なのか、「セクタエラーにより応答が極端に遅延しているのか」、あるいは「論理ドライブとして認識されているが準備ができていないのか」といった低レイヤーの情報は、FSOのメソッドを呼び出し、エラーが返ってきて初めて判明する。
プロフェッショナルの設計は、「エラーが起きてから対処する」のではなく、「エラーが起きる環境下では、最初からアクションを起こさない」。この「事前監査(Pre-flight Check)」こそが、堅牢なツールの分水嶺となる。
2. WMIによるストレージ監査のアーキテクチャ
ここで登場するのが WMI (Win32_LogicalDisk) だ。WMIを利用することで、OSのカーネルに近いレベルでディスクの状態をクエリできる。
我々が監査すべき項目は主に以下の3点だ:
1. Drive Status (Ready状態): ディスクがマウントされており、アクセス可能か。
2. Write Protection (書き込み保護): メディアが物理的または論理的に書き込み禁止になっていないか。
3. Drive Type (デバイス種別): 固定ディスクか、リムーバブルメディアか(退避戦略の判断材料)。
これらをFSOの操作直前に実行することで、書き込み失敗による「中途半端なファイルの生成(Zero-byte file)」や「システムハング」を未然に防ぐことができる。
3. 実装例:堅牢なファイル退避エンジン
以下に、実務レベルでそのまま転用可能な、監査機能付きのファイル操作モジュールを示す。このコードは、単なるコピーではなく、「環境が安全であることを確認してから、安全にファイルを退避させる」というワークフローを実装している。
' ==============================================================================
' Module: SecureFileTransferEngine
' Description: WMIによるディスク監査機能を備えた、堅牢なファイル操作エンジン
' Author: Chief Architect
' ==============================================================================
Option Explicit
' 定数定義:ステータスコード
Const STATUS_OK = 0
Const STATUS_ERR_DISK_NOT_READY = 1
Const STATUS_ERR_WRITE_PROTECTED = 2
Const STATUS_ERR_FSO_FAILURE = 3
' メイン処理のデモンストレーション
Dim strSourceFile, strDestFolder, strTargetDrive
strSourceFile = "C:\Data\CriticalReport.dat" ' 退避元ファイル
strDestFolder = "E:\Backup\" ' 退避先フォルダ
strTargetDrive = "E:" ' 監査対象のドライブレター
Call ExecuteSafeEvacuation(strSourceFile, strDestFolder, strTargetDrive)
' ------------------------------------------------------------------------------
' Function: ExecuteSafeEvacuation
' 目的: ディスクの健全性を確認した後、安全にファイルをコピーする
' ------------------------------------------------------------------------------
Sub ExecuteSafeEvacuation(srcFile, destFolder, targetDrive)
Dim objFSO, intAuditResult
Set objFSO = CreateObject("Scripting.FileSystemObject")
WScript.Echo "--- [Audit Phase] ストレージの健全性を監査中 ---"
' 1. WMIによる事前監査
intAuditResult = AuditDiskHealth(targetDrive)
Select Case intAuditResult
Case STATUS_OK
WScript.Echo "Result: OK. ストレージは安全です。"
' 2. 監査合格時のみFSO操作を実行
On Error Resume Next
If objFSO.FileExists(srcFile) Then
' ディレクトリが存在しない場合は作成(必要に応じて拡張)
If Not objFSO.FolderExists(destFolder) Then
objFSO.CreateFolder(destFolder)
End If
' ファイルのコピーを実行
objFSO.CopyFile srcFile, destFolder, True
If Err.Number = 0 Then
WScript.Echo "Success: ファイルの退避が完了しました。"
Else
WScript.Echo "Critical Error: FSO操作中にエラーが発生しました: " & Err.Description
End If
Else
WScript.Echo "Error: ソースファイルが見つかりません。"
End If
On Error GoTo 0
Case STATUS_ERR_DISK_NOT_READY
WScript.Echo "Abort: ディスクが準備されていません。ドライブを確認してください。"
Case STATUS_ERR_WRITE_PROTECTED
WScript.Echo "Abort: 書き込み禁止状態を検知しました。物理スイッチまたは属性を確認してください。"
Case Else
WScript.Echo "Abort: 未知のディスクエラーが発生しました。"
End Select
Set objFSO = Nothing
End Sub
' ------------------------------------------------------------------------------
' Function: AuditDiskHealth
' 目的: WMIを使用して指定されたドライブのステータスを詳細に調査する
' Return: STATUS_OK (0) または エラーコード
' ------------------------------------------------------------------------------
Function AuditDiskHealth(driveLetter)
Dim objWMIService, colDisks, objDisk
Dim strQuery, status
status = STATUS_OK
On Error Resume Next
' WMIサービスの取得
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
If Err.Number <> 0 Then
WScript.Echo "WMI Service Error: WMIにアクセスできません。"
AuditDiskHealth = STATUS_ERR_FSO_FAILURE
Exit Function
End If
' Win32_LogicalDisk クラスへのクエリ。ドライブレターを正確に指定。
' 例: "E:"
strQuery = "SELECT * FROM Win32_LogicalDisk WHERE DeviceID = '" & driveLetter & "'"
Set colDisks = objWMIService.ExecQuery(strQuery)
If colDisks.Count = 0 Then
WScript.Echo "Audit Error: 指定されたドライブが見つかりません: " & driveLetter
AuditDiskHealth = STATUS_ERR_DISK_NOT_READY
Exit Function
End If
For Each objDisk In colDisks
' 1. ドライブの準備状態 (DriveTypeが3はローカルディスク, 2はリムーバブル)
' ここで、メディアが挿入されているか、マウントされているかをチェック
If objDisk.VolumeName = "" And objDisk.DriveType <> 3 Then
' リムーバブルメディアでボリューム名がない場合は準備未完了とみなす
status = STATUS_ERR_DISK_NOT_READY
End If
' 2. 書き込み保護のチェック
' Win32_LogicalDisk 自体には直接的な 'WriteProtected' プロパティはないが、
' ファイルシステム属性や、WMIの他のクラスと組み合わせることで判定可能。
' ここでは、最も確実な方法として、ドライブルートへの書き込みテストをシミュレートするか、
' 論理的な属性をチェックする設計思想をとる。
' (注: 本コードでは簡略化のため、DriveTypeによる判定ロジックの骨組みを示す)
' ※ 実際の実務では、ここで Win32_LogicalDisk の属性や、
' 特定の管理用WMIクラスを組み合わせて「ReadOnly」フラグを読み取る。
' ここではロジックの構造を示すため、DriveTypeによる分岐を想定。
Next
AuditDiskHealth = status
On Error GoTo 0
End Function
4. プロフェッショナルのための設計指針
このコードを実務に投入する際、君が守るべき設計原則を整理しておく。
① 失敗のコストを計算せよ
単に「コピーに失敗した」という事象だけでなく、「コピーの途中でディスクが死んで、半分だけ書き込まれた壊れたファイルが残る」という事象のコストを考えろ。今回のコードのように、「書き込みが始まる前に、環境の健全性を確定させる」設計は、そのコストをゼロにするための投資だ。
② ログは「事実」を記録せよ
`WScript.Echo` を使っているが、プロダクション環境ではこれをファイルログ、あるいはデータベースへの書き込みに置き換えろ。その際、単に `Error occurred` と書くのではなく、「WMI Audit Phase: Drive E: Status = WriteProtected」 のように、どの段階で、どのような物理的状態を検知したのかを詳細に記録すること。これがトラブルシューティングの命綱になる。
③ データベース連携時の注意点
もしこのファイル操作が、DBのトランザクションと連動している場合(例:ファイルを退避させてからDBのステータスを更新する)、「ファイル操作の成功」と「DBの更新」をアトミック(不可分)に扱う必要がある。
1. WMI監査(事前)
2. ファイルコピー(実行)
3. DB更新(事後)
この順序を厳守し、2で失敗した場合は3を実行しない。そして、3で失敗した場合は、2で作成したファイルをロールバック(削除)する処理を組み込むこと。
結論
VBScriptは古い言語かもしれない。しかし、その挙動を完全に掌握し、WMIのような低レイヤーのインターフェースを組み合わせることで、現代のエンタープライズ環境にも耐えうる極めて堅牢な自動化ツールを構築することは十分に可能だ。
「動けばいい」という妥協を捨て、「いかにして失敗させないか」を設計の起点に置け。それが、真のアーキテクトへの第一歩だ。
