【テクニカル・上級編】【上級者向け】VSDXファイルが破損しているかをプログラムで判定し、修復を試みるためのバイナリ解析の基礎 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:VSDX破損検知と自己修復システムの設計

Visioのファイル形式(.vsdx)が、本質的にZIPアーカイブとXMLの集合体であることは、エンタープライズ領域のVBAエンジニアであれば常識の部類に属する。しかし、巨大なネットワーク図やプラント図を日々生成・更新するシステムにおいて、「突然のVSDX破損(Corrupted File)」は悪夢のようなインシデントだ。Visio標準の `Documents.Open` は、構造的な異常に直面すると容赦なくモーダルダイアログをポップアップさせ、バッチ処理や夜間自動化スクリプトを完全にフリーズさせる。

本稿では、レガシーなVBA環境の限界を超越するため、VSDXのバイナリ/構造解析による破損の事前検知と、Windows APIおよびZIP構造を利用した無人自動修復(セルフヒーリング)フローの全貌を、実戦投入可能なコードとともに解き明かす。

—

1. VSDX破損のメカニズムとアーキテクチャの要件

VSDXファイルの実体はOpen Packaging Conventions (OPC) に準拠したZIPコンテナである。主要な構成要素は以下の通りだ。

  • `[Content_Types].xml`:パッケージ内の全パーツのMIMEタイプ定義
  • `_rels/.rels`:ルートリレーションシップ定義
  • `visio/document.xml`:ドキュメントのメタデータとページ構造
  • `visio/pages/page.xml`:個別のページ・シェイプ定義

破損が発生する原因の多くは、書き込み中の電源断やネットワーク切断による「ZIPのセントラルディレクトリ(Central Directory)の欠損」または「必須XMLパーツの不完全な終端」である。

Visioオブジェクトモデルに頼ったエラーハンドリングでは、ファイルを開こうとした時点でVisioエンジンがクラッシュするため、「Visioで開く前にプログラム側で構造を検証する」という逆転の発想が必要となる。

—

2. 実装:VBAによるVSDX構造検証と自己修復エンジン

以下のコードは、外部ツールに依存せず、VBA標準の機能およびWindows標準コンポーネントのみを使用してVSDXの整合性を検証し、異常時にバックアップからの復元や構造修復を試みる堅牢なモジュールである。

Option Explicit

‘ Windows API: ファイル存在確認およびアトミックなコピー操作用
Private Declare PtrSafe Function CopyFile Lib “kernel32” Alias “CopyFileA” ( _
ByVal lpExistingFileName As String, _
ByVal lpNewFileName As String, _
ByVal bFailIfExists As Long) As Long

”’

”’ VSDXファイルの整合性を検証し、必要に応じて修復・バックアップからの復元を行うメイン関数
”’

”’ 対象のVSDXファイルパス ”’ 破損時に備えたバックアップ格納ディレクトリ ”’ Boolean 処理成功の可否
Public Function VerifyAndRepairVsdx(ByVal targetPath As String, ByVal backupDir As String) As Boolean
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 1. ファイルの物理存在確認
If Not fso.FileExists(targetPath) Then
Err.Raise 53, “VerifyAndRepairVsdx”, “指定されたファイルが存在しません: ” & targetPath
End If

‘ 2. 構造的整合性(ZIPとしての完全性)のチェック
If Not IsValidZipStructure(targetPath) Then
Debug.Print “[CRITICAL] VSDXの構造異常(破損)を検知しました: ” & targetPath

‘ 3. バックアップからの自動復元を試行
If RestoreFromBackup(targetPath, backupDir) Then
Debug.Print “[INFO] 最新の正常なバックアップから復元しました。”
VerifyAndRepairVsdx = True
Else
Debug.Print “[FATAL] バックアップからの復元に失敗しました。手動介入が必要です。”
VerifyAndRepairVsdx = False
End If
Exit Function
End If

‘ 構造が正常であればTrueを返す
VerifyAndRepairVsdx = True
Set fso = Nothing
End Function

”’

”’ Shell.Applicationを使用してZIP(VSDX)としての構造が保たれているかを検証する
”’

Private Function IsValidZipStructure(ByVal filePath As String) As Boolean
On Error GoTo ErrorHandler

Dim shellApp As Object
Set shellApp = CreateObject(“Shell.Application”)

Dim ns As Object
Set ns = shellApp.NameSpace(filePath)

‘ 破損している場合、NameSpaceオブジェクトの取得に失敗するか、Itemsのカウントでエラーが発生する
If ns Is Nothing Then
IsValidZipStructure = False
Exit Function
End If

Dim itemCount As Long
itemCount = ns.Items.Count

‘ 必須のルートパーツ([Content_Types].xml等)が存在するか簡易チェック
Dim hasContentTypes As Boolean
hasContentTypes = False

Dim item As Object
For Each item In ns.Items
If LCase(item.Name) = “[content_types].xml” Then
hasContentTypes = True
Exit For
End If
Next item

