【実務・中級編】【上級者】ネットワーク経由でのPDF一括エクスポート時、一時的な回線切断やパケットロス発生時にトランザクションを安全にロールバックし、不完全な成果物を自動削除するレジリエント制御マクロ – PowerPoint VBA解析バイブル

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

ネットワークの不確実性を制御せよ:PowerPoint VBAにおける「トランザクション型」PDFエクスポートの実装術

業務自動化の現場において、多くのエンジニアが陥る致命的な罠がある。それは、「正常系(Happy Path)のコードしか書いていない」ことだ。

「ファイルを開き、PDFとして保存し、次のファイルへ移る」——。
ローカル環境でテストしていれば、この単純なループで完璧に動作するだろう。しかし、ターゲットが不安定なVPN越し、あるいは高負荷な社内共有ネットワーク(SMB)上のディレクトリになった瞬間、そのコードは「ゴミ(破損ファイル)」を量産する破壊兵器へと変貌する。

ネットワークのパケットロス、瞬断、あるいはセッションのタイムアウト。これらが発生した際、PowerPointの `ExportAsFixedFormat` メソッドは、中途半端なサイズで書き込みを停止したり、0バイトの空ファイルを生成したりする。これらは、後続のプロセス(自動メール送信やアーカイブ処理)において、システム全体の連鎖的なエラーを引き起こす「静かな時限爆弾」となる。

本稿では、単なる「エラー回避」を超えた、「トランザクション(原子性)の概念」をVBAに持ち込む、レジリエント(回復力のある)な設計手法を伝授する。

1. なぜ「直接書き込み」は悪なのか:設計思想の転換

まず、初心者が書くコードと、プロフェッショナルが設計するコードの決定的な違いを理解せよ。

アンチパターン:直接ネットワーク書き込み

' ❌ 危険な設計:ネットワークへの直接出力
For Each ppt In Presentations
    ppt.ExportAsFixedFormat Path:="\\Server\Share\Output\" & ppt.Name & ".pdf", FixedFormatType:=ppFixedFormatTypePDF
Next ppt

このコードには、「書き込みの失敗」を考慮したロールバック機構が存在しない。 通信が途切れた瞬間、ネットワーク上の共有フォルダには「壊れたPDF」が残る。この破損ファイルは、後続の処理が「ファイルが存在する」と判定して処理を進めてしまうため、検知が極めて困難なのだ。

プロフェッショナルな設計:ローカル・ステージング・パターン

堅牢なシステムを構築する場合、以下の「3フェーズ・トランザクション」を採用すべきである。

1. Stage (ローカル保存): まず、ネットワークではなく、確実に通信が保証されているローカルのTempディレクトリにPDFを出力する。
2. Verify (整合性検証): ローカルに生成されたファイルが、期待通りのサイズ、かつ正常に書き込まれたかを検証する。
3. Commit (確定移動): 検証をパスしたファイルのみを、一括でネットワーク上の最終目的地へ「移動(Move)」させる。

この「Move」操作は、OSレベルでアトミック(不可分)に近い挙動を示す。これにより、「中途半端なファイルがネットワーク上に残る」という事態を物理的に防ぐことができる。

2. 実装:レジリエントなPDFエクスポート・エンジン

以下に、実務のプロダクション環境でそのまま使用できるレベルの、堅牢な制御ロジックを示す。ここでは `FileSystemObject` を活用し、エラーハンドリングとクリーンアップを徹底している。

Option Explicit

' ==============================================================================
' プロジェクト: Resilient PDF Exporter
' 機能: ネットワーク経由のPDF一括エクスポートにおけるトランザクション制御
' 設計: Local Staging -> Verification -> Network Commit (Rollback enabled)
' ==============================================================================

Sub ExecuteResilientExport()
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' --- 設定領域 ---
    Dim targetFolder As String: targetFolder = "\\Server\Share\Target\PDF_Output\" ' ネットワークパス
    Dim localTempFolder As String: localTempFolder = Environ("TEMP") & "\PPT_Export_Stage\"
    ' ----------------
    
    ' 1. 準備フェーズ: ローカルステージング領域の確保
    On Error Resume Next
    If Not fso.FolderExists(localTempFolder) Then fso.CreateFolder (localTempFolder)
    On Error GoTo 0
    
    Dim pptApp As Object: Set pptApp = Application
    Dim pres As Presentation
    Dim successCount As Long: successCount = 0
    Dim errorCount As Long: errorCount = 0
    
    Debug.Print "--- Export Process Started: " & Now & " ---"

    ' 2. メインループ
    For Each pres In pptApp.Presentations
        Dim localFile As String
        Dim networkFile As String
        Dim isSuccess As Boolean
        
        ' ファイル名の定義
        localFile = localTempFolder & Replace(pres.Name, ".pptx", ".pdf")
        networkFile = targetFolder & Replace(pres.Name, ".pptx", ".pdf")
        
        ' トランザクション開始
        isSuccess = TransactionalExport(pres, localFile, networkFile, fso)
        
        If isSuccess Then
            successCount = successCount + 1
        Else
            errorCount = errorCount + 1
        End If
    Next pres

    ' 3. 最終報告
    Debug.Print "--- Export Process Finished ---"
    Debug.Print "Success: " & successCount
    Debug.Print "Failed: " & errorCount
    MsgBox "処理完了" & vbCrLf & "成功: " & successCount & vbCrLf & "失敗: " & errorCount, vbInformation
