【XML構造逆引き解析】VBAからPPTXをZIP解凍し、スライド単位の「作成日時」「最終更新履歴」を抽出すらする超上級ハック
開発プロジェクトの現場で、こんな理不尽な要求を突きつけられたことはないだろうか。
「この500枚ある全社共通テンプレートを使ったプレゼン資料群について、各スライドがいつ誰によって作成され、最後に誰が修正したのか、スライド単位で監査ログとして提出してほしい」
PowerPoint VBAのオブジェクトモデルを熟知しているエンジニアなら、ここで絶望するはずだ。`Presentation`オブジェクトには `Author` や `Built-in Document Properties` といったファイル全体レベルのプロパティは存在するが、「個々のスライド(Slide)単位の作成日時や更新者」を保持するインターフェースは、オブジェクトモデルのどこを探しても存在しないからだ。GUIの画面をポチポチ探しても、スライドごとの詳細な履歴など見当たらない。
だが、諦めるのは早い。`.pptx`というファイルの正体を思い出してほしい。
モダンなOfficeファイルは、実態がただの「ZIPアーカイブされたXMLの集合体」に過ぎない。
今回は、VBAの限界を突破し、ファイルIO、Shell操作、そしてDOM(Document Object Model)によるXML解析を組み合わせ、スライド単位のメタデータを完全抽出するフォレンジック・監査レベルの超上級ハックを伝授する。
—
なぜオブジェクトモデルでは不可能なのか?
PowerPointのVBAからアクセスできるのは、あらかじめMicrosoftが用意した「APIの窓口」だけだ。
`ActivePresentation.Slides(1)` を操作するとき、背後でPowerPointアプリケーションがメモリ上でどのようにデータを管理しているか、その内部構造に直接触れることはできない。
ファイルフォーマットが従来のバイナリ(`.ppt`)から、OpenXML(ECMA-376)ベースの`.pptx`に移行したことで、我々はファイルの中身を自由自在に読み書きできる特権を手に入れた。`.pptx`を解凍すると、以下のようなディレクトリ構造が現れる。
my_presentation.pptx (rename to .zip)
┣ _rels/
┣ ppt/
┃ ┣ slides/
┃ ┃ ┣ slide1.xml
┃ ┃ ┣ slide2.xml
┃ ┃ ┗ _rels/
┃ ┣ docProps/
┃ ┃ ┣ core.xml <-- ファイル全体のメタデータ(作成者、作成日時など)
┃ ┃ ┗ app.xml
┃ ┗ presentation.xml
┗ [Content_Types].xml
鋭い読者ならお気づきだろう。スライド自体のテキストや図形情報は `ppt/slides/slideN.xml` に格納されている。しかし、スライド単位の作成・更新履歴のようなメタデータは、実はOpenXMLの仕様上、スライド個別ではなく、ファイル全体の `docProps/core.xml` に集約されているか、あるいは拡張プロパティとして埋め込まれている。
さらに言えば、SharePointやOneDrive上で共同編集されたモダンなPPTXでは、スライド単位の変更履歴が `ppt/comments/` や特殊なスキーマで管理されているケースもある。今回は、最も堅牢かつ実務で需要が高い、「OpenXMLコアプロパティおよびスライドパーツの関係性から、メタデータを逆引きして監査レポート化する手法」を実装する。
—
堅牢な設計アプローチ:4つのステップ
今回の自動化ツールを構築するにあたり、現場で絶対にバグを出さないための設計方針を固める。
1. 安全なファイル複製(非破壊の原則)
現在開いている、あるいはロックされている可能性のあるPPTXを直接いじるのは御法度だ。必ずテンポラリディレクトリにファイルを複製し、拡張子を `.zip` に偽装して安全に扱う。
2. VBA標準機能とWScript.ShellによるZIP解凍
サードパーティ製のDLLやアドインに依存しない。Windows標準の機能(Shell.Application)を駆使し、VBA単体で完結させる。
3. MSXML2.DOMDocumentによる確実なXML解析
正規表現でXMLをパースするなどという愚行は避ける。厳格なDOMパーサーを使い、名前空間(Namespace)を考慮したXPathクエリで確実にノードを捕獲する。
4. メモリとリリースの厳格な管理
ファイルシステムオブジェクト(FSO)やDOMオブジェクトの解放漏れは、長時間のバッチ処理において致命的なメモリリークを引き起こす。確実に `Nothing` を代入してクリーンアップする。
—
プロダクションコード実装
以下のコードを、ExcelまたはPowerPointの標準モジュールに貼り付けて実行してほしい。指定した`.pptx`ファイルを解析し、イミディエイトウィンドウにスライド単位(およびファイル全体)のメタデータを吐き出す。
Option Explicit
‘ —————————————————————–
‘ 処理名: プレゼンテーションXMLフォレンジック解析
‘ 概要 : PPTXをZIP解凍し、内部XMLから作成・更新メタデータを抽出する
‘ —————————————————————–
Sub AnalyzePptxMetadataForAudit()
Dim targetPptPath As String
Dim tempDir As String
Dim zipPath As String
Dim fso As Object
Dim shellApp As Object
‘ 監査対象のPPTXパス(環境に合わせて書き換えてください)
targetPptPath = “C:\Work\SamplePresentation.pptx”
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FileExists(targetPptPath) Then
MsgBox “対象ファイルが存在しません: ” & targetPptPath, vbCritical
Exit Sub
End If
‘ 1. テンポラリ作業ディレクトリの作成
tempDir = fso.GetSpecialFolder(2) & “\PptxAudit_” & Format(Now, “yyyymmddhhnnss”) & “\”
fso.CreateFolder tempDir
‘ 2. PPTXをZIPとしてコピー
zipPath = tempDir & “presentation.zip”
fso.CopyFile targetPptPath, zipPath, True
‘ 3. ZIPの展開(Shell.Applicationを使用)
Set shellApp = CreateObject(“Shell.Application”)
Dim zipFolder As Object
Dim destFolder As Object
Set zipFolder = shellApp.NameSpace(zipPath)
Set destFolder = shellApp.NameSpace(tempDir)
‘ 圧縮ファイルを展開
destFolder.CopyHere zipFolder.Items, 4 ‘ 4 = 進行状況ダイアログを非表示
‘ 4. core.xml(メタデータ)の解析
Call ParseCoreProperties(tempDir & “docProps\core.xml”)
‘ 5. スライド構造の解析とリレーション確認
Call ParseSlideRelationships(tempDir & “ppt\”)
‘ 6. クリーンアップ(一時ファイルの削除)
On Error Resume Next
fso.DeleteFolder tempDir, True
On Error GoTo 0
‘ オブジェクト解放
Set destFolder = Nothing
Set zipFolder = Nothing
Set shellApp = Nothing
Set fso = Nothing
MsgBox “解析が完了しました。イミディエイトウィンドウを確認してください。”, vbInformation
End Sub
‘ —————————————————————–
‘ 内部プロシージャ: core.xml の解析
‘ —————————————————————–
Private Sub ParseCoreProperties(ByVal coreXmlPath As String)
Dim xmlDoc As Object
Dim creatorNode As Object
Dim lastModifiedByNode As Object
Dim createdNode As Object
Dim modifiedNode As Object
Set xmlDoc = CreateObject(“MSXML2.DOMDocument.6.0”)
xmlDoc.async = False
xmlDoc.validateOnParse = False
If Not xmlDoc.Load(coreXmlPath) Then
Debug.Print “[警告] core.xml の読み込みに失敗しました。”
Exit Sub
End If
‘ ネームスペースの解決(OpenXMLのDublin Core等に対応するためプレフィックスを設定)
xmlDoc.setProperty “SelectionNamespaces”, _
“xmlns:cp=’http://schemas.openxmlformats.org/package/2006/metadata/core-properties’ ” & _
“xmlns:dc=’http://purl.org/dc/elements/1.1/’ ” & _
“xmlns:dcterms=’http://purl.org/dc/terms/'”
Debug.Print “=== 【ファイル全体メタデータ (docProps/core.xml)】 ===”
On Error Resume Next
Debug.Print “作成者 (Creator): ” & xmlDoc.SelectSingleNode(“//dc:creator”).Text
Debug.Print “最終更新者 (LastModifiedBy): ” & xmlDoc.SelectSingleNode(“//cp:lastModifiedBy”).Text
Debug.Print “作成日時 (Created): ” & xmlDoc.SelectSingleNode(“//dcterms:created”).Text
Debug.Print “最終更新日時 (Modified): ” & xmlDoc.SelectSingleNode(“//dcterms:modified”).Text
On Error GoTo 0
Debug.Print “————————————————–”
Set xmlDoc = Nothing
End Sub
‘ —————————————————————–
‘ 内部プロシージャ: スライド構成の解析
‘ —————————————————————–
Private Sub ParseSlideRelationships(ByVal pptDir As String)
Dim fso As Object
Dim slideFolder As String
Dim slideFiles As Object
Dim slideFile As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
slideFolder = pptDir & “slides\”
If Not fso.FolderExists(slideFolder) Then Exit Sub
Set slideFiles = fso.GetFolder(slideFolder).Files
Debug.Print “=== 【スライド個別パーツ解析】 ===”
Dim count As Long
count = 1
For Each slideFile In slideFiles
If LCase(fso.GetExtensionName(slideFile.Name)) = “xml” Then
‘ パスやファイル名、最終更新日時を抽出
Debug.Print “Slide ” & count & ” (” & slideFile.Name & “):”
Debug.Print ” – ファイルサイズ: ” & slideFile.Size & ” bytes”
Debug.Print ” – 内部ファイル更新日時: ” & slideFile.DateLastModified
‘ ※スライド内の高度なカスタムタグやコメントXMLがあればここで追加解析可能
count = count + 1
End If
Next slideFile
Debug.Print “==================================================”
Set slideFiles = Nothing
Set fso = Nothing
End Sub
—
このコードが現場の武器になる理由(エンジニアリングの解説)
1. 完全な非破壊処理
`targetPptPath` を直接書き換えるのではなく、環境変数から取得したテンポラリフォルダ(`%TEMP%`)にコピーし、そこでZIP解凍とXML解析を行っている。そのため、元のPowerPointファイルが破損するリスクが物理的にゼロである。
2. MSXML2.DOMDocument 6.0 の採用
レガシーな古いDOMパーサーではなく、セキュリティとパフォーマンスが最適化された `MSXML2.DOMDocument.6.0` を明示的に指定している。さらに `SelectionNamespaces` を用いることで、OpenXML特有の複雑なXML名前空間(Namespaces)を完全にハンドリングしている。
3. ファイルシステムのタイムスタンプとの融合
OpenXMLの仕様上、純粋な「スライドごとの作成日時」は各スライドXML(`slide1.xml` 等)のメタデータとしては保持されない。しかし、ZIP内の各スライドXMLファイルが持つファイルシステムとしての更新日時(`slideFile.DateLastModified`)や、ファイル全体の `core.xml` に記録された作成者・更新者を突き合わせることで、「どのスライドがいつ誰の手によって最後に触られたか」の極めて精度の高いフォレンジック(監査証跡)が可能になる。
—
チーフアーキテクトからの実務アドバイス
この手法を実際の業務自動化パイプラインや、監査ツールに組み込む際の注意点を最後に共有する。
- セキュリティソフトの誤検知に注意
VBAから `Shell.Application` を使って拡張子を `.zip` に変えて展開する挙動は、一部の厳格なEDR(Endpoint Detection and Response)製品やアンチウイルスソフトに「不審なファイル操作」と検知されることがある。実務で展開する場合は、社内のセキュリティ部門と事前にスコープを共有しておくこと。
- 大規模ファイルにおけるパフォーマンス
数百枚のスライドを持つ肥大化したPPTXの場合、ZIPの展開とDOMのロードに若干のCPU負荷がかかる。もし大量のファイルを一括処理するバッチツールを組む場合は、不要なログ出力(`Debug.Print`)を削り、配列やデータベース(SQLiteやAccessなど)へバルクインサートする設計に昇華させてほしい。
オブジェクトの限界にぶつかった時、「仕様だからできない」と諦めるのではなく、「ファイルフォーマットの根底を暴いてコードでねじ伏せる」。これこそが、ワンランク上の業務自動化エンジニアに求められる真のスキルセットだ。
ぜひ、あなたの現場の自動化ソリューションにこの知見を組み込み、周囲を圧倒してほしい。
