【シャドウコピー自動化】編集中のファイルをロックせず世代バックアップを回す「オートセーブ・エンジン」の設計
開発現場でよく耳にする絶望的な悲鳴がある。
「電源が落ちて、さっきまでの3時間が消えた」
「上書き保存の瞬間にフリーズしてファイルが壊れた」
PowerPointの標準オートセーブ機能やOneDriveのバージョン履歴は強力だが、クローズドな社内ネットワーク、膨大な図形・オブジェクトを含む巨大な`.pptm`、あるいはマクロの暴走リスクを抱えた開発環境においては、「自分の手で完全に制御できるバックアップ機構」を持たせることが、プロフェッショナルなVBAエンジニアの生存戦略となる。
今回は、実務の現場で即座に採用できる、編集中のファイルをロックせずにバックグラウンドで世代バックアップを生成し続ける「シャドウコピー自動化エンジン」の全貌を解説する。
—
1. なぜ「通常の保存」ではダメなのか?(アーキテクチャの罠)
多くの素人がやりがちなアプローチは、タイマー処理の中で定期的に `ActivePresentation.Save` を叩くことだ。
これは最悪の悪手である。理由を挙げよう。
1. UIスレッドの完全なブロック
`Save`メソッドが走る瞬間、PowerPointは一時的にフリーズする。プレゼンターが発表の直前や、緻密なシェイプ調整を行っている最中にこれが起きると、致命的なストレスを与える。
2. ファイル競合と破損リスク
大容量のバイナリを書き込んでいる最中に強制終了やネットワーク切断が起きると、元ファイルごと破損するリスクが跳ね上がる。
解決策:`SaveCopyAs` と Windows API の融合
ここで登場するのが `Presentation.SaveCopyAs` メソッドである。
このメソッドは、メモリ上のドキュメント構造をそのまま別のパスに吐き出す。現在開いているファイル(カレントプレゼンテーション)のハンドルを一切ロックしないため、ユーザーの編集作業を妨げない。
さらに、これをVBAの `Application.OnTime` を用いた非同期タイマーで駆動させ、日時を付与した「世代管理バックアップ」としてテンポラリ領域に蓄積していく。これが本エンジンの中核思想だ。
—
2. 堅牢なバックアップエンジンの設計方針
プロダクションコードとして耐えうるシステムにするため、以下の要件を満たす設計とする。
- 無音のバックアップ(Silent Execution):エラーが発生してもユーザーの作業を邪魔せず、イミディエイトウィンドウやログにのみ吐き出す。
- 世代管理(Generation Control):無限にファイルが増えないよう、古いバックアップを自動パージ(削除)する。
- 未保存ファイルの検知:一度も名前をつけて保存されていない新規ファイル(パスが空)の場合は、無駄なエラーを出さずにスキップする。
—
3. プロダクションコード実装
以下のコードを標準モジュール(例:`ModAutoBackup`)に配置してほしい。
クラスモジュールを使わずとも、Applicationイベントとグローバル変数(あるいはWorkbook/Presentationライフサイクル)を巧みに利用することで、極めて軽量かつ堅牢なエンジンが構築できる。
Option Explicit
‘ =================================================================
‘ módulo: ModAutoBackup
‘ 概要: 編集中のファイルをロックしないシャドウコピー・オートセーブエンジン
‘ =================================================================
‘ タイマー実行を保持する時刻
Private NextSaveTime As Date
‘ 設定定数
Private Const BACKUP_INTERVAL_MINUTES As Double = 10 ‘ バックアップ間隔(分)
Private Const MAX_GENERATIONS As Long = 5 ‘ 保持する最大世代数
Private Const BACKUP_FOLDER_NAME As String = “_PowerPointShadowCopies”
Public Sub StartAutoBackup()
‘ タイマーの多重起動を防ぐため一度停止を挟む
On Error Resume Next
Call StopAutoBackup
On Error GoTo 0
‘ 初回実行スケジュールを設定
NextSaveTime = Now + TimeSerial(0, BACKUP_INTERVAL_MINUTES, 0)
Application.OnTime NextSaveTime, “ExecuteShadowCopy”
Debug.Print “[AutoBackup] Engine Started. Interval: ” & BACKUP_INTERVAL_MINUTES & ” min.”
End Sub
Public Sub StopAutoBackup()
‘ タイマーの解除
On Error Resume Next
Application.OnTime NextSaveTime, “ExecuteShadowCopy”, , False
On Error GoTo 0
Debug.Print “[AutoBackup] Engine Stopped.”
End Sub
Public Sub ExecuteShadowCopy()
Dim targetPres As Presentation
Dim originalPath As String
Dim originalName As String
Dim ext As String
Dim backupDir As String
Dim timeStamp As String
Dim backupPath As String
‘ エラーハンドリング:バックアップ失敗で本体の作業を止めてはならない
On Error GoTo ErrorHandler
‘ アクティブなプレゼンテーションが存在するか確認
If Documents.Count = 0 Then GoTo Reschedule
Set targetPres = ActivePresentation
‘ 未保存(新規ファイル)の場合はパスがないためスキップ
If targetPres.Path = “” Then GoTo Reschedule
originalPath = targetPres.Path
originalName = Left(targetPres.Name, InStrRev(targetPres.Name, “.”) – 1)
ext = Mid(targetPres.Name, InStrRev(targetPres.Name, “.”))
‘ バックアップ先のパス構築(元ファイルと同じ階層に隠しフォルダ的を作成、または環境変数TEMP等でも可)
backupDir = originalPath & “\” & BACKUP_FOLDER_NAME
If Dir(backupDir, vbDirectory) = “” Then
MkDir backupDir
End If
‘ タイムスタンプ生成 (YYYYMMDD_HHMMSS)
timeStamp = Format(Now, “yyyymmdd_HHMMSS”)
backupPath = backupDir & “\” & originalName & “_bk_” & timeStamp & ext
‘ ★極限の知見:SaveCopyAsにより、ファイルをロックせずに裏でコピー生成
targetPres.SaveCopyAs backupPath
Debug.Print “[AutoBackup] Success: ” & backupPath
‘ 世代管理(古いファイルのパージ)
Call PurgeOldBackups(backupDir, originalName, ext)
Reschedule:
‘ 次回実行のスケジュール登録
NextSaveTime = Now + TimeSerial(0, BACKUP_INTERVAL_MINUTES, 0)
Application.OnTime NextSaveTime, “ExecuteShadowCopy”
Exit Sub
ErrorHandler:
‘ 予期せぬエラーでもサイレントに処理を継続させ、エンジンの堅牢性を保つ
Debug.Print “[AutoBackup Error] Code: ” & Err.Number & ” – ” & Err.Description
Resume Reschedule
End Sub
Private Sub PurgeOldBackups(ByVal dirPath As String, ByVal baseName As String, ByVal fileExt As String)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim targetFolder As Object
Set targetFolder = fso.GetFolder(dirPath)
Dim fileList As Object
Set fileList = CreateObject(“System.Collections.ArrayList”)
Dim f As Object
For Each f In targetFolder.Files
‘ 該当ファイルのパターンに一致するものだけをリスト化
If Left(f.Name, Len(baseName)) = baseName And f.Name Like “” & fileExt Then
fileList.Add f
End If
Next f
‘ ファイル名(=タイムスタンプ)順にソート
fileList.Sort
‘ 最大世代数を超えている場合、古いものから削除
Do While fileList.Count > MAX_GENERATIONS
Dim oldestFile As Object
Set oldestFile = fileList(0)
oldestFile.Delete True
fileList.RemoveAt 0
Debug.Print “[AutoBackup Purge] Deleted old backup: ” & oldestFile.Name
Loop
End Sub
—
4. ライフサイクル管理の要諦(プレゼンテーション起動・終了時のフック)
上記のコード単体では、PowerPointを起動したときに自動でエンジンが走り出さない。また、ファイルを閉じるときにタイマーが残っていると、存在しないオブジェクトに対してイベントが発火し、予期せぬエラーやPowerPointのゴーストプロセスを発生させる原因となる。
これを完璧に制御するためには、`Auto_Open` や `Auto_Close`、あるいは専用のクラスによるイベントハンドリングを組み合わせる必要があるが、シンプルかつ確実なアプローチとして、プレゼンテーションのオープン・クローズに連動させる仕組みを組み込む。
‘ ThisPresentation モジュール(または標準モジュールでのWorkbook/Presentation代用イベント)
‘ ※PowerPoint VBAにはWorkbook_Openのような簡単なお手軽イベントがないため、
‘ 実際にはアドイン(.ppam)化するか、リボンUIやAuto_Openプロシージャを活用する。
Sub Auto_Open()
‘ アドインまたはマクロ有効ドキュメントが開かれたときに自動起動
Call StartAutoBackup
End Sub
Sub Auto_Close()
‘ 終了時に確実にタイマーを解放し、メモリリークを防ぐ
Call StopAutoBackup
End Sub
> architect’s Note:
> 本格的な業務ツールとして展開する場合、このモジュール群を独立したPowerPointアドイン(.ppam)としてコンパイルし、起動時に自動読み込みさせることを強く推奨する。そうすることで、ユーザーがどのプレゼンテーションを編集していても、バックグラウンドで一元的にシャドウコピーの網を張ることが可能になる。
—
5. 運用上の注意点と実務でのトリアージ
1. テンポラリ領域の肥大化対策
コード内の `MAX_GENERATIONS = 5` は、10分おきなら直近50分間(約1時間分)のバックアップを保持する。もしプレゼンファイルの容量が500MBもある場合、5世代で2.5GBのストレージを消費する。対象ファイルのサイズに応じて `BACKUP_INTERVAL_MINUTES` と `MAX_GENERATIONS` のバランスをチューニングすること。
2. ネットワークドライブ上のファイル
社内共有サーバー(NASやS3互換ストレージのマウントなど)で直接作業している最中に `SaveCopyAs` を走らせると、ネットワークの帯域を圧迫することがある。その場合は、`Environ(“TEMP”)` などのローカル一時フォルダにまずバックアップを吐き出すようにコードを改修するのが、プロフェッショナルとしての正しい判断である。
総括
VBAにおける自動化の本質は、「コードを動かすこと」ではなく、「ユーザーに存在を意識させず、システムの脆弱性やヒューマンエラーを完全にハイドレート(無効化)すること」にある。
今回構築したシャドウコピー・オートセーブエンジンは、そのための最もエレガントかつ実用的なソリューションだ。ぜひ、あなたの現場のファットなプレゼンテーション群に導入し、データ消失の恐怖を過去のものにしてほしい。
