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

スポンサーリンク

【Project VBA極限解説】別ファイルの先行タスクを完全自動リンク!クロスプロジェクト制約のロジカル実装

大規模なプロジェクト管理において、単一の`.mpp`ファイルで全工程を管理するなどというのは、幻想に過ぎない。サブシステムごと、あるいはベンダーごとにファイルが分かれ、それらが複雑な依存関係(外部タスクリンク)で結ばれているのが現実だ。

「Aシステムのリリース完了」が、「Bシステムの結合テスト開始」の前提条件になっている。
これを手動でマウスをカチカチやってリンクさせている現場を見ると、私はエンジニアとして痛烈な非効率を感じずにはいられない。ファイルパスが変わった瞬間、リンクは切断され、リンク切れの修復作業だけで何時間も溶かしていく。

今回は、別ファイル(外部プロジェクト)にある先行タスクをVBAで動的に解決し、堅牢なクロスプロジェクトリンクを自動生成する実務的アプローチを伝授する。

1. なぜ「単純なリンク設定」は現場で破綻するのか?

MS Projectのオブジェクトモデルにおいて、タスク間の依存関係は `Task.Predecessors.Add` メソッドで結ぶ。同一ファイル内であれば、先行タskのUniqueIDを指定するだけで終わる。

しかし、これが別ファイル(External Task)になると話が変わる。

1. 外部UIDの不確実性:
相手ファイルが更新されるたび、タスクの内部ID(UniqueID)は変わらなくとも、ファイルパスやタスク名、ユニークIDの前提が揺らぐ。
2. ファイルオープン状態の罠:
外部プロジェクトのファイルが開かれていない状態でリンクを試みると、Projectは暗黙的にファイルをバックグラウンドで開こうとするが、ファイル共有違反やパス解決エラーでVBAが容赦なくクラッシュする。
3. 文字列マッチングの脆弱性:
「タスク名」だけで外部タスクを検索してリンクしようとする愚行が散見されるが、同名タスクの存在やタイポで一発レッドカード(バグ)となる。

これらを克服するには、「外部プロジェクトの安全なロード」「一意なキー(WBSコードや独自Textフィールド)によるタスク特定」「リンク存在確認の二重チェック」という3層防御のアーキテクチャが不可欠だ。

2. 堅牢なクロスプロジェクトリンクのアーキテクチャ設計

今回構築する自動化スクリプトの設計思想は以下の通りである。

  • 外部ファイルの明示的制御: リンク先のProjectファイルをバックグラウンド(またはリードオンリー)で安全に開く。
  • 一意キーによる特定: タスク名ではなく、あらかじめ定義したカスタムフィールド(例: `Text1` に格納した業務ID)をキーにして、外部タスクの `UniqueID` を動的に取得する。
  • 安全なリンク追加: すでに同一の先行タスクが存在する場合のエラー(重複リンク例外)を捕捉し、ロジックを止めない。

3. プロダクションコード:外部プロジェクト自動リンクモジュール

以下のコードは、実務の現場でそのままコピー&ペーストして組み込める、エラーハンドリングを極限まで高めたVBAコードだ。

Option Explicit

‘ ==============================================================================
‘ 外部プロジェクトのタスクを自プロジェクトのタスクに先行タスクとして自動リンクする
‘ ==============================================================================
Public Sub AutoLinkExternalTask()
Dim prjCurrent As Project
Dim prjExternal As Project
Dim tskCurrent As Task
Dim tskExternal As Task

‘ 設定値(実際のパスやキーに合わせて変更してください)
Dim externalFilePath As String
externalFilePath = “C:\ProjectData\SubProject_A.mpp”

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

Dim searchKey As String
searchKey = “EXT-TASK-001” ‘ 外部タスクを特定するための一意キー(Text1に格納されている想定)

Set prjCurrent = ActiveProject

‘ 1. 外部プロジェクトがすでに開いているか確認し、なければ開く
Set prjExternal = GetOrOpenProject(externalFilePath)
If prjExternal Is Nothing Then
MsgBox “外部プロジェクトのオープンに失敗しました: ” & externalFilePath, vbCritical
Exit Sub
End If

‘ 2. 外部プロジェクトから一意キーを持つタスクを検索
Set tskExternal = FindTaskByCustomField(prjExternal, searchKey)
If tskExternal Is Nothing Then
MsgBox “外部プロジェクト内に指定されたキー(” & searchKey & “)を持つタスクが見つかりませんでした。”, vbExclamation
Exit Sub
End If

