PowerPoint VBAを極限まで加速する:マルチスレッド風・非同期シーケンス制御による数百ファイルの一括PDF超高速化基盤
こんにちは。チーフアーキテクトの私だ。
日々の業務で、数百枚あるいは数千枚のPowerPoint(PPTX)ファイルをPDFに一括変換するタスクに直面したことはないだろうか?
素朴なVBAコードでこれをやろうとすると、こうなる。
‘ 【アンチパターン】シングルスレッドによる絶望的な順次処理
Dim fileName As Variant
For Each fileName In fileList
Set pptApp = CreateObject(“PowerPoint.Application”)
Set pres = pptApp.Presentations.Open(fileName)
pres.SaveAs outputPath, ppSaveAsPDF
pres.Close
pptApp.Quit
Next fileName
愚か者のコードだ。これではCPUの1コアしか使えず、モダンなマルチコアCPUの9割が遊んでいる。しかも、PowerPointの重厚長大なCOMオブジェクトが毎ループ起動・破棄を繰り返すため、メモリリークの温床となり、中盤から激しいスラッシングを引き起こしてフリーズする。
プロのエンジニアなら、この非効率を許してはならない。
今回は、VBAとVBScriptの非同期連携によって擬似的なマルチスレッド環境を構築し、複数コアをフル稼働させて数百ファイルのPPTXを限界突破のスピードでPDF化する「超大型バッチ処理基盤」の設計思想と実装を叩き込む。
—
1. アーキテクチャの全体像:なぜ「VBA + VBScript」なのか?
VBA単体では真のマルチスレッド(別スレッドでの処理実行)を行うことは言語仕様上不可能である(DoEventsによる簡易的な非同期はあるが、重いCOM処理の並列化には無力だ)。
そこで、以下のアーキテクチャを採用する。
1. オーケストレーター(VBA): 処理すべきファイルリストを管理し、ワーカースクリプトへ動的にタスクを割り振る。
2. ワーカー(VBScript): VBAから独立したプロセス(`WScript.Shell`の`.Run`メソッド非同期実行)として起動し、割り当てられたPPTXを静かに、かつ高速にPDF化して自ら消滅する。
3. プロセス数制御(セマフォ制御の擬似実装): CPUの論理コア数に応じた「同時実行上限数(例: 4〜8プロセス)」を設け、リソース枯渇を防ぎながら限界までスループットを引き上げる。
—
2. 実装コード:プロダクション・グレードの基盤
現場でそのままコピー&ペーストして使える、実用性と堅牢性を極限まで高めたコードを提示する。
コアモジュール 1:オーケストレーター(VBA側)
このモジュールをExcelまたはPowerPointの標準モジュールに配置し、`ExecuteParallelPDFConversion`を実行せよ。
Option Explicit
‘ ==============================================================================
‘ 超高速並列PDF変換オーケストレーター
‘ ==============================================================================
Public Sub ExecuteParallelPDFConversion()
Dim targetDir As String
Dim outputDir As String
Dim fso As Object
Dim folder As Object
Dim file As Object
Dim vbsPath As String
Dim shell As Object
Dim runningTasks As Collection
Dim maxConcurrent As Integer
Dim wshScriptExec As Object
‘ — 設定エリア —
targetDir = “C:\PresentationFiles\” ‘ 変換元PPTXフォルダ
outputDir = “C:\PresentationFiles\PDF\” ‘ 出力先PDFフォルダ
maxConcurrent = 4 ‘ 同時実行プロセス数(CPUコア数に応じて調整)
‘ ——————
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(outputDir) Then fso.CreateFolder (outputDir)
‘ 一時VBScriptワーカーのパス生成
vbsPath = Environ(“TEMP”) & “\ppt_worker.vbs”
Call CreateWorkerScript(vbsPath)
Set shell = CreateObject(“WScript.Shell”)
Set runningTasks = New Collection
Dim startTime As Double
startTime = Timer
MsgBox “並列PDF変換バッチを開始します。対象フォルダ: ” & targetDir, vbInformation
Set folder = fso.GetFolder(targetDir)
For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Name)) = “pptx” Then
Dim pdfPath As String
pdfPath = outputDir & fso.GetBaseName(file.Name) & “.pdf”
‘ 既にPDFが存在する場合はスキップ(冪等性の担保)
If Not fso.FileExists(pdfPath) Then
‘ プロセス上限に達している場合は空きが出るまで待機
Do While runningTasks.Count >= maxConcurrent
Call CleanFinishedTasks(runningTasks)
DoEvents
Application.Wait (Now + TimeValue(“0:00:01”))
Loop
‘ VBScriptを非同期(Wait = False, WindowStyle = 0)で起動
Dim cmd As String
cmd = “wscript.exe “”” & vbsPath & “”” “”” & file.Path & “”” “”” & pdfPath & “”””
‘ WScript.Shell.Run (Command, WindowStyle, WaitOnReturn)
‘ 0 = 非表示ウィンドウ, False = 非同期実行
Set wshScriptExec = shell.Exec(cmd)
runningTasks.Add wshScriptExec
End If
End If
Next file
‘ 全ての残存タスクの完了を待つ
Do While runningTasks.Count > 0
Call CleanFinishedTasks(runningTasks)
DoEvents
Application.Wait (Now + TimeValue(“0:00:01”))
Loop
‘ ワーカーのクリーンアップ
If fso.FileExists(vbsPath) Then fso.DeleteFile vbsPath, True
MsgBox “すべての変換が完了しました。処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, vbInformation
End Sub
‘ 終了したプロセスをコレクションからパージするヘルパー関数
Private Sub CleanFinishedTasks(ByRef tasks As Collection)
Dim i As Long
For i = tasks.Count To 1 Step -1
‘ Status = 0 は実行中, Status = 1 は終了
If tasks(i).Status = 1 Then
tasks.Remove i
End If
Next i
End Sub
‘ ワーカーとなるVBScriptを動的に生成する関数
Private Sub CreateWorkerScript(ByVal path As String)
Dim fso As Object, ts As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.CreateTextFile(path, True, False)
ts.WriteLine “If WScript.Arguments.Count < 2 Then WScript.Quit"
ts.WriteLine "pptPath = WScript.Arguments(0)"
ts.WriteLine "pdfPath = WScript.Arguments(1)"
ts.WriteLine ""
ts.WriteLine "On Error Resume Next"
ts.WriteLine "Dim pptApp, pres"
ts.WriteLine "Set pptApp = CreateObject(""PowerPoint.Application"")"
ts.WriteLine "If Err.Number <> 0 Then WScript.Quit”
ts.WriteLine “pptApp.Visible = False”
ts.WriteLine “pptApp.DisplayAlerts = 0”
ts.WriteLine “”
ts.WriteLine “Set pres = pptApp.Presentations.Open(pptPath, True, False, False)”
ts.WriteLine “If Err.Number = 0 Then”
ts.WriteLine ” pres.SaveAs pdfPath, 32 ‘ 32 = ppSaveAsPDF”
ts.WriteLine ” pres.Close”
ts.WriteLine “End If”
ts.WriteLine “”
ts.WriteLine “pptApp.Quit”
ts.WriteLine “Set pres = Nothing”
ts.WriteLine “Set pptApp = Nothing”
ts.Close
End Sub
—
3. 現場で絶対に踏んではならない「地雷」と回避策
このレベルのバッチ基盤を実運用に投入する場合、理論通りにいかない「ハードウェアおよびOSの罠」が存在する。チーフとして、事前に潰すべきポイントを伝授する。
① PowerPointの「ファイルロック」と競合問題
複数プロセスが同時に起動すると、稀に同一ファイルへの同時アクセスや、PowerPointが内部生成する一時ファイル(`~$filename.pptx`)の競合が発生する。
- 対策: `Presentations.Open` の引数を厳密に設定せよ。
`Presentations.Open(FileName, ReadOnly, Untitled, WithWindow)`
- `ReadOnly := True`(読み取り専用で開き、元ファイルを保護&ロック回避)
- `WithWindow := False`(ウィンドウを表示しないことで描画コストと競合を排除)
② COMのゾンビ化とメモリリークの温床
VBScript側で万が一エラーが発生して `pptApp.Quit` が実行されない場合、Windowsのバックグラウンドに「幽霊プロセス(POWERPNT.EXE)」が残り続け、メモリを食いつぶす。
- 対策: VBScript内に `On Error Resume Next` を適切に配置し、異常終了時であっても確実にCOMオブジェクトを解放してプロセスが消滅するように担保している(上記の生成コードには組み込み済みだ)。
③ ディスクI/Oの飽和とプロセス数のチューニング
`maxConcurrent`(同時実行数)を「8」や「16」などと欲張って大きく設定しすぎると、HDD/SSDの読み書き(I/O)がボトルネックになり、かえってコンテキストスイッチのオーバーヘッドが増大して処理速度が落ちる。
- 推奨値:
- 高速なNVMe SSD環境: 論理プロセッサ数の1/2 〜 同数(例: 4〜8)
- 一般的なSATA SSD/HDD環境: 2 〜 4
—
4. チーフアーキテクトからの総括
今回構築した「VBAオーケストレーター + 動的VBScriptワーカー」による非同期シーケンス制御は、PowerPoint自動化の限界を突破するための強力な武器となる。
VBAはもはや「おもちゃのマクロ言語」ではない。アーキテクチャさえ正しく設計すれば、企業インフラの基幹バッチ処理をも凌駕するハイパフォーマンスな自動化エンジンへと昇華させることができる。
あなたのプロジェクトにこの設計を導入し、圧倒的な処理速度と安定性を手に入れてほしい。健闘を祈る。
