【実務中級者向け】ドライブレターに依存しない!GetDriveとVolumeNameで実現する堅牢なUSB自動バックアップの極意
開発現場でよくある失敗談からはじめよう。
「毎朝、社内システムのデータを外付けUSBメモリにバックアップするVBScriptを書いた。`E:\Backup\` にファイルをコピーするように設定し、タスクスケジューラーで自動化も完璧——」
だが、ある日突然、現場から悲鳴が上がる。
「バックアップが失敗しています! エラーコード:経路が見つかりません」
原因は何か? 別なUSB機器を挿したことで、あるいはWindowsの仕様変更によって、その日の朝だけターゲットのUSBメモリのドライブレターが `E:` から `F:` に変わってしまっていたのだ。
固定的なドライブレター(`C:` や `D:`)に依存する設計は、インフラの変動に対してあまりにも脆い。レガシーなVBScriptであっても、プロフェッショナルが書くコードは「環境の変化を動的に吸収する」堅牢性を備えていなければならない。
今回は、FileSystemObject(FSO)の `GetDrive` メソッドと `VolumeName` プロパティを極限まで活用し、「特定のボリューム名を持つUSBメモリ」を動的に特定して安全にファイルを退避させるプロダクションコードを伝授する。
—
1. なぜ固定ドライブレターのコードは実務で破綻するのか?
多くの初級〜中級プログラマは、ドライブの存在確認を以下のように実装しがちだ。
‘ 【アンチパターン】絶対マネしてはいけない脆弱なコード
Dim fso, targetPath
Set fso = CreateObject(“Scripting.FileSystemObject”)
targetPath = “E:\MyBackupFolder\”
If fso.FolderExists(targetPath) Then
‘ コピー処理…
End If
このアプローチの致命的な欠陥は以下の通りだ。
1. ドライブレターの流動性: WindowsはUSBデバイスが接続される空きドライブの中で最も若いアルゴリズムを動的に割り当てる。他の外付けHDDやカードリーダーの抜き挿しで一発で変わる。
2. 誤爆のリスク: もし別人のUSBメモリが偶然 `E:` ドライブとして認識された場合、意図しない外部メディアへ機密データやバックアップが書き出されてしまうセキュリティ上の重大なリスク(情報漏洩の温床)になる。
解決策:ボリューム名(ラベル)によるアイデンティティの確立
USBメモリの「ドライブレター」は変動するが、管理者が付与した「ボリューム名(例: `CORP_BACKUP`)」は変わらない。Windows APIの抽象化レイヤーであるFSOを正しく叩けば、このボリューム名をスマートに取得し、完全な一致検証を行うことができる。
—
2. アーキテクチャ設計:堅牢なUSB特定アルゴリズム
今回構築するスクリプトのライフサイクルと判定ロジックは以下のステップで流れる。
1. FSOのインスタンス化: プロセス生存期間中は適切にオブジェクトを維持し、処理終了時に破棄。
2. 全ドライブの列挙 (`fso.Drives`): システムに接続されているすべてのドライブ(HDD, CD-ROM, USB, ネットワーク)のコレクションを取得。
3. ドライブタイプのフィルタリング (`DriveType`): リムーバブルメディア(DriveType = 1)にターゲットを絞り込み、パフォーマンスを最適化。
4. 準備状態の確認 (`IsReady`): メディアが挿入されていないドライブやアクセス中のドライブへの無駄なアクセスエラー(トラップ)を未然に防止。
5. ボリューム名の一致検証 (`VolumeName`): 目的のラベル名と一致した瞬間にそのドライブレターを動的に確定。
—
3. 【プロダクションコード】コピペで使える最強のバックアップスクリプト
以下のコードは、エラーハンドリング(`On Error Resume Next` の正しい封じ込め)とログ出力、そして厳格なボリューム名チェックを実装した実務投入可能なスクリプトだ。
‘ ==============================================================================
‘ Script Name : SecureUSBBackup.vbs
‘ Description : 指定したボリューム名を持つUSBメモリを動的に検出し、
‘ ローカルの特定フォルダからファイルを安全に退避させる。
‘ Author : Chief Architect
‘ ==============================================================================
Option Explicit
‘ — 定数定義 —
Const TARGET_VOLUME_NAME = “CORP_BACKUP” ‘ 探索するUSBメモリのボリューム名(ラベル)
Const SOURCE_DIR = “C:\AppWork\Data” ‘ 退避元ローカルフォルダ
Const BACKUP_SUBDIR = “\DailyBackup” ‘ USB内の保存先サブフォルダ
Const LOG_FILE_PATH = “C:\AppWork\Logs\backup_execution.log”
Sub Main()
Dim fso, targetDriveLetter, sourcePath, destPath
Set fso = CreateObject(“Scripting.FileSystemObject”)
Call WriteLog(“=== バックアップ処理開始 ===”)
‘ 1. ボリューム名からUSBのドライブレターを動的に特定
targetDriveLetter = GetDriveLetterByVolume(fso, TARGET_VOLUME_NAME)
If targetDriveLetter = “” Then
Call WriteLog(“エラー: ターゲットボリューム ‘” & TARGET_VOLUME_NAME & “‘ を持つUSBメモリが見つかりません。”)
Set fso = Nothing
Exit Sub
End If
Call WriteLog(“ターゲットUSB検出: ドライブ [” & targetDriveLetter & “:]”)
‘ 2. 退避元・退避先のパス構築
sourcePath = SOURCE_DIR
destPath = targetDriveLetter & “:” & BACKUP_SUBDIR
‘ 3. 退避元存在チェック
If Not fso.FolderExists(sourcePath) Then
Call WriteLog(“エラー: 退避元フォルダが存在しません -> ” & sourcePath)
Set fso = Nothing
Exit Sub
End If
‘ 4. USB側の保存先フォルダが存在しなければ作成
If Not fso.FolderExists(destPath) Then
On Error Resume Next
fso.CreateFolder(destPath)
If Err.Number <> 0 Then
Call WriteLog(“エラー: USB側の保存先フォルダ作成に失敗しました (” & Err.Description & “)”)
On Error GoTo 0
Set fso = Nothing
Exit Sub
End If
On Error GoTo 0
Call WriteLog(“保存先フォルダを作成しました -> ” & destPath)
End If
‘ 5. ファイルのコピー実行(上書き許可: True)
On Error Resume Next
‘ フォルダ内の全ファイルを対象(ワイルドカード)
fso.CopyFile sourcePath & “\.”, destPath & “\”, True
If Err.Number <> 0 Then
Call WriteLog(“エラー: ファイルコピー中に異常が発生しました (” & Err.Description & “)”)
Err.Clear
Else
Call WriteLog(“成功: ファイルの退避が完了しました -> ” & destPath)
End If
On Error GoTo 0
Call WriteLog(“=== バックアップ処理終了 ===”)
Set fso = Nothing
End Sub
‘ ——————————————————————————
‘ 外部関数: ボリューム名からドライブレターを逆引きする
‘ ——————————————————————————
Function GetDriveLetterByVolume(fso, volumeName)
Dim d, drives
GetDriveLetterByVolume = “” ‘ 初期値は空文字列
Set drives = fso.Drives
For Each d in drives
‘ DriveType = 1 はリムーバブルディスク(USBメモリ等)
If d.DriveType = 1 Then
‘ IsReady が True の場合のみ VolumeName にアクセス可能(さもないとエラーになる)
If d.IsReady Then
‘ 大文字小文字を区別せずに比較
If UCase(Trim(d.VolumeName)) = UCase(Trim(volumeName)) Then
GetDriveLetterByVolume = d.DriveLetter
Exit Function
End If
End If
End If
Next
End Function
‘ ——————————————————————————
‘ 簡易ロガー関数
‘ ——————————————————————————
Sub WriteLog(message)
Dim fso, logFile, formattedMsg
Set fso = CreateObject(“Scripting.FileSystemObject”)
formattedMsg = “[” & Now & “] ” & message
WScript.Echo formattedMsg ‘ コンソール実行時の確認用
‘ ログフォルダの自動生成配慮
Dim logDir: logDir = fso.GetParentFolderName(LOG_FILE_PATH)
If Not fso.FolderExists(logDir) Then
fso.CreateFolder(logDir)
End If
‘ 追記モードでログ出力 (ForAppending = 8, Create = True = -1)
Set logFile = fso.OpenTextFile(LOG_FILE_PATH, 8, True)
logFile.WriteLine formattedMsg
logFile.Close
Set logFile = Nothing
Set fso = Nothing
End Sub
‘ メイン処理のキック
Call Main()
—
4. プロジェクトアーキテクトが教える「現場でハマる罠」と回避策
このスクリプトを運用に乗せる際、実務上遭遇するいくつかの「罠」と、その回避策を共有しておく。
① `d.IsReady` チェックを省いてはいけない理由
リムーバブルメディア(カードリーダーの空スロットや、反応が遅いUSBメモリ)に対して、準備ができていない状態(`IsReady = False`)で `VolumeName` や `DriveLetter` プロパティを叩くと、「デバイスの準備ができていません (Error 71)」 という致命的なランタイムエラーが発生し、スクリプトが即座に強制終了する。
FSOを扱う際は、常に「物理的なハードウェア状態が整っているか」をワンクッション挟むのが鉄則だ。
② 大文字小文字・前後の空白への配慮
Windowsのボリューム名はユーザーが手動で変更することが多く、意図せず末尾にスペースが入ったり (`”CORP_BACKUP “`)、大文字と小文字が混ざることがある。
上記コード内で `UCase(Trim(d.VolumeName))` を用いているのは、こうしたヒューマンエラーによる「一致判定漏れ」を完全に防ぐための防衛的プログラミングの定石である。
③ セキュリティと実行権限
社内PCのタスクスケジューラーから実行する場合、実行アカウントの権限不足によりUSBへの書き込み権限が弾かれるケースがある。また、昨今のセキュリティ製品(EDRやUSB制御ソフト)は、VBScriptからの突然のリムーバブルメディアへの一括書き込みを「ランサムウェアの挙動」と誤検知してブロックすることがある。本番導入前に必ずテスト環境でホワイトリスト登録などの検証を行うこと。
—
総括
VBScriptはレガシーな技術と揶揄されることもあるが、OSの根幹に直接アプローチできるFSOのポテンシャルは、現代の業務自動化においても依然として強力無比である。
「固定値に頼らず、状態を動的に看破して処理を決定する」
このオブジェクト指向的かつインフラ耐性の高い設計思想を取り入れるだけで、あなたの書く自動化スクリプトの品質はプロフェッショナルの領域へと劇的に飛躍する。現場の運用負荷をゼロにする堅牢なツールを、ぜひあなたの手で組み上げてほしい。
