【テクニカル・上級編】プロジェクト横断の依存関係:別ファイルにある先行タスクをVBAで自動リンクする – Project VBA解析バイブル

スポンサーリンク

プロジェクト横断の依存関係:別ファイルにある先行タスクをVBAで自動リンクする極限の知見

レガシーな現場において、Microsoft Project(以下、MS Project)は依然として巨大なプロジェクトの心臓部として君臨している。しかし、組織が拡大するにつれて「単一の巨大なMppファイル」というモノリスな設計は破綻する。サブプロジェクトへの分割、そしてそれらを結合するプロジェクト横断のWBS(Work Breakdown Structure)の構築が不可欠となるのだ。

今回は、Project VBAのオブジェクトモデルの深層に踏み込み、「別ファイル(外部ファイル)に存在する先行タスクに対し、動的なファイルパス解決を用いて依存関係(Link)をプログラムから自動生成する」ための極限のテクニックを解説する。

表面的なAPIのラッパーを使うだけのコードは、数千件のタスクを抱えた実務環境ではメモリリークとパフォーマンス低下で必ず爆ぜる。本稿では、COMのライフサイクル管理から、マルチファイル環境におけるリンク切れの防止策まで、プロフェッショナルの実装を提示する。

1. 外部依存関係(Cross-Project Links)の構造的罠

MS Projectにおけるタスク間リンク(Task.Predecessors.Add)は、同一ファイル内であれば直感的だ。しかし、先行タスクが別ファイル(例: `SubProject_A.mpp`)に存在する場合、Projectの内部エンジンは以下の複雑な処理を行う。

1. 外部ファイルの暗黙的オープン: リンクを張る瞬間、参照先ファイルがバックグラウンドまたは目に見える形でメモリ上にロードされる。
2. UID(一意識別子)による参照: 外部タスクは「ファイルパス + タスクID/UID」の複合キーで結ばれる。
3. パスの絶対/相対問題: ハードコーディングされたファイルパスは、共有サーバーの移行やローカルPCへのクローン時に一瞬でリンク切れ(Broken Link)を起こす。

これをVBAで完全自動化するためには、「参照先ファイルの確実なロード」「適切なオブジェクト解放」「パス解決の動的化」をコードの血肉として組み込まなければならない。

2. 実装アーキテクチャ

以下のVBAコードは、親プロジェクト(統合ファイル)側から実行し、指定した設定テーブル(あるいは動的配列)に基づき、別ファイルのタスクを先行タスクとして自プロジェクトのタスクに紐付ける実用モジュールである。

Option Explicit

‘ ==============================================================================
‘ 外部プロジェクトファイル先行タスク自動リンクエンジニアリングモジュール
‘ アーキテクト仕様: オブジェクトの完全解放とパス解決の動的化
‘ ==============================================================================

Public Sub AutoLinkCrossProjectTasks()
Dim prjMaster As Project
Set prjMaster = ActiveProject

‘ パフォーマンス最大化のための画面描画・計算停止
Application.ScreenUpdating = False
Application.Calculation = pjManual

On Error GoTo ErrorHandler

‘ 【設定値】実際にはシートや外部設定から動的に取得する構造にする
Dim externalFilePath As String
externalFilePath = “C:\Projects\SubFolder\SubProject_A.mpp”

Dim masterTaskID As Long
masterTaskID = 15 ‘ 自プロジェクト側の後続タスクID

Dim externalTaskID As Long
externalTaskID = 8 ‘ 外部プロジェクト側の先行タスクID

‘ 1. 外部プロジェクトの安全なロード(既に開いている場合はそれを取得)
Dim prjExternal As Project
Set prjExternal = GetOrOpenExternalProject(externalFilePath)

If prjExternal Is Nothing Then
Err.Raise 9999, “AutoLink”, “外部プロジェクトのロードに失敗しました: ” & externalFilePath
End If

‘ 2. ターゲットタスクの取得
Dim tskMaster As Task
Dim tskExternal As Task

Set tskMaster = GetTaskBy njegovID(prjMaster, masterTaskID)
Set tskExternal = GetTaskBy njegovID(prjExternal, externalTaskID)

If Not tskMaster Is Nothing And Not tskExternal Is Nothing Then
‘ 3. 依存関係の構築 (FS: Finish-to-Start, ラグなし)
‘ 構文: Task.Predecessors.Add(PredeseeorTask, Type, Lag)
On Error Resume Next
tskMaster.Predecessors.Add tskExternal, pjLinkFinishToStart, “0d”
If Err.Number <> 0 Then
Debug.Print “Warning: リンク追加時に警告または失敗 – ” & Err.Description
Err.Clear
End If
On Error GoTo ErrorHandler

