【テクニカル・上級編】【上級】ステンシルファイルが破損した際の修復とバックアップ自動生成 – Visio VBA解析バイブル

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

ステンシル破損という静かなる崩壊:Visio VBAにおける堅牢な自己修復アーキテクチャの構築

エンタープライズ環境におけるVisioの自動化は、単なる「図形の描画」ではない。それは、複雑なシステム構成図、ネットワークトポロジ、あるいは高度なプロセスフローを定義する「設計図の生成エンジン」である。

そのエンジンの心臓部を担うのが、ステンシル(.vssx)ファイルだ。

しかし、現場のエンジニアが直面する現実は、美しいコードとは程遠い。ネットワークストレージへの書き込み遅延、不適切なプロセス終了、あるいはOSのファイルシステムレベルでの不整合。これらは、昨日まで完璧に動作していたステンシルを、突如として「読み込み不能なゴミ」へと変貌させる。

一般的な開発者は `On Error Resume Next` でエラーを握りつぶし、あるいは単純な `If Dir(path) <> “”` で存在確認を行うに留まる。だが、それはプロフェッショナルの仕事ではない。ファイルが存在しても、その中身が壊れていれば、COMオブジェクトのロードプロセスは不可解な挙動を示し、最悪の場合、Visioプロセス自体をゾンビ化させる。

本稿では、ステンシルの整合性を検証し、破損時にバックアップから自動復旧させる、極限まで堅牢性を高めたアーキテクチャを解説する。

1. 破損のメカニズムと防御の設計思想

ステンシルファイル(特にOOXML形式の .vssx)は、内部的にはXMLの集合体である。ファイルが完全に書き込まれる前にプロセスが遮断されると、XMLの構造が破壊され、Visioの `Documents.Open` メソッドは、エラーコードすら返さずに低レベルな例外をスローすることがある。

我々が構築すべきは、以下の3層からなる防御アーキテクチャである。

1. L1: Existence & Lock Check(ファイルは存在するか? 他のプロセスが掴んでいないか?)
2. L2: Integrity Validation(ファイルはVisioが認識可能な構造を保っているか?)
3. L3: Automated Recovery(破損時、即座に世代管理されたバックアップへ切り替え可能か?)

2. 実装:堅牢なステンシル・マネージャー

以下に示すのは、Windows APIを用いてファイルロック状態を検知し、破損時に自動復旧を行う高度な制御ロジックを組み込んだモジュールである。

Option Explicit

' --- Windows API Declarations ---
' ファイルのオープン状態を確認し、ロックされているかを判定するために使用
Private Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileA" ( _
    ByVal lpFileName As String, _
    ByVal dwDesiredAccess As Long, _
    ByVal dwShareMode As Long, _
    ByVal lpSecurityAttributes As LongPtr, _
    ByVal dwCreationDisposition As Long, _
    ByVal dwFlagsAndAttributes As Long, _
    ByVal hTemplateFile As LongPtr) As LongPtr

Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long

Private Const GENERIC_READ As Long = &H80000000
Private Const FILE_SHARE_READ As Long = &H1
Private Const FILE_SHARE_WRITE As Long = &H2
Private Const OPEN_EXISTING As Long = 3
Private Const INVALID_HANDLE_VALUE As LongPtr = -1

' --- Constants ---
Private Const ERROR_CORRUPT_STENCIL As Integer = 1001 ' カスタムエラーコード

''' <summary>
''' ステンシルの整合性を検証し、必要に応じて復旧を行う
''' </summary>
''' <param name="targetPath">対象のステンシルパス</param>
Public Sub SecureLoadStencil(ByVal targetPath As String)
    Dim backupPath As String
    Dim doc As Visio.Document
    Dim isValid As Boolean
    
    backupPath = targetPath & ".bak"
    
    On Error GoTo ErrorHandler

    ' 1. ファイルの存在とロック状態のチェック
    If Not FileExists(targetPath) Then
        Err.Raise vbObjectError + Error_FileNotFound, "SecureLoadStencil", "Target file not found."
    End If
    
    If IsFileLocked(targetPath) Then
        Err.Raise vbObjectError + Error_FileLocked, "SecureLoadStencil", "File is currently locked by another process."
    End If

    ' 2. 整合性チェック (実際に開けるか試行)
    isValid = TestStencilIntegrity(targetPath)

    If Not isValid Then
        Debug.Print "Critical: Stencil corruption detected. Initiating recovery..."
        
        ' 3. 復旧プロセス
        If FileExists(backupPath) Then
            Call PerformRecovery(targetPath, backupPath)
            ' 復旧後の再ロード試行
            isValid = TestStencilIntegrity(targetPath)
        Else
            Err.Raise vbObjectError + ERROR_CORRUPT_STENCIL, "SecureLoadStencil", "Stencil corrupted and no backup available."
        End If
    End If

    ' 4. 最終的なロード
    Set doc = Visio.Application.Documents.Open(targetPath)
    Debug.Print "Stencil loaded successfully: " & doc.Name
    
    ' オブジェクトのライフサイクル管理:
    ' ここでdocを戻り値として返す設計にするのが理想的だが、
    ' 今回はデモのためロード完了までを記述
    Exit Sub

