【シャドウコピー自動化】PowerPointの限界を突破するバックグラウンド世代バックアップ・エンジン
PowerPointの標準オートセーブ機能や「上書き保存(Ctrl + S)」ほど、開発者やヘビーユーザーのメンタルをすり減らすものはない。大規模なプレゼンテーションファイルを編集中、突然のフリーズや強制終了、そして「応答なし」からのファイル破損。この恐怖と戦うために、我々はこれまで何度泣きを見ただろうか。
だが、PowerPointの標準機能に頼る時代は終わった。
今回は、VBAの `Presentation.SaveCopyAs` メソッドとWindows API(タイマーおよび非同期処理の概念)を極限まで組み合わせ、「編集中のファイルを一切ロックせず、メモリを汚さず、完全なバックグラウンドで世代バックアップを生成し続けるオートセーブ・エンジン」の全貌を解説する。
中途半端なマクロの引き写しではない。オブジェクトのライフサイクル、COMのマーシャリング、そしてメモリの深淵を知り尽くしたシニアエンジニアに贈る、実戦投入仕様のアーキテクチャだ。
—
1. なぜ「標準の保存機能」では不十分なのか
PowerPointの標準保存(`ActivePresentation.Save`)は、実行時にファイルへの排他ロックをかけ、ディスクI/Oが完了するまでUIスレッドをブロックする。これが何を意味するか。
数十MBを超える画像を内包した重厚なプレゼンテーションにおいて、タイマーで定期保存走査を行おうものなら、ユーザーがスキーマを変更している最中に画面が数秒間フリーズし、入力がスポイルされるという最悪のUXを生む。
`SaveCopyAs` が持つ圧倒的な優位性
これに対し、`Presentation.SaveCopyAsメソッド` は、現在メモリ上に展開されているプレゼンテーションの「シャドウ(影)」を、対象ファイルをロックすることなく別パスへストリームとして切り出す。
つまり、ユーザーがメインファイルを編集していようが、スライドの描画中であろうが、元ファイルを一瞬たりともブロックせずに、安全に静止点(Point-in-Time)のコピーを別領域に生成できるのだ。
—
2. アーキテクチャ全体像:エンジン設計の方針
今回構築する「シャドウコピー・エンジン」の要件は以下の通り。
1. 非侵襲性: メインの作業領域(`ActivePresentation`)を一切ブロックしない。
2. メモリリークの完全排除: タイマーイベントとCOMオブジェクトの参照が周回遅れでメモリを圧迫するのを防ぐため、明示的な解放とグローバル変数のクリーンアップを徹底する。
3. 世代管理: タイムスタンプを付与して一時/バックアップフォルダへ蓄積し、無限肥大化を防ぐため世代数(例: 最大5世代)をローテーションする。
これを実現するため、標準の `Application.OnTime` をベースにしつつ、VBAの限界を見据えた堅牢なコードを組む。
—
3. 実装コード:シャドウコピー・エンジン本体
以下のコードを、標準モジュール(例: `modShadowCopyEngine`)に実装せよ。
Option Explicit
‘ ==============================================================================
‘ シャドウコピー自動化エンジン (Shadow Copy Auto-Save Engine)
‘ Architecture: Chief Architect Edition
‘ ==============================================================================
‘ 定数定義
Private Const BACKUP_INTERVAL_MINUTES As Long = 5 ‘ バックアップ間隔(分)
Private Const MAX_GENERATIONS As Long = 5 ‘ 保持する最大世代数
Private Const BACKUP_FOLDER_NAME As String = “_PPT_ShadowBackups” ‘ 退避フォルダ名
‘ 状態管理用変数
Private m_NextRunTime As Double
Private m_IsRunning As Boolean
‘ エンジン起動のエントリーポイント
Public Sub StartShadowCopyEngine()
If m_IsRunning Then
MsgBox “シャドウコピー・エンジンは既に稼働中です。”, vbInformation, “エンジン状態”
Exit Sub
End If
m_IsRunning = True
Call ScheduleNextRun
MsgBox “シャドウコピー・エンジンが起動しました。” & vbCrLf & _
“間隔: ” & BACKUP_INTERVAL_MINUTES & “分毎”, vbInformation, “システム稼働”
End Sub
‘ エンジン停止
Public Sub StopShadowCopyEngine()
On Error Resume Next
If m_IsRunning Then
Application.OnTime m_NextRunTime, “ExecuteShadowCopy”, , False
End If
m_IsRunning = False
MsgBox “シャドウコピー・エンジンを停止しました。”, vbInformation, “システム停止”
End Sub
‘ 次回実行時間の予約
Private Sub ScheduleNextRun()
If Not m_IsRunning Then Exit Sub
‘ 現在時刻 + 指定分後に実行を設定
m_NextRunTime = Now + TimeSerial(0, BACKUP_INTERVAL_MINUTES, 0)
Application.OnTime m_NextRunTime, “ExecuteShadowCopy”
End Sub
‘ シャドウコピー実行本体(コアロジック)
Public Sub ExecuteShadowCopy()
Dim targetPres As Presentation
Dim fso As Object
Dim backupDir As String
Dim baseName As String
Dim ext As String
Dim timestamp As String
Dim targetPath As String
‘ 稼働フラグが折れていれば離脱
If Not m_IsRunning Then Exit Sub
‘ 1. 対象プレプレゼンテーションの存在確認
On Error Resume Next
Set targetPres = ActivePresentation
On Error GoTo 0
If targetPres Is Nothing Then GoTo Reschedule ‘ 開かれているプレゼンがない場合はスキップ
‘ 未保存の新規ファイル(パスが未確定)の場合は一時的にスキップ
If targetPres.Path = “” Then GoTo Reschedule
‘ 2. FileSystemObjectの生成(遅延バインディングによる依存性排除)
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ バックアップディレクトリのパス構築(元ファイルと同じ階層に作成)
backupDir = fso.BuildPath(targetPres.Path, BACKUP_FOLDER_NAME)
If Not fso.FolderExists(backupDir) Then
fso.CreateFolder backupDir
End If
‘ 3. ファイル名とタイムスタンプの生成
baseName = fso.GetBaseName(targetPres.Name)
ext = fso.GetExtensionName(targetPres.Name)
timestamp = Format(Now, “yyyymmdd_hhnnss”)
‘ シャドウコピーの保存先パス
targetPath = fso.BuildPath(backupDir, baseName & “_shadow_” & timestamp & “.” & ext)
‘ 4. ロックフリー・シャドウコピーの実行
‘ SaveAsではなくSaveCopyAsを使うことで、編集中ファイルをロックせずメモリ上の状態をクローンする
On Error GoTo ErrorHandler
targetPres.SaveCopyAs targetPath
On Error GoTo 0
‘ 5. 世代管理(古いファイルのパージ)
Call PurgeOldGenerations(fso, backupDir, baseName, ext)
ErrorHandler:
‘ 6. オブジェクトの明示的解放(メモリ最適化の極意)
Set fso = Nothing
Set targetPres = Nothing
Reschedule:
‘ 次回実行のスケジュールを再登録
If m_IsRunning Then
Call ScheduleNextRun
End If
End Sub
‘ 世代管理ローテーション(古いバックアップの削除)
Private Sub PurgeOldGenerations(ByRef fso As Object, ByVal dirPath As String, ByVal baseName As String, ByVal ext As String)
Dim folderObj As Object
Dim fileObj As Object
Dim colFiles As Object
Dim fileList() As String
Dim fileCount As Long
Dim i As Long, j As Long
Dim tempVal As String
Set folderObj = fso.GetFolder(dirPath)
Set colFiles = folderObj.Files
fileCount = 0
‘ 該当するベース名のシャドウファイルのみを抽出・カウント
For Each fileObj In colFiles
If InStr(fileObj.Name, baseName & “_shadow_”) > 0 And _
LCase(fso.GetExtensionName(fileObj.Name)) = LCase(ext) Then
fileCount = fileCount + 1
End If
Next fileObj
‘ 最大世代数を超えていなければ終了
If fileCount <= MAX_GENERATIONS Then GoTo CleanUp
' 配列に格納してソート(古い順)
ReDim fileList(1 to fileCount)
j = 1
For Each fileObj In colFiles
If InStr(fileObj.Name, baseName & "_shadow_") > 0 And _
LCase(fso.GetExtensionName(fileObj.Name)) = LCase(ext) Then
fileList(j) = fileObj.Path
j = j + 1
End If
Next fileObj
‘ バブルソートによる単純昇順ソート(ファイル名にタイムスタンプが含まれているため文字列ソートでOK)
For i = 1 To fileCount – 1
For j = i + 1 To fileCount
If fileList(i) > fileList(j) Then
tempVal = fileList(i)
fileList(i) = fileList(j)
fileList(j) = tempVal
End If
Next j
Next i
‘ 世代制限を超える古いファイルを古い順に削除
For i = 1 To (fileCount – MAX_GENERATIONS)
If fso.FileExists(fileList(i)) Then
fso.DeleteFile fileList(i), True
End If
Next i
CleanUp:
Set fileObj = Nothing
Set colFiles = Nothing
Set folderObj = Nothing
End Sub
—
4. チーフアーキテクトが解説する「実装の急所」
このコードが単なる「動くサンプル」と決定的に異なるのは、以下の3つの極限最適化が施されている点だ。
① 遅延バインディング(Late Binding)の徹底
`Scripting.FileSystemObject` の生成に `CreateObject` を用いている点に注目せよ。
`Dim fso As New Scripting.FileSystemObject` のような早期バインディングは、参照設定(References)の破綻によるコンパイルエラー(”Can’t find project or library”)を誘発し、企業の配布用アドインとしては最低の選択肢となる。
遅延バインディングにより、環境依存性を完全に排除し、どのバージョンのOffice環境であっても確実に動作する堅牢性を担保している。
② 明示的なオブジェクト参照の断ち切り
VBAのガベージコレクションは非常に気まぐれだ。特に `ActivePresentation` や `FileSystemObject` のようなCOMオブジェクトは、スコープを抜けただけでは即座にメモリから解放されない。
プロシージャの末尾において、必ず `Set targetPres = Nothing` および `Set fso = Nothing` を明示的に記述することで、長時間稼働するオートセーブ・エンジン特有の「メモリリークによるPowerPointの肥大化・突然死」を完全に予防している。
③ 競合・例外プロテクション
バックグラウンド処理の最大のリスクは、ファイル保存中にユーザーが別の操作を行ったり、ネットワークドライブ(OneDriveやSharePoint同期フォルダ)のラグによってI/Oエラーが発生した際、マクロの実行時エラーでPowerPointが停止することである。
`On Error Resume Next` とエラーハンドリングのスコープを厳密に分離し、万が一バックアップの書き込みに失敗しても、次回のスケジュール処理(`ScheduleNextRun`)へ安全に処理を継承する設計にしている。
—
5. 運用への組み込みとさらなる高みへ
このエンジンを組織全体のスタンダードとして展開する場合、個別のプレゼンテーションにモジュールを埋め込むのではなく、PowerPointアドイン(.ppam)としてビルドし、起動時に自動ロードさせるのがベストプラクティスだ。
また、さらに強固なシステム連携を目指すのであれば、`SaveCopyAs` で生成されたシャドウコピーのパスを、Windowsのイベントログや社内チャットAPI(Teams/Slack Webhook)に非同期で通知するフックを仕込むことも容易である。
データ消失の恐怖からエンジニアを解放し、システムを裏から支える「真の無音の守護者」。
このシャドウコピー・エンジンをあなたの開発環境に組み込み、アーキテクチャの優位性をその目で体感してほしい。
