Project VBAを掌握する極限の知見:肥大化したリソースカレンダーの外科的クリーンアップ
Microsoft ProjectのVBA開発において、避けて通れないのが「リソースとカレンダーの肥大化」という構造的な呪縛である。
プロジェクトが長期化し、リソースの追加・削除、組織変更、外部リソースのインポートを繰り返すうちに、Projectファイル(.mpp)の内部データベースには「幽霊のような」未使用の例外日、不要なベースカレンダー、そして孤立したリソース割り当てデータが蓄積されていく。
これらはファイルサイズを膨らませるだけでなく、計算エンジンのパフォーマンスを著しく低下させ、最悪の場合はファイル破損(XML破損や内部ポインタの不整合)を引き起こす。
本稿では、Project VBAのオブジェクトモデルの深層に切り込み、メモリのリークを防ぎながら、個別プロジェクトファイルから不要なリソースカレンダーおよび例外日をプログラムによって一括削除・浄化する、極限のクリーンアップ手法を解説する。
—
1. Projectオブジェクトモデルの闇:カレンダーとリソースのライフサイクル
多くの初学者は、`Project.Resources` や `Project.Calendars` をループさせ、単純に `.Delete` メソッドを叩けばクリーンアップできると錯覚する。しかし、ProjectのCOMインターフェースはそれほど単純ではない。
参照整合性の壁
リソースカレンダーは、単体で存在しているわけではない。
1. ベースカレンダー (Base Calendar): すべてのカレンダーの基礎。
2. リソースカレンダー (Resource Calendar): 個別のリソースに紐付くベースカレンダーの派生、または独自の上書き(例外日など)。
3. タスクの制約・割り当て: リソースがタスクにアサインされている場合、カレンダーを強制削除するとProjectのスケジュールエンジンが例外(Run-time error)を吐くか、最悪の場合、内部ポインタが宙ぶらりんになる。
したがって、クリーンアップのアルゴリズムは以下の順序を厳守しなければならない。
1. 未使用判定: どのタスクにもアサインされておらず、かつ特定の基準日以降の稼働実績を持たないリソースの特定。
2. 例外日のパージ: 削除できないリソースであっても、過去の過剰な例外日(Exception)をプログラムで剥ぎ取る。
3. 安全な削除: 依存関係を断ち切った上でのオブジェクトの明示的解放。
—
2. 実装:リソースカレンダー一括クリーンアップ・エンジン
以下のコードは、実務の現場で耐えうる堅牢性を持たせたVBAモジュールである。メモリの無駄な消費を防ぐため、オブジェクト変数は適切に解放し、エラーハンドリングを徹底している。
Option Explicit
‘ ==============================================================================
‘ プロジェクト名: Project Calendar & Resource Cleaner
‘ 概要 : 未使用リソースおよび肥大化した例外日をパージし、mppを軽量化する
‘ アーキテクト: 伝説のチーフアーキテクト
‘ ==============================================================================
Sub PurgeUnusedResourceCalendars()
Dim prj As Project
Set prj = ActiveProject
Dim res As Resource
Dim cal As Calendar
Dim exc As Exception
Dim i As Long, j As Long
‘ パフォーマンス向上のためのUI描画停止(トランザクション処理の思想)
Application.ScreenUpdating = False
On Error GoTo ErrorHandler
Debug.Print “=== クリーンアップ開始: ” & prj.Name & ” ===”
‘ 1. リソースに紐付く不要な例外日のパージ(カレンダー自体の削除ではなく軽量化)
For Each res In prj.Resources
If Not res Is Nothing Then
‘ リソース固有のカレンダーが存在するか確認
On Error Resume Next
Set cal = res.Calendar
On Error GoTo ErrorHandler
If Not cal Is Nothing Then
‘ 過去の過剰な例外日(例: 1年以上前のもの)を削除して軽量化
For j = cal.Exceptions.Count To 1 Step -1
Set exc = cal.Exceptions(j)
‘ 例: 過去ログとしての価値がない古い例外日を削除対象とする
If exc.Finish < DateAdd("yyyy", -1, Date) Then
exc.Delete
End If
Next j
End If
End If
Next res
' 2. アサインメントが存在しない「幽霊リソース」の検出と削除
' 逆順ループによる安全なコレクション操作
For i = prj.Resources.Count To 1 Step -1
Set res = prj.Resources(i)
If Not res Is Nothing Then
' コストや氏名が空、かつタスクアサインがゼロの完全なゴーストリソース
If res.Assignments.Count = 0 And (res.Name = "" Or res.StandardRate = 0) Then
Debug.Print "ゴーストリソースを削除: ID=" & res.ID & ", Name=" & res.Name
res.Delete
End If
End If
Next i
Debug.Print "=== クリーンアップ正常終了 ==="
MsgBox "リソースカレンダーのクリーンアップが完了しました。", vbInformation, "システム最適化"
CleanUp:
' オブジェクト変数の明示的解放(メモリリーク防止)
Set exc = Nothing
Set cal = Nothing
Set res = Nothing
Set prj = Nothing
Application.ScreenUpdating = True
Exit Sub
ErrorHandler:
MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbCritical, "致命的エラー"
Resume CleanUp
End Sub
---
3. チーフアーキテクトの知見:メモリ最適化とパフォーマンスの極意
オブジェクトの明示的解放 (`Set … = Nothing`)
VBAのガベージコレクションは頼りにならない。特にMicrosoft ProjectのCOMラッパーは、`For Each` ループ内で暗黙的に参照が保持され続け、プロシージャを抜けてもメモリ上に残存するケース(メモリリーク)が多々発生する。
上記のコードでは、処理の最後に必ずすべてのオブジェクト変数に `Nothing` を明示的に代入し、COMコンポーネントの参照カウントを強制的にデクリメントしている。
逆順ループ (`Step -1`) の鉄則
コレクションの要素(`Resources`, `Exceptions` など)を削除する場合、先頭からループを回すとインデックスがズレて致命的な実行時エラー(インデックスが範囲外です)を引き起こす。
「コレクションの破壊的変更は必ず末尾から逆順で行う」。これはVBAに限らず、すべてのマネージド・アンマネージド開発における不変の鉄則である。
画面描画の抑制 (`Application.ScreenUpdating = False`)
数千行に及ぶリソースやタスクを持つ大規模プロジェクトにおいて、VBAからオブジェクトを操作するたびにProjectのGUIエンジンが再描画を行っていると、処理時間が何倍にも跳ね上がる。
処理の冒頭で描画を切り、最後に復元することで、I/Oのオーバーヘッドを極限まで削減する。
—
4. システム間連携を見据えたレガシー保守の心得
企業内の基幹システム(SAPやP6、独自ERPなど)とMicrosoft Projectを連携させる際、XMLインポート/エクスポート機能やODBC経由でのデータ同期が頻繁に行われる。
この時、外部システムからゴミのようなカスタムカレンダーや重複したリソースIDが持ち込まれ、プロジェクトファイルが「メタデータの肥大化」を起こす。
定期的に上記のVBAスクリプトをアドイン(.global または .ppa)として組み込み、プロジェクトオープン時(`Open` イベント)あるいはクローズ時(`BeforeClose` イベント)にバックグラウンドで走らせるアーキテクチャを構築すること。それこそが、何年もの間メンテナンスされ続けるレガシーシステムを破綻から守る唯一の防壁となる。
妥協のないコードとアーキテクチャ設計こそが、プロフェッショナルなエンジニアリングの証である。