‘ 3. 自プロジェクトから対象タスクを取得
On Error Resume Next
Set tskCurrent = prjCurrent.Tasks.UniqueID(targetCurrentTaskID)
On Error GoTo 0

If tskCurrent Is Nothing Then
MsgBox “自プロジェクト内に指定されたUniqueID(” & targetCurrentTaskID & “)のタスクが見つかりません。”, vbExclamation
Exit Sub
End If

‘ 4. クロスプロジェクトリンクの構築
‘ 外部プロジェクトのタスク形式は “[ファイルパス]\UniqueID” として参照される
Dim predecessorString As String
predecessorString = “‘” & externalFilePath & “‘\” & tskExternal.UniqueID

On Error GoTo ErrorHandler

‘ すでに同じ先行タスクが存在するかチェックした上で追加
If Not IsPredecessorAlreadyLinked(tskCurrent, predecessorString) Then
‘ 終了-開始(FS)関係でリンクを追加
tskCurrent.Predecessors.Add Predecessor:=predecessorString, Type:=pjFS
MsgBox “外部タスクとのリンクに成功しました!” & vbCrLf & _
“後続: ” & tskCurrent.Name & vbCrLf & _
“先行: ” & tskExternal.Name, vbInformation
Else
MsgBox “既にこの外部タスクとはリンクされています。”, vbInformation
End If

Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub

‘ ==============================================================================
‘ 補助関数: プロジェクトが開いていなければ開き、開いていればオブジェクトを返す
‘ ==============================================================================
Private Function GetOrOpenProject(filePath As String) As Project
Dim prj As Project
Dim isOpen As Boolean

isOpen = False
For Each prj In Application.Projects
If StrComp(prj.FullName, filePath, vbTextCompare) = 0 Then
Set GetOrOpenProject = prj
Exit Function
End If
Next prj

‘ 開いていない場合は読み取り専用で開く(安全のため)
On Error GoTo OpenError
FileOpenEx Name:=filePath, ReadOnly:=True, Notify:=False
Set GetOrOpenProject = ActiveProject
Exit Function

OpenError:
Set GetOrOpenProject = Nothing
End Function

‘ ==============================================================================
‘ 補助関数: 指定したカスタムフィールド(Text1)の値でタスクを検索する
‘ ==============================================================================
Private Function FindTaskByCustomField(prj As Project, keyValue As String) As Task
Dim tsk As Task
For Each tsk In prj.Tasks
If Not tsk Is Nothing Then
‘ Text1フィールドに一意キーが格納されていると仮定
If tsk.Text1 = keyValue Then
Set FindTaskByCustomField = tsk
Exit Function
End If
End If
Next tsk
Set FindTaskByCustomField = Nothing
End Function

‘ ==============================================================================
‘ 補助関数: 既にリンクが存在するか重複チェックを行う
‘ ==============================================================================
Private Function IsPredecessorAlreadyLinked(tsk As Task, predString As String) As Boolean
Dim pred As Task
IsPredecessorAlreadyLinked = False

For Each pred In tsk.Predecessors
‘ 外部パスを含むユニーク表現が一致するか検証
If InStr(1, pred.UniqueID, Split(predString, “\”)(1)) > 0 Then
IsPredecessorAlreadyLinked = True
Exit Function
End If
Next pred
End Function

4. チーフアーキテクトからの実務アドバイス

このコードを現場に導入するにあたり、以下の運用ルールを必ず徹底してほしい。

1. 一意キーのガバナンス:
タスク名やWBS番号は、プロジェクトが進むにつれて変更されるリスクが極めて高い。外部タスクを特定するキーには、必ず変更されないUUIDや、専用の管理用テキストフィールド(例: `Text1`)を使用すること。
2. パスのハードコーディング禁止:
上記のサンプルコードでは分かりやすさのためにファイルパスを直書きしているが、実際のプロダクション環境では、SharePointのURLや、Excel等のマスター管理シートからパスを動的に読み込むラッパー関数を必ず前段に咬ませること。
3. パフォーマンスへの配慮:
巨大な`.mpp`ファイルを複数同時に走査すると、COMコンポーネントのメモリ解放のタイミングで動作が重くなることがある。不要になったProjectオブジェクトは適宜参照を切るか、一括処理のループ設計に気を配れ。

手動によるリンク設定という不毛なデスマーチからチームを解放し、VBAによる堅牢な自動化基盤でプロジェクトを真にコントロール下に置くのだ。実装の健闘を祈る。

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