【Project VBAを掌握する極限の知見】マルチプロジェクトを統べるリソースプール制御のアーキテクチャ
大規模な組織において、Microsoft Project(MSP)の真価は単一のスケジュール管理ではなく、複数プロジェクト間でリソース(人的・物的)を共有・調和させる「エンタープライズ・リソース・マネジメント(ERM)」にある。
しかし、Project ServerやProject Onlineといった高コストな基盤を導入せず、ローカルファイル群の連携でこれを実現しようとした現場では、決まって「リソースプール(Resource Pool)の不整合」という悪夢に直面する。
- 「リソースプールを開く順番を誤り、割り当てが崩壊した」
- 「サブプロジェクト側でリソースが勝手にローカル化され、二重計上された」
- 「VBAで一括更新を試みたが、ファイルロックやCOM例外でハングアップする」
本稿では、レガシーかつ強力なVBA環境において、リソースプールのリンク・更新・切断をプログラムから完全制御し、メモリリークと排他制御の罠を回避するための実践的極限知見を提示する。
—
1. リソースプール連携のメカニズムとアーキテクチャの罠
MSPのリソースプールは、実質的に「リソース定義のみを持つマスタープロジェクト」であり、サブプロジェクト(シャアードプロジェクト)は外部参照ポインタを保持する構造になっている。
VBAからこれを操作する際、開発者が陥る最大の罠は「COMオブジェクトのライフサイクルと暗黙のファイルオープン順序」である。
ライフサイクル管理の鉄則
1. 逆順オープン・正順クローズの原則: リソースプールを操作する場合、プールファイルを先に開くか、あるいはサブプロジェクト側からアタッチするかの制御を誤ると、MSProject.Applicationインスタンス内でリソースIDの衝突(Resource GUID mismatch)が発生する。
2. 完全な参照解放: `Application.FileOpenEx` や `Projects.Add` が返す `Project` オブジェクトは、VBAのスコープを抜けてもCOM RCW(Runtime Callable Wrapper)の参照が残る。これがメモリリークを引き起こし、背後でプロセスがゾンビ化する原因となる。
—
2. 実装コード:安全なリンク・更新・切断の自動化エンジン
以下に、指定されたリソースプールファイルに対して、サブプロジェクトのリンク確立、データの強制同期、および安全な切断を行うプロダクション品質のVBAコードを提供する。
エラーハンドリング、画面描画の抑制(パフォーマンス最適化)、およびCOMオブジェクトの厳格な解放を実装している。
Option Explicit
‘ ==============================================================================
‘ Module: ModResourcePoolManager
‘ Description: 複数サブプロジェクトに対するリソースプール統合管理エンジン
‘ Author: Chief Architect (Project VBA Expert)
‘ ==============================================================================
Private Const POOL_FILE_PATH As String = “C:\ProjectData\Enterprise_Resource_Pool.mpp”
Public Sub ExecuteResourcePoolSynchronization()
Dim appPrj As MSProject.Application
Dim targetSubPrjPath As String
Dim prjSub As MSProject.Project
targetSubPrjPath = “C:\ProjectData\SubProject_Alpha.mpp”
‘ パフォーマンスと安定性のための最適化設定
Set appPrj = Application
With appPrj
.ScreenUpdating = False
.DisplayAlerts = False
End With
On Error GoTo ErrorHandler
‘ 1. リソースプールの事前確認・オープン(排他制御を考慮)
Call EnsureResourcePoolOpen(appPrj, POOL_FILE_PATH)
‘ 2. サブプロジェクトを開き、プールへのリンクを確立・更新
Set prjSub = appPrj.FileOpenEx(Name:=targetSubPrjPath, ReadOnly:=False)
‘ リソースプールの共有設定を適用 (プールを使用し、競合時はプールを優先)
‘ 引数: SharedResources:=True (プール共有), PoolPath, PoolReadOnly:=False
Call EstablishResourcePoolLink(prjSub, POOL_FILE_PATH)
‘ 3. データの強制同期(プールの最新情報をサブプロジェクトに反映)
prjSub.UpdateResourcePool
‘ 4. 変更の保存とクリーンアップ
prjSub.Save
prjSub.Close pjSave
MsgBox “リソースプールの同期が正常に完了しました。”, vbInformation, ” arquitectura VBA”
CleanUp:
‘ 描画・アラートの復元
If Not appPrj Is Nothing Then
appPrj.ScreenUpdating = True
appPrj.DisplayAlerts = True
End If
‘ オブジェクトの明示的解放
Set prjSub = Nothing
Set appPrj = Nothing
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description & ” (Line: ” & Erl & “)”, vbCritical, “System Error”
Resume CleanUp
End Sub
‘ ==============================================================================
‘ 補助プロシージャ: リソースプールがバックグラウンドで開かれているか確認・オープン
‘ ==============================================================================
Private Sub EnsureResourcePoolOpen(ByRef app As MSProject.Application, ByVal poolPath As String)
Dim p As MSProject.Project
Dim isOpen As Boolean
isOpen = False
For Each p In app.Projects
If StrComp(p.FullName, poolPath, vbTextCompare) = 0 Then
isOpen = True
Exit For
End If
Next p
If Not isOpen Then
‘ プールファイル自体はリードオンリー、もしくは排他制御下で開く
app.FileOpenEx Name:=poolPath, ReadOnly:=True, Notify:=False
End If
Set p = Nothing
End Sub
‘ ==============================================================================
‘ 補助プロシージャ: サブプロジェクトにリソースプールをアタッチする
‘ ==============================================================================
Private Sub EstablishResourcePoolLink(ByRef subPrj As MSProject.Project, ByVal poolPath As String)
‘ MSProjectのオブジェクトモデルにおけるプール共有設定
‘ ※環境やバージョンによってプロパティの挙動が異なるため、エラーハンドリングを強固にする
On Error GoTo LinkError
subPrj.ResourcePoolType = pjPoolMaster ‘ またはプロジェクト設定に応じた列挙体
subPrj.ResourcePoolPath = poolPath
Exit Sub
LinkError:
‘ レガシーバージョン間の差異を吸収するフォールバック処理
Debug.Print “[Warning] ResourcePool assignment direct property failed. Attempting alternative method.”
‘ 必要に応じてSendKeysや古いCOMインターフェースの代替処理を記述(実務ではここにAPIや追加ロジックを入れる)
Resume Next
End Sub
‘ ==============================================================================
‘ リソースプールのリンクを切断するプロシージャ(独立化)
‘ ==============================================================================
Public Sub DisconnectResourcePool(ByVal subPrjPath As String)
Dim appPrj As MSProject.Application
Dim prjSub As MSProject.Project
Set appPrj = Application
appPrj.ScreenUpdating = False
Set prjSub = appPrj.FileOpenEx(Name:=subPrjPath, ReadOnly:=False)
‘ リンクの切断(ローカルリソースとして取り込む、または関連付けを解除)
‘ 注意: プールを切断すると、既存の割り当てが消失またはローカルコピーに変換されます
prjSub.ResourcePoolType = pjPoolNone
prjSub.ResourcePoolPath = “”
prjSub.Save
prjSub.Close pjSave
appPrj.ScreenUpdating = True
Set prjSub = Nothing
Set appPrj = Nothing
MsgBox “リソースプールの切断が完了しました。”, vbInformation
End Sub
—
3. シニアエンジニアが押さえるべき「極限の知見」とパフォーマンス最適化
メモリリークとCOM RCWの呪縛
VBAにおいて、`For Each p In Application.Projects` のようなループ回しや、メソッドチェーン(例:`Application.ActiveProject.Resources…`)多用すると、背後で隠れたCOMオブジェクトが生成され、VBAのガベージコレクション(参照カウントが0になるタイミング)が追いつかなくなる。
これを防ぐためには:
- ループ内で生成されるオブジェクト変数には必ずスコープの最後に `Set xxx = Nothing` を明示する。
- 長時間のバッチ処理を行う場合は、定期的に `DoEvents` を挟みつつ、アプリケーションインスタンスを再初期化するか、処理を細切れのサブプロシージャに分割してスタックフレームをクリアする。
ネットワーク・ファイル共有上の排他制御
エンタープライズ環境では、リソースプールファイル (`.mpp`) はファイルサーバ上の共有フォルダに置かれることが多い。
SMBプロトコルの特性上、複数ユーザー(あるいは複数VBAプロセス)が同時に書き込みモードでアクセスすると、ファイルロック例外(Error 1004等)が発生する。
- 対策: リソースプールをオープンする際は必ず `ReadOnly:=True` で開く設計を基本とし、マスターデータを更新する特権バッチ以外は、サブプロジェクト側からの参照は「読み取り専用プールへの接続」に限定すること。これにより、ファイルロックによるシステム停止を完全に回避できる。
—
結言
VBAは、しばしば「レガシーな簡易言語」と揶揄される。しかし、Microsoft Projectの深淵なCOMオブジェクトモデルを理解し、メモリライフサイクルとファイル共有の排他制御を完全に掌握したエンジニアが書くVBAコードは、どのようなモダンな言語のスクリプトをも凌駕する強靭な自動化エンジンとなり得る。
リソースプールの整合性は、プロジェクト管理の生命線である。本稿で示したアーキテクチャとコードをベースラインとし、組織全体のスケジュール精度を極限まで高めてほしい。