ErrorHandler:
    ' 致命的なエラーログの記録(システム管理者向け)
    LogError targetPath, Err.Description
    MsgBox "Critical System Error: " & Err.Description, vbCritical, "Stencil Manager"
End Sub

''' <summary>
''' ステンシルをバックグラウンドで読み込み、構造が壊れていないか検証する
''' </summary>
Private Function TestStencilIntegrity(ByVal path As String) As Boolean
    Dim testDoc As Visio.Document
    On Error Resume Next
    ' 非表示状態で試行することで、ユーザーへの干渉を最小限にする
    Set testDoc = Visio.Application.Documents.Open(path)
    
    If Err.Number = 0 And Not testDoc Is Nothing Then
        testDoc.Close
        TestStencilIntegrity = True
    Else
        TestStencilIntegrity = False
    End If
    On Error GoTo 0
    
    ' メモリ解放の徹底
    Set testDoc = Nothing
End Function

''' <summary>
''' ファイルロックを判定する (Windows API)
''' </summary>
Private Function IsFileLocked(ByVal filePath As String) As Boolean
    Dim hFile As LongPtr
    hFile = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0)
    
    If hFile = INVALID_HANDLE_VALUE Then
        IsFileLocked = True
    Else
        CloseHandle hFile
        IsFileLocked = False
    End If
End Function

''' <summary>
''' バックアップからオリジナルへの上書き復旧
''' </summary>
Private Sub PerformRecovery(ByVal target As String, ByVal backup As String)
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' 既存の破損ファイルを削除(ロックされている場合はここで失敗するため、前段のチェックが重要)
    If fso.FileExists(target) Then fso.DeleteFile target, True
    fso.CopyFile backup, target, True
    
    Set fso = Nothing
    Debug.Print "Recovery completed: " & target
End Sub

Private Function FileExists(ByVal path As String) As Boolean
    FileExists = (Dir(path) <> "")
End Function

Private Sub LogError(ByVal context As String, ByVal msg As String)
    ' 実際の実装では、イベントログやDBへ書き出す
    Debug.Print "[ERROR][" & Now & "][" & context & "] " & msg
End Sub

' カスタムエラー定数
Private Const Error_FileNotFound As Long = 513
Private Const Error_FileLocked As Long = 514

3. 極限の知見:メモリ管理とオブジェクトのライフサイクル

シニアエンジニアがこのコードを見て、次に疑うのは「メモリリークとゾンビプロセス」だ。

COMオブジェクトの明示的解放

VBAにおいて `Set obj = Nothing` は、単なるポインタのクリアではない。それは、参照カウント(Reference Counting)をデクリメントする操作である。特にVisioのような重厚なCOMアプリケーションでは、`Documents.Open` で生成されたオブジェクトが適切に `Close` されず、かつ `Nothing` にされない場合、Visioのプロセス内に「目に見えないドキュメント」が残り続ける。これが蓄積すると、メモリ消費の増大だけでなく、ファイルロックが解除されない原因となる。

私の設計では、`TestStencilIntegrity` 内で必ず `testDoc.Close` と `Set testDoc = Nothing` をセットで行っている。これにより、整合性チェックという「検証作業」が、メインの「業務プロセス」に副作用を与えないことを保証している。

Windows APIによる低レイヤー制御の必要性

なぜ `Dir()` ではなく `CreateFile` API を使うのか?
`Dir()` はファイルが存在するかどうかしか教えない。しかし、エンタープライズ環境では「ファイルは存在するが、ネットワーク越しに別のセッションが書き込み権限を保持している」という状態が頻発する。この状態で `Documents.Open` を強行すると、Visioは不安定な状態に陥る。APIを用いて `GENERIC_READ` でのアクセスを試みることで、ファイルシステムレベルでの真の可用性を判定している。

4. 運用への提言:バックアップ戦略

このコードを機能させるには、運用ルールとしての「バックアップ自動生成」が不可欠である。

単なる手動コピーではない。ステンシルを保存(`doc.Save`)する際に、以下のロジックを組み込んだラッパー関数を用意すべきだ。

1. シャドウコピー方式: 保存の直前に、現在のファイルを `.bak` としてコピーする。
2. 世代管理: `.bak` を一つにするのではなく、タイムスタンプ付き(`stencil_20231027.vssx`)で保持し、破損時のロールバック地点を複数確保する。

結論

Visio VBAにおける「堅牢性」とは、エラーが起きないことを祈ることではない。「エラーが起きることを前提とし、システムが自律的にそのダメージを最小化するように設計すること」である。

APIによる低レイヤーの検証、厳格なオブジェクトのライフサイクル管理、そして自動復旧フロー。これらを統合して初めて、レガシーな環境においても信頼に足る「プロフェッショナルな自動化システム」へと昇華するのだ。

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