【実務・中級編】【プレゼン比較】2つのプレゼンテーションの総スライド数やスライド順序、最終更新日時を比較し、差分をイミディエイトウィンドウに出力する簡易監査ツール – PowerPoint VBA解析バイブル

スポンサーリンク

プロジェクトリーダーが直伝する「PowerPoint監査の自動化」

業務でプレゼン資料を扱っていると、必ず直面するのが「どれが最新のマスターか分からない」「どこが勝手に改変されたのか」というバージョン管理の闇だ。特に複数のメンバーが編集に関わった場合、スライドの順序が入れ替わったり、古いスライドが混入したりする。

これを人の目で行うのは、苦行であり、完全なヒューマンエラーの温床だ。

今回は、2つのPowerPointプレゼンテーション(マスター版と編集版)をVBAで並列走査し、「総スライド数」「スライド順序(タイトルやテキストの類似性)」「最終更新日時」を徹底比較してイミディエイトウィンドウに監査レポートを出力する、プロダクション品質のツールを授けよう。

1. 堅牢な設計:なぜ単純なループでは破綻するのか?

多くの初学者は、ふたつの`Presentation`オブジェクトを開き、単純な`For i = 1 To 2`のようなループでスライドを力技比較しようとする。だが、実務の現場ではこれでは即座に破綻する。

陥りがちな罠

1. ファイルのロックとライフサイクル管理:参照するファイルを読取専用で安全に開く必要がある。また、マクロ実行後に裏でゾンビプロセスとして残らないよう、確実に`Close`・`Nothing`する設計が不可欠だ。
2. スライドタイトルの取得コスト:スライドの同一性を判定するためには、タイトルプレースホルダーや先頭のテキストフレームを安全に取得する必要があるが、空のスライドや図形のみのスライドに対してこれをやると、容赦なく実行時エラー(Runtime Error)が飛んでくる。
3. カプセル化と保守性:すべてのロジックを1つのプロシージャに書き殴ると、改修時に地獄を見る。ファイルオープン、監査ロジック、結果出力の責務を明確に分離すべきだ。

2. プロダクションコード:プレゼン比較監査ツール

以下のコードは、エラーハンドリングを網羅し、ファイルの有無や安全なオブジェクト解放を徹底した実務仕様のVBAコードだ。

VBE(Visual Basic Editor)の標準モジュールにそのまま貼り付けて実行してほしい。

Option Explicit

‘ ==============================================================================
‘ 2つのプレゼンテーションの差分を監査するメインプロシージャ
‘ ==============================================================================
Public Sub AuditPresentationDiff()
‘ パスは環境に合わせて書き換えてください(絶対パス推奨)
Const MASTER_PATH As String = “C:\Reports\Master_Presentation.pptx”
Const TARGET_PATH As String = “C:\Reports\Edited_Presentation.pptx”

Dim appPpt As PowerPoint.Application
Dim prsMaster As Presentation
Dim prsTarget As Presentation

‘ ファイル存在チェック
If Not CheckFileExists(MASTER_PATH) Or Not CheckFileExists(TARGET_PATH) Then
Exit Sub
End If

‘ PowerPointインスタンスの取得(既存を流用、なければ新規)
On Error Resume Next
Set appPpt = GetObject(, “PowerPoint.Application”)
If appPpt Is Nothing Then
Set appPpt = New PowerPoint.Application
End If
On Error GoTo 0

‘ バックグラウンドで安全にファイルを開く(読み取り専用、ウィンドウ非表示が理想だがここでは簡略化)
‘ ※実務では ReadOnly:=True を推奨
Set prsMaster = appPpt.Presentations.Open(MASTER_PATH, ReadOnly:=True, WithWindow:=msoFalse)
Set prsTarget = appPpt.Presentations.Open(TARGET_PATH, ReadOnly:=True, WithWindow:=msoFalse)

‘ — 監査実行 —
Debug.Print “==================================================”
Debug.Print ” プレゼンテーション監査レポート”
Debug.Print “==================================================”
Debug.Print “マスター: ” & MASTER_PATH
Debug.Print “編集版 : ” & TARGET_PATH
Debug.Print “————————————————–”

‘ 1. メタデータ(最終更新日時など)の比較
Call AuditMetadata(prsMaster, prsTarget)

‘ 2. 構造(総スライド数)の比較
Call AuditStructure(prsMaster, prsTarget)

‘ 3. スライド内容(タイトル・順序)の並列走査
Call AuditSlideContents(prsMaster, prsTarget)

Debug.Print “==================================================”
Debug.Print ” 監査完了”
Debug.Print “==================================================”

CleanUp:
‘ オブジェクトのライフサイクルを厳格に管理し、メモリリークを防ぐ
On Error Resume Next
If Not prsMaster Is Nothing Then prsMaster.Close
If Not prsTarget Is Nothing Then prsTarget.Close
Set prsMaster = Nothing
Set prsTarget = Nothing
Set appPpt = Nothing
On Error GoTo 0
End Sub

‘ ==============================================================================
‘ ファイル存在確認関数
‘ ==============================================================================
Private Function CheckFileExists(ByVal filePath As String) As Boolean
If Dir(filePath) = “” Then
MsgBox “指定されたファイルが見つかりません: ” & vbCrLf & filePath, vbCritical, “パスエラー”
CheckFileExists = False
Else
CheckFileExists = True
End If
End Function

