【Project VBA】フォルダ内.mpp一括処理の深淵:COMオブジェクトの完全破棄とメモリリークを防ぐバッチループ設計
Microsoft Project(以下、MS Project)におけるVBA自動化は、ExcelやWordのそれとは根本的に異なる重厚なアーキテクチャの上に成り立っています。スケジュール計算エンジン、リソースのレベル付け、複雑な依存関係のリアルタイム演算――これらがバックグラウンドで常時稼働しているため、単に「ファイルをループで開いて閉じる」だけの処理であっても、実装を誤れば容易にメモリリークを引き起こし、`WINPROJ.EXE` のゴーストプロセスを量産することになります。
本記事では、初心者向けとされる「`Dir` 関数を用いた特定フォルダ内の `.mpp` ファイル一括開閉処理」を題材に、エンタープライズ環境の保守に耐えうる完全なメモリ管理と堅牢な例外処理を組み込んだ極限のプログラミングパターンを解説します。
—
1. なぜ「単なるループ処理」でシステムが沈没するのか
MS ProjectのCOMオブジェクトモデルは極めて繊細です。標準的なVBAコードでありがちな「開いて、処理して、閉じずに次へ行く」「オブジェクト変数に `Nothing` を代入しない」といった実装は、小規模なプロジェクトファイル数件程度なら動作します。しかし、数十〜数百の `.mpp` ファイルを連続処理した瞬間、システムは以下の致命的トラブルに見舞われます。
1. `WINPROJ.EXE` プロセスの残存とメモリ枯渇
ファイル参照が内部的に残存し、OSのタスクマネージャー上に非表示の `WINPROJ.EXE` が大量に留まる。
2. 自動計算エンジン(Scheduling Engine)の暴走
ファイルを開くたびにクリティカルパスの再計算が走り、I/O負荷とCPU使用率が100%に張り付く。
3. モーダルダイアログによるスクリプト停止
「リソースの参照更新」や「プレースホルダーの警告」など、UI側の介入要求によって自動処理が永久に停止する。
プロのアーキテクチャ設計においては、これらのリスクをコードレベルで完全に遮断しなければなりません。
—
2. 堅牢なる一括開閉処理の実装パターン
以下に示すコードは、指定フォルダ内の `.mpp` ファイルを順次読み込み、メモリを完全に解放しながら開閉を行うプロダクション水準のVBAプログラムです。
Option Explicit
‘ ==============================================================================
‘ 処理名:BatchProcessProjectFiles
‘ 概要 :指定フォルダ内の全.mppファイルを安全に巡回開閉する
‘ 特徴 :画面更新・自動計算の停止、完全なCOMオブジェクト破棄、堅牢なエラーハンドリング
‘ ==============================================================================
Public Sub BatchProcessProjectFiles()
Dim targetFolderPath As String
Dim fileName As String
Dim fullPath As String
Dim currentProj As MSProject.Project
Dim processedCount As Long
Dim errorCount As Long
‘ 1. 対象フォルダの定義(末尾の円マーク/スラーシュを補正)
targetFolderPath = “C:\ProjectFiles\QuarterlyReport\”
If Right$(targetFolderPath, 1) <> “\” Then
targetFolderPath = targetFolderPath & “\”
End If
‘ フォルダの存在確認
If Dir$(targetFolderPath, vbDirectory) = “” Then
MsgBox “指定されたフォルダが存在しません: ” & targetFolderPath, vbCritical, “エラー”
Exit Sub
End If
‘ 2. MS Project アプリケーション環境の最適化(高速化とUI割り込みの防止)
On Error GoTo CriticalError
With MSProject.Application
.ScreenUpdating = False ‘ 画面描画の停止
.DisplayAlerts = False ‘ アラートダイアログの抑制
.Calculation = pjManual ‘ 再計算を手動に変更(読み込み速度の激変)
End With
‘ 3. Dir関数によるファイル検索ループの開始
fileName = Dir$(targetFolderPath & “.mpp”, vbNormal)
processedCount = 0
errorCount = 0
Do While Len(fileName) > 0
fullPath = targetFolderPath & fileName
‘ 個別ファイル処理のエラーハンドリングブロック
On Error Resume Next
‘ ファイルの非表示オープン(読み取り専用、パスワード要求の回避など)
‘ ※ FileOpenEx メソッドを使用し、詳細パラメータを明示制御する
MSProject.Application.FileOpenEx _
Name:=fullPath, _
ReadOnly:=True, _
IgnoreReadOnlyRecommended:=True, _
NoBarCheck:=True
If Err.Number <> 0 Then
‘ 読み込み失敗時のログ処理(必要に応じてファイルログ等へ出力)
Debug.Print “[ERROR] 開くことができませんでした: ” & fullPath & ” (Code: ” & Err.Number & “)”
errorCount = errorCount + 1
Err.Clear
Else
‘ 正常に開けた場合のオブジェクト参照取得
Set currentProj = MSProject.Application.ActiveProject
‘ ——————————————————————
‘ 【ここに各ファイルに対する具体的な業務ロジックを記述する】
‘ 例: 各タスクの完了率チェック、カスタムフィールドの抽出など
‘ ——————————————————————
Debug.Print “[SUCCESS] 処理完了: ” & currentProj.Name
processedCount = processedCount + 1
‘ ファイルを保存せずに安全に閉じる
‘ FileCloseEx pjDoNotSave を明示指定する
MSProject.Application.FileCloseEx Save:=pjDoNotSave, NoPrompt:=True
End If
‘ 4. COMオブジェクトの明示的解放(メモリリーク防止の肝)
Set currentProj = Nothing
‘ 次のファイルを検索
On Error GoTo CriticalError
fileName = Dir$()
Loop
‘ 5. 処理結果の出力
MsgBox “一括処理が完了しました。” & vbCrLf & _
“成功: ” & processedCount & ” 件” & vbCrLf & _
“失敗: ” & errorCount & ” 件”, vbInformation, “処理完了”
CleanUp:
‘ 6. アプリケーション状態の完全復元(後処理)
On Error Resume Next
With MSProject.Application
.ScreenUpdating = True
.DisplayAlerts = True
.Calculation = pjAutomatic
End With
Exit Sub
CriticalError:
MsgBox “致命的なシステムエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
—
3. チーフアーキテクトが解説する実装の核心
上記のコードには、一般的なサンプルコードには見られない「レガシーシステムを落とさないための防御的アプローチ」が凝縮されています。
① `FileOpenEx` と `FileCloseEx` の厳密な使用
MS Project標準の `Projects.Open` ではなく、拡張メソッドである `FileOpenEx` を採用しています。
- `ReadOnly:=True`: 他ユーザーの排他ロックを回避し、共有サーバー上のファイルに対する読み込み失敗を防ぎます。
- `NoBarCheck:=True`: 統合環境(Enterprise Custom Fields等)との同期チェックをスキップさせ、ロード時間を極限まで削減します。
- `Save:=pjDoNotSave`: 保存処理を明示的に拒否することで、不要なファイル更新日時変更やメタデータの汚染を防ぎます。
② 計算エンジン(`pjManual`)の制御
MS Projectの真骨頂であるスケジューリングエンジンは、バッチ処理においては最大の敵となります。`Application.Calculation = pjManual` を設定せずに100個のファイルを連続で開いた場合、各ファイルを開くたびに先行・後続タスクの再計算が実行され、処理時間は数倍〜数十倍に膨れ上がります。開いて情報を取得するだけであれば、手動計算モードへの切り替えは必須です。
③ COM参照の明示的破棄 (`Set currentProj = Nothing`)
VBAのガベージコレクションは頼りになりません。特にMS Projectの `Project` オブジェクトは、内部でC++の重厚なCOMコンポーネントと紐付いています。明示的に `Set currentProj = Nothing` を実行しない場合、ループが回転するにつれて参照カウントが残り続け、最終的にメモリ領域(Heap)を使い果たします。
—
4. さらに高次元の保守性を目指すエンジニアへ(Windows APIの活用)
VBA組み込みの `Dir` 関数は、手軽である反面、大きな弱点を抱えています。
それは「260文字を超えるロングパス(UNCパス含む)のハンドリング不全」と「マルチスレッド・再帰処理との相性の悪さ」です。
エンタープライズの複雑なネットワークドライブ構造を安全に走査する場合、システム管理者は標準の `Dir` を捨て、`kernel32.dll` の Windows API (`FindFirstFileW` / `FindNextFileW`) を直接呼び出すか、`Scripting.FileSystemObject` (FSO) へ移行すべきです。
[参考] Windows API によるUnicode対応ファイル検索の宣言部
If VBA7 Then
Private Declare PtrSafe Function FindFirstFileW Lib “kernel32” ( _
ByVal lpFileName As LongPtr, _
ByRef lpFindFileData As WIN32_FIND_DATAW) As LongPtr
Private Declare PtrSafe Function FindNextFileW Lib “kernel32” ( _
ByVal hFindFile As LongPtr, _
ByRef lpFindFileData As WIN32_FIND_DATAW) As Long
Private Declare PtrSafe Function FindClose Lib “kernel32” ( _
ByVal hFindFile As LongPtr) As Long
Else
‘ 32ビットレガシー環境互換の宣言
Private Declare Function FindFirstFileW Lib “kernel32” ( _
ByVal lpFileName As Long, _
ByRef lpFindFileData As WIN32_FIND_DATAW) As Long
‘ …(省略)
End If
超大規模なプロジェクト資産のバッチ処理や、階層の深いディレクトリスキャンを行う場合は、これらのAPIを用いてパス文字制限(`MAX_PATH` = 260文字)を無効化(`\\?\` プレフィックスの活用)した設計を検討してください。
—
5. まとめ
- 画面更新と自動計算はループ前に必ず切る (`pjManual`)
- ファイルを開く際は `FileOpenEx` でダイアログと排他制御を抑制する
- ループ内部で割り当てたオブジェクト変数は、必ず `Set = Nothing` で破棄する
- 例外が発生しても、必ず `CleanUp` セクションを通過させて環境設定を元に戻す
プロジェクト管理の現場において、自動化プログラムの停止は業務の停止を意味します。初心者向けとされる「ファイルのループ開閉」こそ、COMモデルのライフサイクルとMS Project特有の仕様を深く理解した、妥協のないコーディングを行ってください。
