【実務・中級編】【実務中級者向け】ネットワーク共有フォルダの接続切れをFSOで検知しローカル代替フォルダへ自動退避するフォールバック処理 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

VBScriptにおける「止まらない」設計論:ネットワーク切断を予見したフォールバック・アーキテクチャ

業務自動化の現場において、スクリプトが「動く」ことと「運用に耐えうる」ことの間には、深くて暗い溝がある。

多くの開発者が、UNCパス(`\\Server\Share`)や割り当てられたネットワークドライブを対象にスクリプトを書き、ネットワークの瞬断やVPNの切断によってスクリプトが例外を吐いて停止するか、あるいは「エラーは無視するがファイルは書き込まれていない」という、最悪のサイレント・フェイル(静かなる失敗)を引き起こす。

本稿では、ファイルシステムオブジェクト(FSO)を駆使し、ネットワークの不安定さを前提とした「フォールバック(代替手段への切り替え)処理」の実装手法を伝授する。これは単なるエラーハンドリングではない。システムの可用性を担保するための設計思想である。

1. なぜ、あなたのスクリプトは「現場」で死ぬのか

中級者が陥りがちな罠は、`On Error Resume Next` を魔法の杖だと勘違いすることだ。

ネットワーク共有フォルダへのアクセスは、ローカルディスクへのアクセスとは比較にならないほど「不確実」だ。タイムアウト、セッション切れ、権限の剥奪。これらの事象が発生した際、単にエラーをスキップするだけのコードは、データの整合性を破壊する。

避けるべき「素人」の設計