‘ ==============================================================================
‘ メタデータ監査
‘ ==============================================================================
Private Sub AuditMetadata(ByRef master As Presentation, ByRef target As Presentation)
Debug.Print “[メタデータ比較]”
‘ BuiltInDocumentPropertiesによるプロパティ取得
Dim masterTime As String, targetTime As String

On Error Resume Next
masterTime = master.BuiltInDocumentProperties(“Last Save Time”).Value
targetTime = target.BuiltInDocumentProperties(“Last Save Time”).Value
On Error GoTo 0

Debug.Print ” – マスター最終保存: ” & IIf(masterTime = “”, “取得不可”, masterTime)
Debug.Print ” – 編集版最終保存 : ” & IIf(targetTime = “”, “取得不可”, targetTime)
Debug.Print “”
End Sub

‘ ==============================================================================
‘ 構造監査(スライド数)
‘ ==============================================================================
Private Sub AuditStructure(ByRef master As Presentation, ByRef target As Presentation)
Debug.Print “[構造比較(スライド数)]”
Debug.Print ” – マスター総数: ” & master.Slides.Count & ” 枚”
Debug.Print ” – 編集版総数 : ” & target.Slides.Count & ” 枚”

If master.Slides.Count <> target.Slides.Count > 0 Then
Debug.Print ” [警告] スライド総数に差異があります!”
Else
Debug.Print ” [一致] スライド総数は一致しています。”
End If
Debug.Print “”
End Sub

‘ ==============================================================================
‘ スライド内容・順序の並列走査
‘ ==============================================================================
Private Sub AuditSlideContents(ByRef master As Presentation, ByRef target As Presentation)
Debug.Print “[スライド内容・順序監査]”

Dim maxSlides As Long
Dim i As Long
Dim masterTitle As String
Dim targetTitle As String

If master.Slides.Count > target.Slides.Count Then
maxSlides = master.Slides.Count
Else
maxSlides = target.Slides.Count
End If

For i = 1 To maxSlides
masterTitle = GetSlideTitle(master, i)
targetTitle = GetSlideTitle(target, i)

If i <= master.Slides.Count And i <= target.Slides.Count Then If masterTitle = targetTitle Then Debug.Print " [一致] No." & i & " : " & masterTitle Else Debug.Print " [差異] No." & i & " -> Master: [” & masterTitle & “] vs Target: [” & targetTitle & “]”
End If
ElseIf i > master.Slides.Count Then
Debug.Print ” [追加] No.” & i & ” (編集版のみ存在) : [” & targetTitle & “]”
Else
Debug.Print ” [削除] No.” & i & ” (マスターのみ存在) : [” & masterTitle & “]”
End If
Next i
End Sub

‘ ==============================================================================
‘ 安全なスライドタイトル取得ヘルパー
‘ ==============================================================================
Private Function GetSlideTitle(ByRef prs As Presentation, ByVal slideIndex As Long) As String
If slideIndex > prs.Slides.Count Then
GetSlideTitle = “(N/A)”
Exit Function
End If

Dim sld As Slide
Set sld = prs.Slides(slideIndex)

Dim titleStr As String
titleStr = “”

‘ プレースホルダーからタイトルを取得する試行
On Error Resume Next
If sld.Shapes.HasTitle Then
titleStr = sld.Shapes.Title.TextFrame.TextRange.Text
End If

‘ タイトルプレースホルダーがない場合は最初のテキストフレームを探す(フォールバック)
If titleStr = “” Then
Dim shp As Shape
For Each shp in sld.Shapes
If shp.HasTextFrame Then
If shp.TextFrame.HasText Then
titleStr = shp.TextFrame.TextRange.Lines(1).Text
Exit For
End If
End If
Next shp
End If
On Error GoTo 0

‘ 改行文字や長すぎる文字列をトリミング
titleStr = Trim(Replace(Replace(titleStr, vbCrLf, ” “), vbCr, ” “))
If Len(titleStr) > 30 Then
titleStr = Left(titleStr, 27) & “…”
End If

If titleStr = “” Then titleStr = “(タイトルなし/図形のみ)”

GetSlideTitle = titleStr
End Function

3. チーフアーキテクトからの実践的アドバイス

1. `WithWindow:=msoFalse` の活用
今回のコードではファイルを開く際に裏側で処理させる設計にしている。巨大なプレゼンテーションを視覚的に開閉させると、画面描画のオーバーヘッドでマクロの実行速度が何倍にも膨れ上がる。バックグラウンド処理を徹底せよ。
2. データベースやログファイル連携への拡張
今回はイミディエイトウィンドウへの出力にとどめたが、実務ではこの結果をCSVやExcel、あるいは社内データベース(SQL Server等)に書き出すことで、「誰がいつ、どの資料を改変したか」の完全な監査ログ(Audit Trail)として機能させることができる。JSON形式でログを吐き出させるラッパーを作るのもモダンなアプローチだ。

業務効率化とは、単に手作業をVBAに置き換えることではない。「エラーが起きない構造をデザインし、保守性を担保すること」だ。このツールをベースに、自社のワークフローに合わせた最強の監査システムを作り上げてほしい。

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