「マクロの記録」を卒業しよう。FSOで実現する、フォルダ階層を完全維持したPDF一括変換術
こんにちは。エンジニアの皆さん、日々の業務自動化、楽しんでいますか?
「マクロの記録」を使って、ボタン一つで何かが動く感動を覚えたあの日から、一歩踏み出したい。そう感じているあなたへ。今日は、単なる操作の自動化を超えた、「構造を理解し、システムを操る」ための極めて実践的な技術をお話ししましょう。
今回挑戦するのは、「複雑なサブフォルダ構造をそのままに、中にある全てのPowerPointファイルをPDFへ一括変換する」というミッションです。
「ファイルを開いて、名前を付けて保存でPDFを選ぶ……」
この作業を、フォルダが100個、ファイルが1000個あったとしても、一瞬で、しかも元のフォルダ構成を完璧に維持したまま終わらせる。そんな魔法のようなツールを、VBAの真の力である「FSO(FileSystemObject)」を使って構築していきます。
ここをクリアすれば、あなたのVBAスキルは「記録レベル」から「設計レベル」へと劇的に進化しますよ。
1. なぜ「Dir関数」ではなく「FSO」を使うのか?
多くの初学者が最初に学ぶのは、VBA標準の`Dir`関数です。確かに`Dir`は軽量で、特定のフォルダ内のファイルを探すには便利です。しかし、今回のような「サブフォルダの中にさらにサブフォルダがある……」という深い階層(ツリー構造)を探索する場合、`Dir`関数は途端に扱いにくくなります。
そこで登場するのが、FileSystemObject (FSO) です。
FSOは、ファイルやフォルダを単なる「文字列(パス)」としてではなく、「オブジェクト」として扱います。
- フォルダは「フォルダオブジェクト」
- ファイルは「ファイルオブジェクト」
これらは「自分の中にどんなサブフォルダを持っているか?」「親フォルダはどこか?」といった属性(プロパティ)を持っています。この「オブジェクト指向」の考え方を取り入れることで、複雑な階層構造をまるで迷路を歩くように、エレガントに探索できるようになるのです。
2. アルゴリズムの核心:「再帰処理」を理解する
このツールの心臓部は、「再帰処理(Recursion)」というテクニックです。
再帰処理とは、「関数の中で、自分自身を再び呼び出す」手法のこと。イメージしてください。
1. あるフォルダに入る。
2. その中にある「ファイル」を見つけたら、PDFに変換する。
3. その中にある「フォルダ」を見つけたら、「そのフォルダに対して、手順1に戻って同じことをする」。
この「自分自身を呼び出す」動作を繰り返すことで、どれほど深い階層であっても、プログラムは自動的に末端まで辿り着くことができるのです。
3. 実装コード:階層維持型エクスポートツール
それでは、魂を込めて書き上げたコードを見ていきましょう。このコードは、コピー&ペーストでそのまま動作するように設計していますが、各行に「なぜそう書くのか」という意図を込めています。
Option Explicit
' ======================================================================================
' 階層維持型 PowerPoint to PDF 一括変換ツール
' 概要: 指定したルートフォルダ以下の全PPTXを、構造を維持したままPDFへ変換する
' ======================================================================================
Sub StartBatchExport()
Dim fso As Object
Dim sourceRoot As String
Dim destRoot As String
Dim sourceFolder As Object
' 1. 準備: FileSystemObjectの生成
Set fso = CreateObject("Scripting.FileSystemObject")
' 2. 対象フォルダの選択(ユーザーに選んでもらう)
MsgBox "変換したいPowerPointが入っている「元のフォルダ」を選択してください。"
sourceRoot = SelectFolder()
If sourceRoot = "" Then Exit Sub
MsgBox "PDFを保存したい「出力先フォルダ」を選択してください。"
destRoot = SelectFolder()
If destRoot = "" Then Exit Sub
' 3. 実行開始
On Error GoTo ErrorHandler
Application.ScreenUpdating = False ' 画面更新を止めて高速化
Set sourceFolder = fso.GetFolder(sourceRoot)
' 再帰処理の開始
ProcessFolder fso, sourceFolder, sourceRoot, destRoot
Application.ScreenUpdating = True
MsgBox "すべての変換が完了しました!お疲れ様でした。", vbInformation
Exit Sub
ErrorHandler:
Application.ScreenUpdating = True
MsgBox "予期せぬエラーが発生しました:" & vbCrLf & Err.Description, vbCritical
End Sub
' --------------------------------------------------------------------------------------
' 再帰的にフォルダを探索し、変換処理を行うメインエンジン
' --------------------------------------------------------------------------------------
Sub ProcessFolder(fso As Object, currentFolder As Object, sourceRoot As String, destRoot As String)
Dim subFolder As Object
Dim file As Object
Dim relativePath As String
Dim targetDestFolder As String
' A. 現在のフォルダの「相対パス」を計算する(ここが階層維持の肝!)
' 例: sourceRootが "C:\Work"、currentFolderが "C:\Work\ProjectA\Doc" なら
' relativePath は "\ProjectA\Doc" になる
relativePath = Replace(currentFolder.Path, sourceRoot, "")
' B. 出力先の対応するフォルダパスを作成
targetDestFolder = destRoot & relativePath
' C. 出力先フォルダが存在しなければ作成する
If Not fso.FolderExists(targetDestFolder) Then
' 階層を一段ずつ作るために、再帰的にフォルダ作成を行う必要があるが、
' 今回はシンプルにするため、FSOの仕組みを利用して作成(※実際には親から順に作る必要がある)
CreateFolderHierarchy fso, targetDestFolder
End If
' D. 現在のフォルダ内の「ファイル」をチェック
For Each file In currentFolder.Files
' 拡張子が .pptx または .ppt の場合のみ処理
If LCase(fso.GetExtensionName(file.Name)) Like "ppt*" Then
ConvertPPTToPDF file.Path, targetDestFolder, fso
End If
Next file
' E. 「サブフォルダ」に対して自分自身を呼び出す(再帰!)
For Each subFolder In currentFolder.SubFolders
ProcessFolder fso, subFolder, sourceRoot, destRoot
Next subFolder
End Sub
' --------------------------------------------------------------------------------------
' 個別のファイル変換処理
' --------------------------------------------------------------------------------------
Sub ConvertPPTToPDF(filePath As String, destFolderPath As String, fso As Object)
Dim pptApp As Object
Dim pres As Object
Dim fileNameNoExt As String
Dim pdfPath As String
On Error Resume Next ' 個別のファイルエラーで全体を止めないためのガード
' ファイル名から拡張子を除いた名前を取得
fileNameNoExt = fso.GetBaseName(filePath)
' 出力先のPDFパスを組み立て
pdfPath = destFolderPath & "\" & fileNameNoExt & ".pdf"
' PowerPointをバックグラウンドで開く
' ※既にPowerPointが起動している場合は、既存のインスタンスを利用
Set pptApp = Application
Set pres = pptApp.Presentations.Open(Filename:=filePath, ReadOnly:=True, WithWindow:=msoFalse)
If Not pres Is Nothing Then
' PDFとしてエクスポート
pres.ExportAsFixedFormat Path:=pdfPath, FixedFormatType:=2 ' 2 = ppFixedFormatTypePDF
' 重要: オブジェクトの解放(メモリリーク防止)
pres.Close
Set pres = Nothing
Else
Debug.Print "ファイルを開けませんでした: " & filePath
End If
On Error GoTo 0
End Sub
' --------------------------------------------------------------------------------------
' フォルダ階層を一段ずつ作成する補助関数
' --------------------------------------------------------------------------------------
Sub CreateFolderHierarchy(fso As Object, fullPath As String)
Dim parts As Variant
Dim i As Integer
Dim buildPath As String
' パスを "\" で分割
parts = Split(fullPath, "\")
buildPath = parts(0)
For i = 1 To UBound(parts)
If parts(i) <> "" Then
buildPath = buildPath & "\" & parts(i)
If Not fso.FolderExists(buildPath) Then
fso.CreateFolder buildPath
End If
End If
Next i
End Sub
' --------------------------------------------------------------------------------------
' フォルダ選択ダイアログを表示する関数
' --------------------------------------------------------------------------------------
Function SelectFolder() As String
Dim fd As FileDialog
Set fd = Application.FileDialog(msoFileDialogFolderPicker)
If fd.Show = -1 Then
SelectFolder = fd.SelectedItems(1)
Else
SelectFolder = ""
End If
End Function
4. アーキテクトの視点:ここがプロの分かれ目
コードを書き終えただけでは、まだ「動くものを作った」に過ぎません。プロのエンジニアとして、以下の3つのポイントを意識してください。
① オブジェクトのライフサイクル(メモリ管理)
`ConvertPPTToPDF` 関数の中に、`pres.Close` と `Set pres = Nothing` を書きましたね。これは極めて重要です。
大量のファイルを処理する際、開いたプレゼンテーションを閉じずに放置すると、PowerPointのメモリ消費量が膨れ上がり、最終的に「メモリ不足」でクラッシュします。オブジェクトは「使い終わったら、確実に手放す」。これが大規模自動化の鉄則です。
② 階層維持のロジック(相対パスの概念)
今回の最大の難所は、`relativePath = Replace(currentFolder.Path, sourceRoot, “”)` の部分です。
「元のルートから見て、今いる場所はどこか?」という相対的な位置関係を算出することで、出力先でも全く同じ構造を再現しています。この「絶対パス」と「相対パス」の使い分けができるようになると、ファイル操作の自由度が飛躍的に上がります。
③ エラーハンドリングの「優しさ」
`On Error Resume Next` を単なる「エラー無視」として使うのは二流です。
今回のコードでは、「一つのファイルが壊れていても、全体の処理を止めずに、次のファイルへ進む」ために使用しています。業務ツールにおいて、1000個のうち1個のエラーで全停止してしまうツールは、現場では使い物になりません。エラーを適切に制御し、堅牢なシステムを作る。これが中級者への入り口です。
最後に
いかがでしたか?
「フォルダを巡回し、構造を維持しながら、オブジェクトを制御して変換する」。
一見難しそうに見えますが、分解して一つずつ積み上げれば、決して到達不可能な壁ではありません。
このスクリプトをベースに、例えば「変換した後に元のファイルをゴミ箱に移す」機能や、「変換ログをExcelに出力する」機能などを追加してみてください。自分で機能を拡張していくプロセスこそが、あなたを真のエンジニアへと育ててくれます。
さあ、次はあなたが、このコードを自分なりに進化させる番です。応援していますよ!