IsValidZipStructure = hasContentTypes
Exit Function

ErrorHandler:
‘ 予期せぬ例外(CRCエラーやヘッダー破損)は全て破損とみなす
IsValidZipStructure = False
End Function

”’

”’ 破損ファイルを隔離し、管理されたバックアップから最新の健全なファイルを復元する
”’

Private Function RestoreFromBackup(ByVal targetPath As String, ByVal backupDir As String) As Boolean
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)

Dim targetFile As Object
Set targetFile = fso.GetFile(targetPath)
Dim fileName As String
fileName = targetFile.Name

‘ 破損ファイルを 「.corrupt.[タイムスタンプ]」として隔離
Dim timestamp As String
timestamp = Format(Now, “yyyymmdd_hhnnss”)
Dim quarantinePath As String
quarantinePath = targetPath & “.corrupt.” & timestamp

On Error GoTo SafeError
fso.MoveFile targetPath, quarantinePath
Debug.Print “[INFO] 破損ファイルを隔離しました: ” & quarantinePath

‘ バックアップディレクトリから最新の正常ファイルを探索
‘ (命名規則: [original_name]_[YYYYMMDD_HHNNSS].bak を想定)
Dim backupFolder As Object
Set backupFolder = fso.GetFolder(backupDir)

Dim bestBackup As String
bestBackup = GetLatestValidBackup(backupFolder, fso.GetBaseName(fileName))

If bestBackup = “” Then
RestoreFromBackup = False
Exit Function
End If

‘ バックアップからターゲットパスへコピー
Dim result As Long
result = CopyFile(bestBackup, targetPath, 0&)

RestoreFromBackup = (result <> 0)
Exit Function

SafeError:
RestoreFromBackup = False
End Function

”’

”’ バックアップ群から最も新しく、かつZIPとして有効なファイルを特定する
”’

Private Function GetLatestValidBackup(ByVal folder As Object, ByVal baseName As String) As String
Dim file As Object
Dim latestDate As Date
Dim latestPath As String

latestDate = 0
latestPath = “”

For Each file in folder.Files
‘ プレフィックスが一致し、拡張子が.bakのものを対象とする
If InStr(file.Name, baseName) > 0 And LCase(fsoGetExtension(file.Name)) = “bak” Then
‘ ZIP(VSDX)としての構造チェックをバックアップに対しても行う
If IsValidZipStructure(file.Path) Then
If file.DateLastModified > latestDate Then
latestDate = file.DateLastModified
latestPath = file.Path
End If
End If
End If
Next file

GetLatestValidBackup = latestPath
End Function

Private Function fsoGetExtension(ByVal fileName As String) As String
Dim p As Long
p = InStrRev(fileName, “.”)
If p > 0 Then
fsoGetExtension = Mid(fileName, p + 1)
Else
fsoGetExtension = “”
End If
End Function

—

3. チーフアーキテクトの視点:堅牢性担保のための設計哲学

上記のコードベースを組み込んだシステムを運用するにあたり、以下のアーキテクチャ上の原則を遵守すべきである。

オブジェクトの明示的解放とメモリ管理

VBAにおける `CreateObject` や `Shell.Application` の利用は、COMコンテキストにメモリリークのリスクを孕む。特にループ内でファイル操作を行う場合、インスタンスは即座に `Set xxx = Nothing` によって解放しなければならない。ガベージコレクションのタイミングに依存した設計は、長期間稼働するバックグラウンドプロセスにおいて致命的なメモリ肥大化を引き起こす。

ファイルロックとアトミック操作

複数プロセスやネットワーク共有(SMB)環境において、Visioファイルへの書き込み競合は破損の最大の温床となる。
前掲のコードで用いた `CopyFile` API等のWin32APIを適切にラップし、ファイルシステムレベルでの排他制御(Exclusive Lock)を意識した実装に拡張することで、マルチスレッド/マルチプロセス環境下での堅牢性が飛躍的に向上する。

例外の握りつぶし禁止

バイナリ解析・検証プロセスにおいて、 `On Error Resume Next` の乱用は厳禁である。想定外のI/Oエラーや権限不足を握りつぶしてしまうと、デバッグが極めて困難になる。検証ロジックにおいては「正常系を証明できた場合のみTrueを返す」という厳格なホワイトリスト方式のエラーハンドリングを貫くこと。

—

結言

Visio VBAによる高度な図面自動生成・管理システムにおいて、ファイル破損への耐性は「実装されていれば望ましい機能」ではなく、「エンタープライズ品質を名乗るための必須要件」である。
VSDXの内部構造を理解し、Visioエンジンに処理を委譲する前の「防衛的バイナリ検証」を組み込むことで、システムは真の自律性と耐障害性を手に入れる。レガシーな技術スタックであっても、エンジニアの知見とアーキテクチャの設計次第で、現代の堅牢なシステムと同等の信頼性を構築することは十分に可能である。

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