PowerPoint VBAにおける `Presentation.SaveAs` メソッドは、一見すると単純な命令に見える。しかし、実務の現場において「ユーザーに自由なパスを入力させたら、拡張子の指定ミスや不適切なフォーマット定数の指定で実行時エラーが起きた」というトラブルを経験した開発者は少なくないはずだ。
「PDFで保存したいのに `.pptx` 用の定数のまま渡して怒られた」
「マクロ有効プレゼンテーション(`.ppsm` / `.pptm`)で保存する際に、定数を切り替えるのを忘れて通常のプレゼンテーションになってしまった」
こうしたヒューマンエラーや想定外の入力を、場当たり的な `On Error Resume Next` で握りつぶすのは、プロフェッショナルなエンジニアリングとは言えない。
今回は、出力先ファイルパスの拡張子を動的に解析し、対応する `PpSaveAsFileType` を完全自動マッピングすることで「保存エラーをゼロにする」、実務で即戦力となる堅牢な汎用保存ラッパーの設計思想と実装コードを伝授する。
—
1. なぜ標準の `SaveAs` では不十分なのか?
PowerPointのオブジェクトモデルにおいて、`Presentation.SaveAs` は以下のような構文を持つ。
expression.SaveAs(FileName, FileFormat, EmbedTrueTypeFonts)
ここで最大の罠となるのが第2引数の `FileFormat`(`PpSaveAsFileType` 列挙型)だ。
ファイルパス(`FileName`)に `.pdf` と指定したとしても、第2引数に `ppSaveAsDefault`(通常は `.pptx`)を渡したままにすると、PowerPointは内部矛盾を起こすか、意図しないバイナリ形式で保存しようとして実行時エラーを吐く。
つまり、「ファイルパスの拡張子」と「ファイルフォーマット定数」の整合性は、常に開発者側が担保しなければならない。これを呼び出し側のスクリプトごとにイチイチ条件分岐させて書いていたら、コードベースが汚染され、保守性も最悪になる。
だからこそ、パスを渡すだけで適切な定数を裏側で自動判別・適用してくれる「汎用保存ラッパー」を共通モジュールとして持たせておく必要があるのだ。
—
2. 堅牢な汎用保存ラッパーの設計要件
プロダクションコードとして耐えうるラッパー関数を作るためには、以下の要件を満たす必要がある。
1. 大文字小文字の吸収: ユーザーが `.PDF` と書いても `.pdf` と書いても正しく判定できること。
2. 未知の拡張子の排除: 対応していない拡張子が渡された場合、保存処理を実行する前に明確なエラーを投げること。
3. 上書き保存時の警告制御: 既存ファイルを上書きする際のExcel/PowerPoint特有のダイアログを適切に制御、あるいは安全に処理できること。
4. フォント埋め込みのオプション化: 必要に応じてファイルサイズと互換性のバランスを取れること。
—
3. プロダクションコード実装
以下のコードを、標準モジュール(例: `ModPresentationIO` など)にそのままコピーしてほしい。実務の現場で直面するあらゆるエッジケースを考慮し、堅牢に組み上げている。
Option Explicit
/
- プレゼンテーションを指定パスの拡張子に合わせて自動で保存する汎用ラッパー
- @param targetPres 対象のPresentationオブジェクト
- @param filePath 保存先のフルパス(拡張子を含む)
- @param embedFonts Trueの場合、TrueTypeフォントを埋め込む(デフォルト: False)
- @return 成功時はTrue、失敗時はFalse
/
Public Function SavePresentationUniversal( _
ByVal targetPres As PowerPoint.Presentation, _
ByVal filePath As String, _
Optional ByVal embedFonts As Boolean = False) As Boolean
SavePresentationUniversal = False
‘ 1. 引数のバリデーション
If targetPres Is Nothing Then
MsgBox “保存対象のプレゼンテーションオブジェクトが無効です。”, vbCritical, “ラッパーエラー”
Exit Function
End If
If Trim(filePath) = “” Then
MsgBox “保存先のファイルパスが指定されていません。”, vbCritical, “ラッパーエラー”
Exit Function
End If
‘ 2. 拡張子の抽出と正規化(小文字に統一)
Dim ext As String
ext = LCase(Mid(filePath, InStrRev(filePath, “.”)))
‘ 3. 拡張子に応じた PpSaveAsFileType の動的マッピング
Dim fileFormat As PpSaveAsFileType
Select Case ext
Case “.pptx”
fileFormat = ppSaveAsDefault 恵まれた標準形式
Case “.pptm”
fileFormat = ppSaveAsPresentationMacroEnabled マクロ有効プレゼンテーション
Case “.ppt”
fileFormat = ppSaveAsPresentation9795 旧97-2003形式
Case “.pdf”
fileFormat = ppSaveAsPDF PDF形式
Case “.xps”
fileFormat = ppSaveAsXPS XPS形式
Case “.ppsx”
fileFormat = ppSaveAsShow スライドショー
Case “.ppsm”
fileFormat = ppSaveAsShowMacroEnabled マクロ有効スライドショー
Case “.potx”
fileFormat = ppSaveAsTemplate デザインテンプレート
Case “.potm”
fileFormat = ppSaveAsTemplateMacroEnabled マクロ有効テンプレート
Case “.thmx”
fileFormat = ppSaveAsTheme テーマ
Case “.jpg”, .jpeg, “.png”, .gif, .bmp”
‘ 画像形式での保存は全スライド出力になるため、特殊なハンドリングが必要な場合はここで分岐
‘ 今回は基本フォーマットの網羅に主眼を置くため標準サポート外とするか、適宜追加
MsgBox “画像形式(” & ext & “)での一括保存は対応していません。専用のエクスポート処理を呼び出してください。”, vbExclamation, “フォーマットエラー”
Exit Function
Case Else
MsgBox “未対応の拡張子です: ” & ext, vbCritical, “フォーマットエラー”
Exit Function
End Select
‘ 4. 保存実行とエラーハンドリング
On Error GoTo ErrorHandler
‘ 既存ファイルが存在する場合の警告を抑制するため、必要に応じた事前削除や
‘ アプリケーション設定の調整(DisplayAlertsなどはないためエラートラップで対応)
targetPres.SaveAs FileName:=filePath, _
FileFormat:=fileFormat, _
EmbedTrueTypeFonts:=IIf(embedFonts, msoTrue, msoFalse)
‘ 成功
SavePresentationUniversal = True
Exit Function
ErrorHandler:
‘ 予期せぬエラー(ファイルが別プロセスで開かれている、書き込み権限がない等)
MsgBox “ファイルの保存中にエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “保存致命的エラー”
SavePresentationUniversal = False
End Function
—
4. 呼び出し側の実装例
このラッパー関数を利用する側(メインの業務自動化スクリプト)は、驚くほどシンプルかつ安全になる。ファイル形式を意識する必要は一切なく、ただ「出力したいパス」を渡すだけだ。
Sub Example_Usage()
Dim wsPath As String
wsPath = “C:\Reports\”
‘ ディレクトリの存在チェック(実務では必須)
If Dir(wsPath, vbDirectory) = “” Then
MkDir wsPath
End If
Dim targetPres As PowerPoint.Presentation
Set targetPres = ActivePresentation
‘ パターンA: 通常のPowerPoint形式で保存
Dim pathPptx As String
pathPptx = wsPath & “MonthlyReport.pptx”
If SavePresentationUniversal(targetPres, pathPptx, embedFonts:=True) Then
Debug.Print “PPTXの保存に成功しました。”
End If
‘ パターンB: 同じプレゼンテーションをPDFとして即座に別名保存
Dim pathPdf As String
pathPdf = wsPath & “MonthlyReport.pdf”
If SavePresentationUniversal(targetPres, pathPdf) Then
Debug.Print “PDFの出力に成功しました。”
End If
End Sub
—
5. チーフアーキテクトからの実務アドバイス
1. ネットワークドライブ(UNCパス)への保存の罠
社内サーバー(`\\server\share\…`)やOneDrive/SharePointの同期フォルダに対してVBAから直接保存を行う場合、ファイルのロックやタイムアウトが発生しやすい。実務でバッチ処理を組む際は、一度ローカルのテンポラリフォルダ(`Environ(“TEMP”)`)にラッパー経由で保存し、その後 `FileCopy` や `FileSystemObject` でリモートへ転送する二段構えのアーキテクチャにすると、ネットワーク起因の謎のエラーを完全に撲滅できる。
2. マクロのセキュリティコンテキスト
`.pptm` や `.ppsm` で保存する際、VBAプロジェクトが含まれていない状態でこれらの拡張子を指定すると、PowerPointが警告を出したり挙動が不安定になることがある。マクロを保持したプレゼンテーションを扱う場合は、必ず元のファイルがマクロ対応形式であるか、あるいは信頼できるパスからの実行であることを前提に設計すること。
総括
コードの美しさは、「変更に強く、エラーを未然に防ぐ構造」から生まれる。
今回紹介した汎用保存ラッパーを取り入れることで、ファイル保存周りのくだらないバグ対応工数をゼロにし、本質的な業務ロジックの構築に集中してほしい。プロフェッショナルなVBA開発者であれば、こうした細部にこそ魂を宿すべきである。