1. エラーを握りつぶす: `On Error Resume Next` でエラーを無視し、ファイルが書き込めなかったまま後続の処理(DB更新など)に進んでしまう。
2. パスの直撃: ネットワークが生きていることを確認せずに、いきなり巨大なファイルの書き込みを開始する。これにより、スクリプトがネットワークタイムアウト待ちで数分間フリーズする。
3. ドライブレターへの依存: `Z:\` のような割り当てドライブは、ユーザーセッションによって存在しないことが多々ある。常にUNCパスを基準に設計すべきだ。

プロが取るべき「堅牢な」設計

1. Connectivity Probe(接続確認): 書き込み前に、ターゲットの親ディレクトリの存在を確認する。
2. Fallback Mechanism(退避戦略): ネットワークが死んでいたら、即座にローカルの「一時待避領域」へパスを切り替える。
3. Atomic Write(原子的な書き込み): 書き込み中に切断されることを想定し、一度 `.tmp` ファイルとして書き出し、完了後にリネームする。

2. 実装:フォールバック機能を備えた堅牢なファイル出力モジュール

以下に、プロダクション環境でそのまま転用できるレベルのコード例を示す。このコードの肝は、「パスの解決」と「書き込みの試行」を分離し、失敗時に自動的に代替ルートへ回す構造にある。

' **
' Module: RobustFileHandler
' Description: ネットワーク切断を検知し、ローカルへ自動退避するファイル操作モジュール
' Author: Chief Architect
' **

Option Explicit

' --- 定数定義 ---
Const PATH_PRIMARY   = "\\Server01\Shared\Data\"  ' 優先:ネットワーク共有パス
Const PATH_SECONDARY = "C:\AppData\Fallback\"     ' 代替:ローカル待避領域
Const LOG_FILE       = "C:\AppData\Logs\system.log"

Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

' --- メイン処理実行例 ---
Call Main()

Sub Main()
    Dim targetFileName, content, targetPath
    targetFileName = "DailyReport_" & Year(Date) & Month(Date) & Day(Date) & ".txt"
    content = "This is a critical business data."

    ' 1. 適切な出力先パスを取得(フォールバックロジックの核心)
    targetPath = GetValidOutputPath(PATH_PRIMARY, PATH_SECONDARY)

    ' 2. ファイル書き込み実行
    If SaveFileWithRetry(targetPath & targetFileName, content) Then
        WScript.Echo "Success: File saved to " & targetPath
    Else
        WScript.Echo "Critical Error: Failed to save file to both Primary and Secondary paths."
        ' ここで管理者へのメール通知や、イベントログへの書き込みを行う
    End If
End Sub

' **
' 関数: GetValidOutputPath
' 目的: ネットワークパスが利用可能か確認し、不可ならローカルパスを返す
' **
Function GetValidOutputPath(primary, secondary)
    On Error Resume Next
    
    ' ネットワークパスの存在確認(フォルダ単位でチェック)
    If fso.FolderExists(primary) Then
        GetValidOutputPath = primary
    Else
        ' ログに警告を記録
        LogMessage "WARNING: Primary path unreachable. Switching to fallback: " & secondary
        
        ' セカンダリ(ローカル)パスの存在確認と作成
        If Not fso.FolderExists(secondary) Then
            ' フォルダ作成を試みる(権限エラーに注意)
            CreateFolderRecursive secondary
        End If
        GetValidOutputPath = secondary
    End If
    
    If Err.Number <> 0 Then
        LogMessage "ERROR: Path resolution failed: " & Err.Description
        GetValidOutputPath = secondary ' 最終手段としてローカルへ
        Err.Clear
    End If
    On Error GoTo 0
End Function

' **
' 関数: SaveFileWithRetry
' 目的: 書き込みの原子性を確保し、失敗時にエラーを返す
' **
Function SaveFileWithRetry(fullPath, content)
    Dim ts, tmpPath
    tmpPath = fullPath & ".tmp"
    SaveFileWithRetry = False
    
    On Error Resume Next
    
    ' 1. 一時ファイルとして書き込み(書き込み途中の切断による破損を防ぐ)
    Set ts = fso.CreateTextFile(tmpPath, True)
    If Err.Number <> 0 Then
        LogMessage "ERROR: Failed to create temp file: " & Err.Description
        Err.Clear
        Exit Function
    End If
    
    ts.Write content
    ts.Close
    
    ' 2. 書き込み成功後、本来のファイル名にリネーム(Atomic操作)
    ' すでに同名ファイルがある場合は削除してからリネーム
    If fso.FileExists(fullPath) Then fso.DeleteFile(fullPath, True)
    fso.MoveFile tmpPath, fullPath
    
    If Err.Number = 0 Then
        SaveFileWithRetry = True
    Else
        LogMessage "ERROR: Failed to finalize file: " & Err.Description
        ' 残骸(tmp)を掃除
        If fso.FileExists(tmpPath) Then fso.DeleteFile(tmpPath)
        Err.Clear
    End If
    
    On Error GoTo 0
End Function

' --- 補助関数: 再帰的なフォルダ作成 ---
Sub CreateFolderRecursive(path)
    Dim parts, i, currentPath
    parts = Split(path, "\")
    currentPath = parts(0)
    For i = 1 To UBound(parts)
        If parts(i) <> "" Then
            currentPath = currentPath & "\" & parts(i)
            If Not fso.FolderExists(currentPath) Then fso.CreateFolder(currentPath)
        End If
    Next
End Sub

' --- 補助関数: ログ出力 ---
Sub LogMessage(msg)
    On Error Resume Next
    Dim ts
    ' ログディレクトリがなければ作成
    Dim logDir : logDir = fso.GetParentFolderName(LOG_FILE)
    If Not fso.FolderExists(logDir) Then CreateFolderRecursive logDir
    
    Set ts = fso.OpenTextFile(LOG_FILE, 8, True) ' 8 = ForAppending
    ts.WriteLine "[" & Now & "] " & msg
    ts.Close
    On Error GoTo 0
End Sub

3. アーキテクトの視点:運用フェーズでの注意点

このスクリプトを実戦投入する際、エンジニアが考慮すべき「設計の重み」が3点ある。

① データの「漂流」をどう回収するか

フォールバックによってローカルに保存されたファイルは、いわば「漂流したデータ」である。
ネットワークが復旧した際、これらをどのように本来の場所へ戻すか(リトライトライアル)の設計がセットで必要だ。

  • 対策: ファイル名に `_FALLBACK_` というプレフィックスを付与するか、特定のサブフォルダに隔離することで、後続の回収バッチが検知しやすくする。

② データベースとの整合性(Single Source of Truth)

ファイルだけをローカルに逃がして、DBには「共有フォルダに保存完了」と記録してはいけない。これはデータの不整合を招く致命的なミスだ。

  • 設計指針: DBのレコードには、「実際に保存されたパス」を記録せよ。
  • `file_path`: `\\Server01\Shared\Data\report.txt`
  • `is_fallback`: `False`
  • (フォールバック時)
  • `file_path`: `C:\AppData\Fallback\report.txt`
  • `is_fallback`: `True`

③ FSOの限界を知る

FSOは強力だが、ネットワークのタイムアウト制御(Timeout設定)を細かく行う機能を持っていない。
もし、ネットワークの応答が極端に遅い環境で、スクリプトが長時間固まることを避けたい場合は、VBScript単体ではなく、`WshShell.Run` を用いて外部コマンド(`robocopy` 等)を呼び出す、あるいは PowerShell をラップして呼び出す構成を検討すべきだ。

まとめ

堅牢な自動化ツールとは、「正常系」を記述する時間よりも、「異常系」をどう制御するかを考える時間に重きを置いたものである。

ネットワークの切断を「エラー」として扱うのではなく、「起こりうる状態」として設計に組み込む。この一歩が、あなたの作るシステムの信頼性を、単なるスクリプトから「プロフェッショナルな業務基盤」へと昇華させるのである。

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