はじめに:Visio最大の悲劇「ファイルが壊れました」に立ち向かう
こんにちは!業務自動化の現場で、Visioと日々向き合っているエンジニアの先輩です。
業務フロー図、ネットワーク構成図、オフィスレイアウトなど、Visioは極めて重要なドキュメントを担うことが多いですよね。そんなVisioをマクロで自動化し、大量のファイルをバッチ処理しているとき、背筋が凍るような事態に遭遇したことはありませんか?
そう、「ファイルが壊れて開けなくなる(破損)」という恐怖です。
マクロの記録から一歩踏み出し、大量の図面を自動生成・一括保存するようになると、不意のプロセス強制終了やネットワークの瞬断などによって、ファイルが中途半端に書き込まれてしまうリスクが跳ね上がります。
今回は、「壊れてから泣く」のを完全に卒業しましょう。
VSDXの内部構造(バイナリ・コンテナ仕様)の基本をわかりやすく紐解きながら、「保存前の安全な世代バックアップ」と「破損検知時の自動復旧(リストア)」をVBAで完結させるプロクオリティの実装パターンをステップバイステップで解説します。
ここをクリアすれば、ただ動くだけではない「絶対に落ちない堅牢なVisio自動化システム」が作れるようになりますよ!
—
そもそもVSDXファイルの中身はどうなっているの?
まず、敵を知ることから始めましょう。普段私たちが目にする `.vsdx` ファイル。あれは一体何者なのでしょうか?
実は、近代のOfficeドキュメント(.docxや.xlsxと同様)である `.vsdx` は、「XMLファイル群をZIP形式で固めたパッケージ(Open Packaging Conventions: OPC仕様)」なのです。
VSDXの内部イメージ
[ Sample.vsdx ] ← 実体はZIPアーカイブ!
│
├── [Content_Types].xml (ファイル構成の定義書)
├── _rels/ (関連付け情報)
└── visio/
├── document.xml (ドキュメント全体の設定)
├── pages/
│ ├── page1.xml (図面ページの実体・シェイプ情報)
│ └── …
└── masters/ (ステンシル・マスタシェイプ情報)
拡張子を `.vsdx` から `.zip` に変えて解凍ソフトで展開すると、上記のようにフォルダとXMLファイルが綺麗に出てきます。
なぜファイルは壊れるのか?
Visioが図面を保存するとき、内部では膨大なXMLの書き出しとZIP圧縮処理が走っています。
マクロ実行中にVisioがクラッシュしたり、保存中にPCの電源が落ちたりすると、ZIPのヘッダー情報(バイナリの末尾にある目録データ)が壊れたり、XMLの終了タグが閉じられない状態になります。
これが起きると、Visioは「このファイルは破損しているため開けません」と匙を投げてしまうわけですね。
だからこそ、現場では次の3つの防壁をプログラムで構築する必要があります。
1. 事前バックアップ(世代管理):上書き保存する前に、必ず直前の正常な状態を退避させる。
2. ZIP整合性の簡易検証:ファイルを開く前に、最低限ファイル構造が壊れていないかをチェックする。
3. 自動ロールバック(リストア):開く処理がエラーになったら、ユーザーに気づかせずバックアップから即座に復元する。
—
実践コード:破損を防ぎ自動復旧する「堅牢なファイル管理クラス」
それでは、現場でそのまま使える実戦的なVBAコードを見てみましょう。
初心者の方でも構造が理解しやすいよう、今回は標準モジュールにコピペしてすぐ動かせる形でまとめました。
実装の全体像
以下のコードは、指定したVSDXファイルを安全に開き、編集後に世代バックアップを取りながら安全に保存する一連のライフサイクルを自動化します。
Option Explicit
‘ ==============================================================================
‘ モジュール名: mod_RobustVisioIO
‘ 概要: VSDXファイルの破損を未然に防ぎ、エラー時に自動復旧する安全制御モジュール
‘ ==============================================================================
‘ 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フルパス
”’
Public Function SafeOpenDocument(ByVal targetFilePath As String) As Visio.Document
Dim visDoc As Visio.Document
Dim backupPath As String
backupPath = targetFilePath & “.bak”
‘ 1. そもそもファイルが存在しない場合のガード
If Dir(targetFilePath) = “” Then
MsgBox “指定されたファイルが存在しません: ” & vbCrLf & targetFilePath, vbExclamation, “確認”
Set SafeOpenDocument = Nothing
Exit Function
End If
‘ 2. バイナリの簡易チェック(ZIPシグネチャの存在確認)
If Not IsValidZipHeader(targetFilePath) Then
Debug.Print “[警告] ZIPヘッダーが破損している可能性があります: ” & targetFilePath
‘ ヘッダーが壊れているなら、開く処理の前にバックアップからの復旧を試みる
If RestoreFromBackup(targetFilePath, backupPath) Then
Debug.Print “[情報] バックアップからリストアに成功しました。”
Else
MsgBox “ファイルが物理的に破損しており、有効なバックアップもありません。”, vbCritical, “復旧不能”
Set SafeOpenDocument = Nothing
Exit Function
End If
End If
‘ 3. エラーハンドリングを有効にして開く
On Error Resume Next
‘ Visioのアラート(ダイアログ)を一時的に抑止し、プログラムで制御する
Dim prevAlertResponse As Integer
prevAlertResponse = Application.AlertResponse
Application.AlertResponse = 7 ‘ IDNO: 警告ダイアログが出た場合は「いいえ/キャンセル」を自動応答
Set visDoc = Application.Documents.Open(targetFilePath)
‘ アラート設定を元に戻す
Application.AlertResponse = prevAlertResponse
‘ 開く処理でエラーが発生したか確認
If Err.Number <> 0 Or visDoc Is Nothing Then
Debug.Print “[エラー検知] 文書を開く際に失敗: ” & Err.Description
Err.Clear
On Error GoTo 0
‘ 開く処理に失敗した場合も、バックアップ復旧を試行
If RestoreFromBackup(targetFilePath, backupPath) Then
On Error Resume Next
Set visDoc = Application.Documents.Open(targetFilePath)
On Error GoTo 0
If visDoc Is Nothing Then
MsgBox “リストア後もファイルを開けませんでした。”, vbCritical, “致命的エラー”
Else
MsgBox “ファイルをバックアップから正常に自動復旧しました!”, vbInformation, “自動復旧完了”
End If
Else
MsgBox “ファイルを開けず、復旧用バックアップも存在しませんでした。”, vbCritical, “中断”
End If
Else
On Error GoTo 0
Debug.Print “[成功] 正常にドキュメントが開かれました: ” & visDoc.Name
End If
Set SafeOpenDocument = visDoc
End Function
”’
”’
Public Function SafeSaveDocument(ByRef doc As Visio.Document) As Boolean
If doc Is Nothing Then Exit Function
Dim originalPath As String
originalPath = doc.FullName
Dim backupPath As String
backupPath = originalPath & “.bak”
On Error GoTo ErrorHandler
‘ まだ一度も名前を付けて保存されていない新規ファイルは通常保存
If InStr(originalPath, “\”) = 0 Then
doc.Save
SafeSaveDocument = True
Exit Function
End If
‘ 1. 現在の正常な状態をバックアップ(既存の.bakがあれば上書き)
‘ ※Windows APIのCopyFileを使用することで高速かつバイナリロックの影響を最小化
If Dir(originalPath) <> “” Then
CopyFile originalPath, backupPath, 0
End If
‘ 2. 保存処理本体
‘ Visio内部のキャッシュフラッシュを確実にするため、明示的にSaveをコール
doc.Save
‘ 3. 保存後の整合性を即時チェック
If Not IsValidZipHeader(originalPath) Then
Err.Raise vbObjectError + 514, “SafeSaveDocument”, “保存後のバイナリ検証に失敗しました。”
End If
SafeSaveDocument = True
Debug.Print “[成功] バックアップ作成および安全な保存が完了しました。”
Exit Function
ErrorHandler:
SafeSaveDocument = False
Debug.Print “[致命的エラー] 保存中にエラーが発生しました: ” & Err.Description
‘ 万が一保存に失敗しファイルが壊れた場合は、直前のバックアップで上書き復旧
Call RestoreFromBackup(originalPath, backupPath)
MsgBox “保存中に問題が発生したため、直前のバックアップ状態に戻しました。” & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “保存ロールバック”
End Function
‘ ——————————————————————————
‘ 内部補助関数
‘ ——————————————————————————
”’
”’
Private Function IsValidZipHeader(ByVal filePath As String) As Boolean
Dim fileNum As Integer
Dim byte1 As Byte, byte2 As Byte
IsValidZipHeader = False
If Dir(filePath) = “” Then Exit Function
‘ ファイルサイズが極端に小さい(ZIPとして成立しない)場合は除外
If FileLen(filePath) < 4 Then Exit Function
On Error GoTo CleanUp
fileNum = FreeFile
Open filePath For Binary Access Read As #fileNum
Get #fileNum, 1, byte1
Get #fileNum, 2, byte2
Close #fileNum
' ZIP書式のマジックナンバー: 0x50 ("P"), 0x4B ("K")
If byte1 = &H50 And byte2 = &H4B Then
IsValidZipHeader = True
End If
Exit Function
CleanUp:
If fileNum > 0 Then Close #fileNum
IsValidZipHeader = False
End Function
”’
”’
Private Function RestoreFromBackup(ByVal targetPath As String, ByVal backupPath As String) As Boolean
RestoreFromBackup = False
If Dir(backupPath) = “” Then
Debug.Print “[復旧失敗] バックアップファイルが存在しません: ” & backupPath
Exit Function
End If
‘ バックアップ自体の健全性もチェック
If Not IsValidZipHeader(backupPath) Then
Debug.Print “[復旧失敗] バックアップファイル自体が壊れています。”
Exit Function
End If
On Error Resume Next
‘ 元ファイルを削除してバックアップをコピー
If Dir(targetPath) <> “” Then Kill targetPath
CopyFile backupPath, targetPath, 0
If Err.Number = 0 Then
RestoreFromBackup = True
Else
Err.Clear
End If
On Error GoTo 0
End Function
—
実際に動かしてみよう!使い方テストコード
上記で作った堅牢な関数群を呼び出すサンプルです。
「開いて、少し図面を編集して、安全に保存して閉じる」という、日常で一番よくあるパターンですね。
Public Sub TestRobustProcess()
Dim samplePath As String
samplePath = ThisDocument.Path & “\ネットワーク構成図_本番.vsdx”
‘ 1. 安全にファイルを開く(破損していたら自動で復旧)
Dim targetDoc As Visio.Document
Set targetDoc = SafeOpenDocument(samplePath)
If targetDoc Is Nothing Then
MsgBox “処理を中止します。”, vbExclamation, “中断”
Exit Sub
End If
‘ 2. 何かしらの図面処理を行う(例: ページ1にテキストを追加)
Dim pg As Visio.Page
Set pg = targetDoc.Pages(1)
Dim shp As Visio.Shape
Set shp = pg.DrawRectangle(1, 1, 3, 2)
shp.Text = “自動更新: ” & Now()
‘ 3. バックアップを確保しながら安全に保存する
If SafeSaveDocument(targetDoc) Then
Debug.Print “すべての処理が安全に完了しました。”
Else
Debug.Print “保存処理に失敗しました。”
End If
‘ 4. ドキュメントを閉じる
targetDoc.Close
Set targetDoc = Nothing
End Sub
—
ここで差がつく!陥りやすい罠と3つの設計ポイント
1. 「マジックナンバー」を活用した超高速プリチェック
ファイルが壊れているかどうかを、わざわざ重たいVisioの `Documents.Open` を走らせる前に見極めるのが、今回の `IsValidZipHeader` です。
VSDXファイルは、先頭の2バイトが必ず「`P`」と「`K`」(ASCIIコードで `0x50`, `0x4B`。ZIP形式の開発者 Phil Katz 氏のイニシャル)になっています。
もし書き込み途中でクラッシュして0バイトになっていたり、中身がブランク(NULL文字)で埋まっていたりすると、このシグネチャが消失します。
これをVisioに渡す前にわずか数ミリ秒で検知できるため、Visio本体が無応答(フリーズ)になる事故を最小限に防げるのです。
2. ダイアログの「勝手なポップアップ」を遮断する
ファイルが軽微に破損しているとき、Visioは「修復を試みますか?」「ドキュメントを開けません」というモーダルダイアログ(ボタンを押すまで止まる画面)を出して処理を停止させることがあります。
Application.AlertResponse = 7 ‘ 自動的に「いいえ/キャンセル」を返してダイアログを止める
夜間バッチ処理などでこれが止まってしまうと朝まで処理が進みません。上記のように `AlertResponse` をセットしておくことで、プログラム側にエラー制御の主導権を引き戻すことができます。
3. オブジェクトの「参照の掴みっぱなし」に注意
Visio VBAを触る初学者の方が最も引っかかるのが、「ファイルを閉じたはずなのに、裏でVisioが掴んでいてバックアップのコピーや削除ができない(Permission Denied)」という現象です。
- ドキュメントを閉じたら `Set targetDoc = Nothing` でポインタを解放する
- ページ(`Page`)やシェイプ(`Shape`)の参照変数も使い終わったらクリアする
この「後片付け」を意識するだけで、ファイルの入出力エラーは劇的に減少しますよ。
—
まとめ:信頼される自動化プログラムを目指して
マクロの記録からスタートした頃は、「動けばいいや!」と嬉しくなるものですよね。
しかし、自動化が会社のコア業務になればなるほど、「万が一途中でエラーが起きても、データだけは絶対に失われない設計」が求められるようになります。
今回学んだ要点は以下の3つです。
1. VSDXはZIPコンテナである(先頭2バイト `PK` の意味を知る)
2. 上書きする前にバックアップを世代管理する(転ばぬ先の杖)
3. オープン失敗・保存失敗を想定したロールバックを用意する
このパターンを一度身につけてしまえば、VisioだけでなくExcelやWordのファイルIO処理でも一目置かれる「プロの書き方」ができるようになります。
ぜひご自身の現場のツールに組み込んで、絶対に壊れない安心な自動化システムを作り上げてくださいね!