MsgBox “外部タスクとのリンクが正常に完了しました。”, vbInformation, “Architect VBA Engine”
Else
MsgBox “指定されたタスクが見つかりませんでした。IDを確認してください。”, vbExclamation
End If

CleanUp:
‘ 4. 厳格なオブジェクト解放 (メモリリークの根絶)
Set tskExternal = Nothing
Set tskMaster = Nothing
Set prjExternal = Nothing
Set prjMaster = Nothing

Application.Calculation = pjAutomatic
Application.ScreenUpdating = True
Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub

‘ ==============================================================================
‘ 補助関数群:ライフサイクルと安全性の担保
‘ ==============================================================================

Private Function GetOrOpenExternalProject(filePath As String) As Project
Dim p As Project
Dim fileName As String
fileName = Mid(filePath, InStrRev(filePath, “\”) + 1)

‘ 既にメモリ上に開かれているか走査
For Each p In Application.Projects
If StrComp(p.Name, fileName, vbTextCompare) = 0 Then
Set GetOrOpenExternalProject = p
Exit Function
End If
Next p

‘ 開かれていない場合はバックグラウンド(ReadOnly)で開く
‘ ※大規模プロジェクトでは ReadOnly 運用がセッション競合を防ぐ鉄則
On Error Resume Next
Application.FileOpenEx Name:=filePath, ReadOnly:=True, Notify:=False
If Err.Number = 0 Then
Set GetOrOpenExternalProject = Application.Projects(fileName)
Else
Set GetOrOpenExternalProject = Nothing
End If
On Error GoTo 0
End Function

Private Function GetTaskBy njegovID(prj As Project, taskID As Long) As Task
Dim t As Task
On Error Resume Next
‘ IDプロパティによる検索(UIDではなく表示上のID)
Set GetTaskBy njegovID = prj.Tasks.UniqueID(taskID)
If GetTaskBy njegovID Is Nothing Then
‘ ユニークIDで見つからない場合は通常のIDでフォールバック
Set GetTaskBy njegovID = prj.Tasks.Item(taskID)
End If
On Error GoTo 0
End Function

3. シニアエンジニアが押さえるべき「3つの極限知見」

上記のコードを実戦投入するにあたり、通常のVBAプログラミングの常識を超えたアーキテクチャ上の注意点を共有する。

① COMコンテキストの肥大化とガベージコレクションの罠

MS ProjectのVBAは背後でCOM(Component Object Model)を叩いている。`Application.Projects` や `Task.Predecessors` コレクションを巡回する際、変数の開放(`Set obj = Nothing`)を怠ると、Excel以上に強烈なメモリリークを引き起こし、MS Projectプロセス自体がフリーズ(Not Responding)する。
特にプロジェクト横断リンクでは、外部プロジェクトのポインタを保持したままマスター側を操作するな。スコープを厳密に区切り、処理が終わった瞬間に参照を断ち切ること。

② ファイルパスの動的解決(相対パス運用の限界)

MS Projectの標準機能は、別ファイルへのリンクを持つと絶対パスを保持しようとする。これがファイルサーバーの移行(例: `\\ServerA\` から `\\ServerB\` へ)で全リンク崩壊を引き起こす原因となる。
極限の環境では、Mppファイルを開いた直後に `ProjectSummaryTask` や特定のテキストフィールド(Text1-30など)に「ルートディレクトリの相対基準値」を埋め込み、VBA側で `Environ` 関数や `FileSystemObject` を使ってパスを動的に再構築してから `FileOpenEx` に渡すラッパー層を構築すべきだ。

③ 計算エンジンの非同期・同期競合

数千行規模のWBSにおいて、タスク間にリンクを1本追加するたびにMS Projectはクリティカルパスの再計算を走らせる。これが自動リンク処理のパフォーマンスを数分から数時間に引き延ばす。
前述のコードの通り、`Application.Calculation = pjManual` による計算エンジンの手動化は必須である。すべてのリンク張りが完了した最後に、一度だけ `CalculateProject` を叩くこと。この一手間で処理速度が数百倍に跳ね上がる。

終わりに:レガシーの先にある自動化の極み

MS ProjectのVBAは、一見するとドキュメントが少なく古い技術に見えるかもしれない。しかし、大規模プロジェクトの整合性をミリ秒単位で制御し、手作業によるヒューマンエラーを根絶するための強力な武器である。

「別ファイルのリンクが切れた」「重くて動かない」といった現場の悲鳴を、設計の美しさと確実なコードでねじ伏せることこそ、我々プロフェッショナルエンジニアの存在意義である。今日の知見を、あなたの現場の自動化パイプラインに組み込んでほしい。

タイトルとURLをコピーしました