【入門編】【中級者】FSOを活用し、指定フォルダ以下のサブフォルダ構造を完全に維持したまま全PPTXをPDFへ一括変換する階層維持エクスポートツール – PowerPoint VBA解析バイブル

スポンサーリンク
スポンサーリンク

「マクロの記録」を卒業しよう。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に出力する」機能などを追加してみてください。自分で機能を拡張していくプロセスこそが、あなたを真のエンジニアへと育ててくれます。

さあ、次はあなたが、このコードを自分なりに進化させる番です。応援していますよ!

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