【Project VBA極限解説】タスクIDとユニークIDの暗黙の罠:堅牢なWBS制御と依存関係構築の極意
開発現場でMS ProjectのVBA自動化を手がけるエンジニアなら、一度は直面する悪夢がある。
「昨日まで完璧に動いていたタスク間のリンク(先行・後続関係)が、今日実行したら全く関係ないタスクを結んでいる」
「タスクを挿入・削除した途端、依存関係の制御が盛大に崩壊する」
この現象の原因は、多くの開発者が「タスクID (ID)」と「ユニークID (UniqueID)」の仕様の非対称性を軽視していることにある。
今回は、Project VBAにおけるタスク階層構造(WBS)の操作、そして依存関係の自動設定において、なぜ「タスクID」を使ってはならないのか、そして「ユニークID」を用いて実務に耐えうる堅牢な自動化ツールをどう構築すべきか、その全貌をロジカルに解説する。
—
1. 致命的な仕様の乖離:「ID」と「UniqueID」の本質
MS Projectのオブジェクトモデルにおいて、タスクを特定するプロパティには主に2つ存在する。
- `Task.ID`(タスクID):
- 性質: ガントチャートの左端に表示される「通し番号」。
- 変動性: 極めて高い。 タスクの並び替え、行の挿入、行の削除、フィルタリングの適用によって、動的に再割り当てされる。
- 用途: 画面上の表示用、または一時的なループ処理のインデックス。
- `Task.UniqueID`(ユニークID):
- 性質: タスクが生成された瞬間にシステム内部で発行される固有の整数値。
- 変動性: 絶対に変動しない。 タスクが削除されるまで不変であり、タスクを移動しても、行を挿入しても、その値が書き換わることはない。
- 用途: 外部データベース連携、ファイル間同期、そして依存関係(Predecessors / Successors)のプログラム的制御。
なぜ「ID」を使ったコードは破綻するのか?
例えば、「ID=5のタスクの先行タスクにID=4を設定する」というマクロを書いたとする。このコードの実行直前にユーザーがタスクを1行挿入した場合、元々ID=4だったタスクはID=5にズレ、元々のID=5はID=6に化ける。
結果、意図しないタスク同士がリンクされ、プロジェクト全体のスケジュールが破壊される。これが現場のエンジニアを絶望させる「IDの罠」の正体だ。
—
2. 依存関係自動設定における設計原則
実務で堅牢なWBS自動生成・リンク設定ツールを作るための鉄則はただ一つ。
> 「検索・特定・リンク指定には必ず UniqueID を使い、ID は画面表示や一時的なカウンタにしか使わない」
依存関係(`Task.Predecessors` や `Task.LinkPredecessors`)をVBAから操作する際、MS ProjectのCOMインターフェースは内部的に `UniqueID` での指定をサポートしている。これを利用しない手はない。
—
3. 【プロダクションコード】ユニークIDによる堅牢な依存関係自動構築エンジン
以下のコードは、外部定義(あるいはExcel等のデータ)から読み込んだ「タスク名」と「先行タスク名」を基に、タスクの階層構造(WBS)を構築し、UniqueIDを完全に対象として依存関係を安全に結ぶ実用的なプロシージャである。
Option Explicit
‘ ==============================================================================
‘ 処理名: BuildWBSAndDependencies
‘ 概要: 外部データに基づきタスクを生成し、UniqueIDをベースに依存関係を構築する
‘ アーキテクトノート: タスクIDの変動リスクを排除するため、リンク設定は必ずUniqueIDで行う
‘ ==============================================================================
Sub BuildWBSAndDependencies()
Dim prj As Project
Set prj = ActiveProject
‘ エラーハンドリングの開始
On Error GoTo ErrorHandler
‘ パフォーマンス向上のためのUI更新停止(画面描画・再計算の抑制)
App.ScreenUpdating = False
Calculations = pjManual
Dim tskNew As Task
Dim targetUniqueID As Long
‘ ————————————————————————–
‘ 1. タスクの生成フェーズ(この時点ではIDではなくUniqueIDを回収する)
‘ ————————————————————————–
‘ ※実務ではここでExcel等からデータをループ処理する想定
‘ 親タスクの作成
Set tskNew = prj.Tasks.Add(Name:=”要件定義フェーズ”)
Dim parentUniqueID As Long
parentUniqueID = tskNew.UniqueID ‘ ユニークIDを確実に保持
‘ 子タスク1の作成
Set tskNew = prj.Tasks.Add(Name:=”ヒアリング実施”)
Dim child1UniqueID As Long
child1UniqueID = tskNew.UniqueID
‘ 子タスク2の作成
Set tskNew = prj.Tasks.Add(Name:=”要件定義書作成”)
Dim child2UniqueID As Long
child2UniqueID = tskNew.UniqueID
‘ ————————————————————————–
‘ 2. 階層構造(WBS)のインデント調整
‘ ————————————————————————–
‘ IDではなくUniqueIDからタスクオブジェクトを再取得して操作する
prj.Tasks.UniqueID(child1UniqueID).OutlineIndent
prj.Tasks.UniqueID(child2UniqueID).OutlineIndent
‘ ————————————————————————–
‘ 3. 依存関係(リンク)の構築フェーズ(極限の堅牢性)
‘ ————————————————————————–
‘ 「子タスク2」の先行タスクとして「子タスク1」を紐付ける
‘ パターンA: Task.LinkPredecessors メソッドを使用する方法(推奨)
Dim tskChild1 As Task
Dim tskChild2 As Task
Set tskChild1 = prj.Tasks.UniqueID(child1UniqueID)
Set tskChild2 = prj.Tasks.UniqueID(child2UniqueID)
‘ 依存関係の設定(リンクタイプ: 終了-開始(FS), ラグ: 0日)
‘ ※UniqueIDを元に取得したオブジェクト同士であれば、途中でタスクが挿入されても安全
Call EstablishLinkByUniqueID(tskChild2, tskChild1, pjFinishToStart)
‘ 終了処理
Calculations = pjAutomatic
App.ScreenUpdating = True
MsgBox “WBSおよび依存関係の構築が正常に完了しました。”, vbInformation, “正常終了”
Exit Sub
ErrorHandler:
‘ 異常時の復旧処理
Calculations = pjAutomatic
App.ScreenUpdating = True
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ ==============================================================================
‘ 補助関数: ユニークIDベースのリンク設定ラッパー
‘ ==============================================================================
Private Sub EstablishLinkByUniqueID(ByRef successorTask As Task, ByRef predecessorTask As Task, ByVal linkType As PjTaskLinkType)
On Error GoTo LinkError
‘ LinkPredecessors コレクションに追加
successorTask.LinkPredecessors.Add Task:=predecessorTask, Type:=linkType
Exit Sub
LinkError:
Err.Raise Err.Number, “EstablishLinkByUniqueID”, “依存関係の設定に失敗しました (Predecessor UID: ” & predecessorTask.UniqueID & ” -> Successor UID: ” & successorTask.UniqueID & “): ” & Err.Description
End Sub
—
4. ファイル・データベース連携における設計上の注意点
Excel、SQL Server、あるいはSharePointリストなど、外部システムとMS Project間でWBSデータを同期させるシステムを設計する場合、以下のアーキテクチャ上の制約を必ず守らなければならない。
1. 外部DBの主キー(PK)として `UniqueID` をマッピングする
- 外部ストレージ側のテーブルには、必ず MS Project の `UniqueID` を保存するカラムを用意すること。
- `ID` を外部キーにしてはならない。ファイルを開き直した際や他のユーザーが編集した際に一意性が担保されなくなる。
2. 新規タスク同期時のアプローチ
- 外部から新規タスクを取り込む際、Project側にまずタスクを追加し、その直後に発行された `UniqueID` を取得して外部DB側に書き戻す(ラウンドトリップ・同期)デザインパターンを採用する。
3. プロジェクト横断(Master/Subproject)の罠
- マスタープロジェクト(複数ファイルを結合したプロジェクト)を扱う場合、`UniqueID` はファイル(サブプロジェクト)を跨ぐと一意でなくなる(各サブプロジェクト内で1から採番されるため)。
- マスタープロジェクト環境下でタスクを制御する場合は、`Task.Guid`(GUIDプロパティ)を使用するか、外部連携のスコープを単一のサブプロジェクトファイル単位に限定するアーキテクチャ設計が必要となる。
—
総括
Project VBAにおける開発は、単なるマクロの記録の延長ではない。MS Projectという強力かつ複雑なスケジューリング・エンジンの挙動、特にオブジェクトのライフサイクルと識別子の仕様を完全に理解した者だけが、保守性の高い、現場で信頼されるシステムを構築できる。
今日からあなたのコードにある `Tasks(i)` や `Tasks(ID)` という記述をすべて見直し、`Tasks.UniqueID(val)` へとリファクタリングしてほしい。それだけで、あなたの自動化ツールは「使えないおもちゃ」から「現場を救う堅牢なインフラ」へと進化するはずだ。
