Project VBAを掌握する極限の知見:リソース稼働率の週次スナップショットと時系列分析エンジンの構築
シニアエンジニアおよびシステム管理者の諸君。
Project VBA(Microsoft ProjectのVBA環境)におけるリソース管理の限界に直面したことはないだろうか。
標準機能である「リソース配分」や「キャッシュフロー分析」は、あくまで「現在のスナップショット」か「計画値」しか映し出さない。プロジェクトマネジメントの本質である「リソース負荷の経時変化(トレンド)」を追跡しようとした途端、標準機能は無力となる。計画変更(リスケジュール)のたびに過去のデータが上書きされ、いつ誰が過負荷状態にあったのかという「歴史」が消え去るからだ。
今回は、毎週金曜日の終わりにProjectのタスクおよびリソース配分状況を自動抽出し、別シート(または外部データストア)へ不可逆なスナップショットとして蓄積、リソースの疲弊曲線(バーンアウト・トレンド)を完全可視化するエンタープライズグレードのマクロアーキテクチャを解説する。
レガシーなVBAの枠組みを超え、メモリ管理、COMオブジェクトのライフサイクル、そしてProject特有の非同期計算モデルをねじ伏せる実戦的なコードを提示しよう。
—
1. アーキテクチャ設計の要諦
MS Projectのオブジェクトモデルにおいて、リソースの稼働率(Peak Units や Work)を時系列で取得する際、最大のボトルネックとなるのは「タイムスケールデータ(Timescaled Data)」の膨大な走査コストである。
すべてのタスク、すべてのリソースに対して日別・週別のTimescaledValueを無計画にループさせると、COMの境界を跨ぐオーバーヘッドによって実行時間が数分単位に跳ね上がる。さらに、VBAの不完全なガベージコレクションにより、メモリリークを引き起こしてProject自体がクラッシュする。
これを防ぐための設計方針は以下の3点だ。
1. スコープの最小化: アクティブなリソース、および稼働率が0より大きい割り当て(Assignment)のみに処理を限定する。
2. オブジェクトの即時解放: ループ内で生成されるRangeやAssignment等のCOMオブジェクトは、イテレーションごとに明示的かつ逆順に破棄する。
3. トランザクション的書き込み: 履歴シートへの転記は、画面描画を完全に停止した状態で一括配列(Array)処理を行う。
—
2. 実装コード:週次スナップショット・コレクター
以下のコードは、現在アクティブなProjectファイルからリソースごとの週次稼働率(Workの合計とPeak Units)を抽出し、`ResourceHistory` という名の履歴シートへ追記していくエンジンである。
Option Explicit
‘ ==============================================================================
‘ 処理名: リソース稼働率 週次スナップショット・エンジン
‘ 概要 : 現在のプロジェクトのリソース負荷を抽出し、履歴シートへ時系列蓄積する
‘ ==============================================================================
Public Sub CaptureResourceWorkloadSnapshot()
Dim prj As Project
Set prj = ActiveProject
Dim wsHist As Worksheet
Set wsHist = GetOrCreateHistorySheet(prj)
‘ パフォーマンス極限最適化のための設定
Call ToggleApplicationState(False)
Dim startTime As Double
startTime = Timer
On Error GoTo ErrorHandler
Dim res As Resource
Dim asn As Assignment
Dim targetDate As Date
targetDate = Date ‘ スナップショット取得日(実行日)
Dim nextRow As Long
nextRow = wsHist.Cells(wsHist.Rows.Count, “A”).End(xlUp.Row) + 1
‘ メモリ上の配列へデータを集約するための一時コレクションやバッファ
‘ ここでは直接シートへ書き込むが、描画抑制によりオーバヘッドを最小化
Dim rIndex As Long
For rIndex = 1 To prj.Resources.Count
Set res = prj.Resources(rIndex)
‘ 予約済みリソース(Nullでなく、コストリソースでもないこと)かつ実稼働がある場合
If Not res Is Nothing Then
If res.Type <> pjResourceTypeCost Then
Dim totalWork As Double
totalWork = 0#
Dim peakUnitSum As Double
peakUnitSum = 0#
Dim asnCount As Long
asnCount = 0
‘ リソースに紐付くアサインメントを走査
Dim aIndex As Long
For aIndex = 1 To res.Assignments.Count
Set asn = res.Assignments(aIndex)
If Not asn.Task.Milestone Then
totalWork = totalWork + asn.Work / 60 ‘ 分単位から時間に変換
peakUnitSum = peakUnitSum + asn.PeakUnits
asnCount = asnCount + 1
End If
Set asn = Nothing ‘ ループ内オブジェクトの解放
Next aIndex
‘ 履歴シートへの出力
With wsHist
.Cells(nextRow, 1).Value = targetDate ‘ スナップショット日時
.Cells(nextRow, 2).Value = prj.Name ‘ プロジェクト名
.Cells(nextRow, 3).Value = res.ID ‘ リソースID
.Cells(nextRow, 4).Value = res.Name ‘ リソース名
.Cells(nextRow, 5).Value = res.StandardRate ‘ 標準単価
.Cells(nextRow, 6).Value = totalWork ‘ 総工数 (h)
.Cells(nextRow, 7).Value = IIf(asnCount > 0, peakUnitSum / asnCount, 0) ‘ 平均ピーク稼働率
.Cells(nextRow, 8).Value = res.MaxUnits ‘ 最大稼働容量
End With
nextRow = nextRow + 1
End If
End If
Set res = Nothing
Next rIndex
MsgBox “リソース稼働率のスナップショットが正常に完了しました。” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, vbInformation, “完了”
CleanUp:
Call ToggleApplicationState(True)
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Resume CleanUp
End Sub
‘ ==============================================================================
‘ 補助関数: 履歴シートの存在確認および自動生成
‘ ==============================================================================
Private Function GetOrCreateHistorySheet(ByzingPrj As Project) As Worksheet
Dim xlApp As Object
Dim xlWb As Object
‘ ProjectからExcelをオートメーション操作する場合のインスタンス取得
‘ ※ProjectVBAからExcelを操作する標準的な手法
On Error Resume Next
Set xlApp = GetObject(, “Excel.Application”)
If xlApp Is Nothing Then
Set xlApp = CreateObject(“Excel.Application”)
End If
On Error GoTo 0
‘ 注意: 実運用ではProjectのアクティブなレポート用Excelブック、
‘ または専用の管理用Excelアドイン間での連携を想定。
‘ ここではProjectに紐づくActiveWorkbook(Excelがホストの場合)を前提とする。
‘ ※純粋なMSP単体マクロの場合は、ODBC経由でDBへ書き出すかExcelをCreateObjectする。
‘ 今回は簡略化のため、Excel側で実行されるコード片として記述、
‘ またはProjectからExcelを制御するインターフェースの設計思想を示す。
End Sub
‘ ==============================================================================
‘ ユーティリティ: アプリケーションの描画・警告抑制による極限高速化
‘ ==============================================================================
Private Sub ToggleApplicationState(ByVal enable As Boolean)
On Error Resume Next
With Application
.ScreenUpdating = enable
.Calculation = IIf(enable, xlCalculationAutomatic, xlCalculationManual)
.DisplayAlerts = enable
End If
On Error GoTo 0
End Sub
—
3. シニアエンジニアが知るべき「メモリとCOMの罠」
上記のコードを単にコピー&ペーストするだけでは、現場の巨大なプロジェクトファイル(数千タスク規模)ではメモリリークを引き起こす。ここで、VBAの背後にあるCOMアーキテクチャの挙動について深く言及しておこう。
1. `Set obj = Nothing` の強制力
VBAは参照カウンタ方式のガベージコレクションを採用している。しかし、スコープを抜けるまでの間、ループ内で生成されたオブジェクト(`res` や `asn`)の参照はスタック/ヒープに残り続ける。
特に `res.Assignments(aIndex)` のようなコレクションのインデックスアクセスは、背後でIUnknownポインタを一時生成している。これをループの終端で `Set asn = Nothing` と明示的に解放しないと、数千回のイテレーションでメモリ消費量が跳ね上がり、最終的に「メモリ不足(Out of Memory)」エラーを引き起こす。
2. Projectの再計算エンジン(Calculation)の制御
Projectはデフォルトで「自動再計算」が有効になっている。マクロの実行中にリソース情報を読み取る際、背後でタスクの依存関係やコストの再計算が走り、これが深刻なパフォーマンス劣化を招く。
エンタープライズ環境では、処理の最初に `Application.Calculation = pjCalculationManual` に設定し、すべてのデータ抽出が終わった後に手動で再計算を行わせるのが鉄則である。
—
4. 時系列分析への発展:トレンドの可視化
蓄積されたスナップショット(`ResourceHistory` シート)は、以下のような構造を持つ。
| 取得日時 | プロジェクト名 | リソースID | リソース名 | 標準単価 | 総工数(h) | 平均ピーク稼働率 | 最大稼働容量 |
| :— | :— | :— | :— | :— | :— | :— | :— |
| 2023/10/20 | Project_A | 1 | 開発 太郎 | 5000 | 45.5 | 1.15 (115%) | 1.0 (100%) |
| 2023/10/27 | Project_A | 1 | 開発 太郎 | 5000 | 52.0 | 1.30 (130%) | 1.0 (100%) |
このデータを元に、Excel側のピボットグラフやパワークエリを用いて「平均ピーク稼働率が継続して 1.0(100%)を超えているリソース」を検知するアラートダッシュボードを構築する。
これにより、プロジェクトマネージャーは「どのリソースが慢性的な過負荷状態にあるか」を主観ではなく客観的な時系列データとして把握できる。
—
総括
VBAは、しばしば「レガシーでオワコンな言語」とやゆされる。しかし、オブジェクトのライフサイクルを完璧に制御し、アプリケーションの挙動の裏にあるCOMのメカニズムを理解したエンジニアが書いたVBAコードは、巨大なプロジェクトデータをも瞬時にねじ伏せる圧倒的な戦闘力を発揮する。
リソース管理の本質は「過去の過ちを隠蔽すること」ではなく、「過去の負荷の歪みを正確に観測し、未来の計画を補正すること」にある。
このスナップショット・エンジンを君のプロジェクト管理基盤に組み込み、真のエンジニアリング・ガバナンスを実現してほしい。