End Sub

''' <summary>
''' 個別のプレゼンテーションに対するトランザクション制御
''' </summary>
Private Function TransactionalExport(ByRef pres As Presentation, _
                                    ByVal localPath As String, _
                                    ByVal networkPath As String, _
                                    ByRef fso As Object) As Boolean
    On Error GoTo ErrorHandler
    
    ' --- STEP 1: Local Stage (ローカルへ書き出し) ---
    ' ネットワークの遅延や切断の影響を最小限にするため、まずはローカルへ
    pres.ExportAsFixedFormat _
        Path:=localPath, _
        FixedFormatType:=ppFixedFormatTypePDF, _
        Intent:=ppFixedFormatIntentPrint
    
    ' --- STEP 2: Verification (整合性検証) ---
    ' ファイルが存在するか、かつサイズが0でないことを確認
    If Not fso.FileExists(localPath) Then
        Err.Raise vbObjectError + 513, "TransactionalExport", "File creation failed."
    End If
    
    If fso.GetFile(localPath).Size = 0 Then
        Err.Raise vbObjectError + 514, "TransactionalExport", "Generated file is empty (0 bytes)."
    End If
    
    ' --- STEP 3: Commit (ネットワークへの移動) ---
    ' ローカルでの検証が完了した「正しい」ファイルのみをネットワークへ送る
    ' 既存ファイルがある場合は上書き(必要に応じてロジック変更)
    If fso.FileExists(networkPath) Then
        fso.DeleteFile networkPath, True
    End If
    
    ' Move操作は、書き込み完了後に実行されるため、ネットワーク断絶時は
    ' ここでエラーが発生し、ネットワーク側に不完全なファイルが残るのを防ぐ
    fso.MoveFile localPath, networkPath
    
    TransactionalExport = True
    Exit Function

ErrorHandler:
    ' --- ROLLBACK (ロールバック) ---
    ' 失敗した場合、不完全なローカルファイルを削除し、クリーンな状態を維持する
    Debug.Print "Error in [" & pres.Name & "]: " & Err.Description
    
    If fso.FileExists(localPath) Then
        On Error Resume Next ' クリーンアップ中のエラーは無視
        fso.DeleteFile localPath, True
        On Error GoTo 0
    End If
    
    ' ネットワーク側に中途半端なファイルが残っていないか念のため確認・削除
    If fso.FileExists(networkPath) Then
        On Error Resume Next
        fso.DeleteFile networkPath, True
        On Error GoTo 0
    End If
    
    TransactionalExport = False
End Function

3. アーキテクチャの深層:なぜこのコードが「強い」のか

このコードが単なるエラー回避マクロと一線を画す理由は、以下の3点に集約される。

① 失敗の局所化 (Error Isolation)

`TransactionalExport` 関数を独立させ、個別のプレゼンテーションごとにエラーハンドリングを完結させている。これにより、1つのファイルがネットワークエラーで失敗しても、ループ全体を停止させることなく、次のファイルの処理へとレジリエントに移行できる。

② 不変条件の維持 (Maintaining Invariants)

我々の設計における「不変条件」は、「ネットワーク共有フォルダには、常に完全なPDFのみが存在しなければならない」ということだ。
`MoveFile` メソッドを使用することで、「書き込み中」という不安定な状態をネットワーク上で作り出さない。ファイルは「ローカルに存在する」か「ネットワークに完全に存在する」かのどちらかの状態しか取らない。

③ 副作用の最小化 (Minimizing Side Effects)

`ErrorHandler` 内でのロールバック処理は、エンジニアの「掃除」に対する執着心を表している。ローカルにゴミを残さない、ネットワークにゴミを残さない。この徹底したクリーンアップこそが、自動化ツールを「道具」から「信頼できるインフラ」へと昇華させる。

結論

自動化エンジニアの価値は、コードの短さではなく、「システムの予測可能性」をいかに高めるかで決まる。

「ネットワークはいつ切れるか分からない」「ファイルはいつ壊れるか分からない」という、最悪のシナリオを前提とした設計(Design for Failure)を行え。ローカル・ステージング・パターンをマスターすれば、君の作るツールは、どんなに劣悪な通信環境下でも、沈黙することなく、正確に任務を遂行するだろう。

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