【極限の知見】FSOとWScript.Shellを統合する:堅牢なディスク監視システムの設計思想
業務自動化の現場において、「動けばいい」という思考は、後に必ず致命的な負債となって返ってくる。
特に、システム管理者や運用担当者が常用する「監視スクリプト」においては、その傾向が顕著だ。エラーハンドリングを怠ったスクリプトは、異常事態に沈黙し、あるいはエラーの連鎖によってシステムリソースを食いつぶす。
今回、我々が扱うテーマは「FSO(FileSystemObject)を用いたディスク残容量の監視」と「WScript.Shellによるデスクトップ通知」の連携だ。これは一見、初歩的なスクリプトに見える。しかし、これを「プロダクション環境に耐えうる設計」へと昇華させるには、オブジェクトのライフサイクル、例外処理、そして「非対話型セッション」という実務上の罠を理解していなければならない。
本稿では、単なるコードの提示ではなく、なぜその設計が必要なのか、というアーキテクチャの視点から解説していく。
1. 設計における「3つの急所」
実装に入る前に、設計者が意識すべき急所を整理する。中級者への入り口は、ここにある。
① ゼロ除算と型変換の罠
ディスク容量の割合を算出する際、`FreeSpace / TotalSize` という計算を行う。しかし、もし対象のドライブが論理的なエラーや特殊なマウント状態にあり、`TotalSize` が `0` を返した場合、スクリプトは即座にランタイムエラーで停止する。これは、自動実行タスクにおいて「監視が止まる」という最悪の結果を招く。
② WScript.Shell.Popup の「ブロッキング」問題
`WScript.Shell.Popup` メソッドは、ユーザーがボタンを押すか、タイムアウトするまでスクリプトの実行を停止(ブロッキング)させる。もし、タスクスケジューラから「ユーザーがログオンしているかどうかにかかわらず実行する」設定で起動した場合、このポップアップはユーザーの画面に表示されず、バックグラウンドでプロセスが永久に残り続ける「ゴーストプロセス」を生む原因となる。
③ ログの永続化
デスクトップ通知は「その場」の警告には有効だが、事後調査には役に立たない。「いつ、どのドライブが、どの閾値を超えたのか」を、外部ファイル(ログ)に書き出す設計が不可欠である。
2. プロダクション・グレードの設計図
我々が構築するのは、以下のコンポーネントを持つ構造化されたスクリプトである。
1. Configuration Layer: 閾値や監視対象ドライブを定数として定義。
2. Logic Layer: FSOを用いて容量計算を行い、判定を下す。
3. Notification Layer: 条件合致時にPopupおよびログ出力を行う。
4. Error Handling Layer: 予期せぬエラーを捕捉し、ログに記録して安全に終了する。
3. 実装コード:DiskMonitorPro.vbs
以下に、実務でそのまま利用可能な、堅牢性を極限まで高めたコードを示す。
' ==============================================================================
' Script Name: DiskMonitorPro.vbs
' Description: FSOを用いたディスク空き容量監視および警告通知スクリプト
' Author: Chief Architect
' ==============================================================================
Option Explicit
' --- Configuration Layer (設定値) ---
Const TARGET_DRIVE = "C:" ' 監視対象ドライブ
Const THRESHOLD_PERCENT = 15 ' 警告を出す空き容量の閾値 (%)
Const LOG_FILE_PATH = "C:\Logs\DiskMonitor.log" ' ログ出力先
Const POPUP_TIMEOUT = 30 ' ポップアップの自動閉鎖時間 (秒)
Const POPUP_TITLE = "【警告】ディスク容量不足"
' --- Global Objects ---
Dim objFSO, objShell, objLogFile
' ==============================================================================
' Main Execution Logic
' ==============================================================================
Sub Main()
On Error Resume Next ' エラーハンドリングの開始
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("WScript.Shell")
' 1. ドライブの存在確認
If Not objFSO.DriveExists(TARGET_DRIVE) Then
WriteLog "ERROR: Target drive " & TARGET_DRIVE & " not found."
Exit Sub
End If
Dim objDrive
Set objDrive = objFSO.GetDrive(TARGET_DRIVE)
' 2. 容量計算 (ゼロ除算防止策)
Dim totalSize, freeSpace, percentFree
totalSize = objDrive.TotalSize
freeSpace = objDrive.FreeSpace
If totalSize = 0 Then
WriteLog "ERROR: TotalSize is 0 for drive " & TARGET_DRIVE
Exit Sub
End If
percentFree = (freeSpace / totalSize) * 100
' 3. 判定ロジック
If percentFree < THRESHOLD_PERCENT Then
' 閾値未満の場合、警告を実行
Dim msg
msg = "警告: ドライブ " & TARGET_DRIVE & " の空き容量が減少しています。" & vbCrLf & _
"現在残量: " & Round(percentFree, 2) & "%" & vbCrLf & _
"設定閾値: " & THRESHOLD_PERCENT & "%"
NotifyAdmin msg
WriteLog "WARNING: Threshold breached. Current: " & Round(percentFree, 2) & "%"
Else
' 正常時は詳細ログを必要としない(運用負荷軽減のため)
' 必要に応じて WriteLog "INFO: Capacity OK." を追加
End If
' エラーチェック
If Err.Number <> 0 Then
WriteLog "CRITICAL ERROR: " & Err.Description
Err.Clear
End If
' クリーンアップ
Set objDrive = Nothing
Set objFSO = Nothing
Set objShell = Nothing
End Sub
' ==============================================================================
' Notification Layer (通知処理)
' ==============================================================================
Sub NotifyAdmin(strMessage)
' Popup(Text, SecondsToWait, Title, Type)
' Type 48 = Exclamation Icon, Type 1 = OK Button
On Error Resume Next
objShell.Popup strMessage, POPUP_TIMEOUT, POPUP_TITLE, 48
If Err.Number <> 0 Then
WriteLog "ERROR: Failed to display Popup. " & Err.Description
End If
On Error GoTo 0
End Sub
' ==============================================================================
' Logging Layer (ログ出力処理)
' ==============================================================================
Sub WriteLog(strText)
On Error Resume Next
' ログディレクトリの存在確認と作成
Dim logDir
logDir = objFSO.GetParentFolderName(LOG_FILE_PATH)
If Not objFSO.FolderExists(logDir) Then
' フォルダ作成が失敗する場合は、権限不足などの可能性がある
' 本来はここでエラー通知を行うべきだが、ログ自体が書けないため標準出力等へ
Exit Sub
End If
' 追記モードでファイルを開く
Set objLogFile = objFSO.OpenTextFile(LOG_FILE_PATH, 8, True)
objLogFile.WriteLine "[" & Now & "] " & strText
objLogFile.Close
Set objLogFile = Nothing
On Error GoTo 0
End Sub
' --- Entry Point ---
Main
4. アーキテクトによるコード解説:なぜこれが「強い」のか
堅牢なエラーハンドリングの階層化
このスクリプトでは、`On Error Resume Next` を乱用していません。`Main` 関数全体を包むことで、予期せぬランタイムエラーをキャッチし、それを「黙殺」するのではなく、`WriteLog` を通じて「記録」するように設計しています。これが、運用フェーズにおける「原因究明の速さ」に直結します。
ゼロ除算と存在確認のガード句
`If totalSize = 0` や `If Not objFSO.DriveExists` といった「ガード句(Guard Clauses)」を、ロジックの最上流に配置しています。これにより、不正な状態のまま計算処理へ進むことを防ぎ、不整合なデータによる二次災害を回避しています。
Popupメソッドの「安全性」
`objShell.Popup` には必ず `POPUP_TIMEOUT` を指定しています。これは、管理者が不在、あるいは通知に気づかなかった場合でも、スクリプトがプロセスとして残り続けるリスクを最小化するための「保険」です。
ログの設計思想
`WriteLog` 関数では、ログディレクトリが存在しない場合のハンドリングを含めています。自動化ツールにおいて、ログ出力先への書き込み権限不足は頻発する問題です。このスクリプトは、エラーが発生した際、その理由(`Err.Description`)をログに刻み込むことで、エンジニアが「何が起きたか」を即座に把握できるようにしています。
5. 実務への適用アドバイス
このスクリプトをタスクスケジューラに登録する際は、以下の点に留意してください。
1. 実行権限: ログファイルへの書き込み、およびドライブ情報の取得には、適切な権限が必要です。「最上位の特権で実行する」にチェックを入れることを検討してください。
2. 対話型セッションの考慮: 前述の通り、デスクトップ通知は「ユーザーがログオンしている状態」でないと目に見えません。サーバー運用においては、通知(Popup)に頼りすぎず、ログを監視する別の仕組み(SIEMや監視ツール)と組み合わせるのがプロの設計です。
優れたエンジニアは、コードを書くこと以上に、「コードが失敗したときに何が起きるか」を設計します。このスクリプトはその思想を具現化したものです。
