【実務中級】MS Project VBAを極める:Taskの「Predecessors」文字列解析と依存関係のグラフ化データ抽出法
開発プロジェクトの現場において、Microsoft Project(以下、MSP)が吐き出すガントチャートの美しさに酔いしれているうちは、真の自動化エンジニアとは言えない。
プロジェクトマネジメントの真髄は「クリティカルパスの把握」であり、タスク間の依存関係(先行・後続関係)の構造化にある。特に、大規模なスケジュールを扱うとき、MSP標準のビューだけでは複雑に入組んだ依存関係の全体像を把握しきれない。ここで実務上必要になるのが、「Predecessors(先行タスク)プロパティをプログラムから解析し、外部のグラフ可視化ツール(Graphviz等)へ流し込むためのデータ抽出パイプラインの構築」だ。
今回は、VBAのオブジェクトモデルの裏側を知り尽くしたアーキテクトの視点から、文字列としてのPredecessorsが抱える罠を回避し、頑健(ロバスト)な依存関係データを抽出する極限のテクニックを伝授する。
—
1. なぜ「Predecessors」の直接参照は罠だらけなのか?
初心者がやりがちな致命的なミスは、`Task.Predecessors` や `Task.LinkPredecessors` コレクションの扱いを誤ることだ。
MSPのVBAにおいて、`Task.Predecessors` は `Task` オブジェクトのコレクションを返す。一見すると「ループを回してIDを取ればいいだけ」に見えるが、ここに大きな落とし穴がある。
1. パフォーマンスの地獄:数千行規模のプロジェクトで、すべてのタスクから `Predecessors` コレクションを逐次評価すると、COMオブジェクトのラウンドトリップが多発し、処理が数分単位でフリーズする。
2. 遅延バインディングと型安全性の欠如:リンク種別(FS, SS, FF, SF)やラグタイム(Lag)を含めた正確なトポロジカル構造を取るには、コレクションオブジェクトを叩くよりも、生文字列(Raw String)をパースする方が圧倒的に高速かつ安全である場合が多い。
MSPが保持する `Task.Predecessors` 文字列は、例えば `”3FS+2d, 5″` のような形式で格納されている。この泥臭い文字列をいかに美しく、かつバグなく構造化データに変換するか。そこにエンジニアの腕の見せ所がある。
—
2. 設計思想:堅牢なパーサーとGraphviz用DOT言語の出力
今回のゴールは、アクティブプロジェクトの全タスクを走査し、各タスクの依存関係を解析した上で、オープンソースのグラフ可視化エンジン「Graphviz(DOT言語)」形式のテキスト、またはCSVとして出力することだ。
以下の設計要件を満たすプロダクションコードを提供する。
- 高速化:画面描画(ScreenUpdating)と計算モード(Calculation)を一時停止し、COMのオーバヘッドを極限まで排除。
- 正規表現(RegEx)の活用:複雑な先行タスク文字列から「タスクID」「リンク種別」「ラグ」を安全に抽出。
- エラーハンドリング:孤立タスクや不正な文字列フォーマットによるクラッシュを完全防御。
—
3. プロダクションコード:依存関係抽出モジュール
以下のコードを、MS ProjectのVBAエディタ(Standard Module)に貼り付けて実行してほしい。
Option Explicit
‘================================================================================
次元を超えた依存関係抽出エンジン (Graphviz DOT Export)
Author: Chief Architect
Description: TaskのPredecessors文字列を完全解析し、依存関係グラフを生成する
================================================================================
Public Sub ExportTaskDependenciesToDOT()
Dim prj As Project
Set prj = ActiveProject
‘ パフォーマンス最適化の鉄則:UI描画と自動計算を停止
Application.ScreenUpdating = False
Application.Calculation = pjManual
On Error GoTo ErrorHandler
スポンジのように高速な処理を開始
Dim tsk As Task
Dim dotContent As String
Dim fso As Object
Dim ts As Object
Dim filePath As String
‘ 出力ファイルのパス(デスクトップに吐き出す)
filePath = CreateObject(“WScript.Shell”).SpecialFolders(“Desktop”) & “\project_dependency.dot”
‘ DOT言語のヘッダ構築
dotContent = “digraph ProjectGraph {” & vbCrLf
dotContent = dotContent & ” node [shape=box, style=filled, fillcolor=lightblue, fontname=””Meiryo””];” & vbCrLf
dotContent = dotContent & ” rankdir=LR;” & vbCrLf ‘ 左から右へのフロー
Dim regEx As Object
Set regEx = CreateObject(“VBScript.RegExp”)
‘ タスクID、リンクタイプ(FS/SS等)、ラグをキャプチャする正規表現
‘ 例: “12FS+2d” -> ID: 12, Type: FS, Lag: +2d
regEx.Pattern = “^(\d+)([A-Z]{2})?(.)$”
regEx.IgnoreCase = True
regEx.Global = False
Dim predString As String
Dim preds() As String
Dim i As Long, j As Long
Dim rawItem As String
Dim matches As Object
Dim targetID As String, linkType As String, lagVal As String
‘ タスクループの高速化(コレクションの直接参照)
For Each tsk In prj.Tasks
‘ サマリータスクや削除済みタスクの除外
If Not tsk Is Nothing Then
If Not tsk.Summary And tsk.Active Then
‘ ノード自体の定義(ラベルにタスク名とIDを入れる)
dotContent = dotContent & ” ” & tsk.ID & ” [label=””[” & tsk.ID & “] ” & EscapeDotLabel(tsk.Name) & “””];” & vbCrLf
predString = tsk.Predecessors
If Len(predString) > 0 Then
‘ カンマ区切りで複数先行タスクを分割
preds = Split(predString, “,”)
For i = LBound(preds) To UBound(preds)
rawItem = Trim(preds(i))
If regEx.Test(rawItem) Then
Set matches = regEx.Execute(rawItem)
targetID = matches(0).SubMatches(0)
linkType = matches(0).SubMatches(1)
lagVal = Trim(matches(0).SubMatches(2))
‘ デフォルトリンクはFS(Finish-to-Start)
If Len(linkType) = 0 Then linkType = “FS”
‘ Graphviz用のエッジ(矢印)を追加
‘ 依存関係の向き:先行(targetID) -> 後続(tsk.ID)
Dim edgeAttr As String
edgeAttr = ” [label=””” & linkType & IIf(Len(lagVal) > 0, ” ” & lagVal, “”) & “””];”
dotContent = dotContent & ” ” & targetID & ” -> ” & tsk.ID & edgeAttr & vbCrLf
End If
Next i
End If
End If
End If
Next tsk
dotContent = dotContent & “}” & vbCrLf
‘ ファイル出力(UTF-8 または Shift-JIS / ここではADODB.Streamで安全に出力)
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.CreateTextFile(filePath, True, True) ‘ Unicode出力
ts.Write dotContent
ts.Close
Application.ScreenUpdating = True
Application.Calculation = pjAutomatic
MsgBox “依存関係グラフの抽出が完了しました。” & vbCrLf & “出力先: ” & filePath, vbInformation, “Architect Automation”
Exit Sub
ErrorHandler:
‘ 異常系の復旧:必ず環境を元に戻す
Application.ScreenUpdating = True
Application.Calculation = pjAutomatic
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
End Sub
‘ DOT言語のラベル用エスケープ関数(ダブルクォーテーション等のサニタイジング)
Private Function EscapeDotLabel(ByVal text As String) As String
text = Replace(text, “”””, “\”””)
text = Replace(text, vbCrLf, ” “)
text = Replace(text, vbCr, ” “)
text = Replace(text, vbInf, ” “)
EscapeDotLabel = text
End Function
—
4. このコードが「実務レベル」である理由(アーキテクトの解説)
1. `Application.Calculation = pjManual` の徹底
MSPは、タスクのプロパティ(特に依存関係や日付)に触れるたびに、スケジュール全体を再計算しようとする。これを防がないマクロは、実務の数千行プロジェクトにおいて間違いなくフリーズを引き起こす。処理の最初で止め、最後で確実に戻す(Error Handlersの活用含む)のがプロの作法だ。
2. VBScript.RegExp による堅牢なパース
`Predecessors` の文字列は、プロジェクトの設定や言語(日本語版・英語版)によって微細に挙動が変わる。純粋な文字列操作(`InStr` や `Mid`)でパースしようとすると、ラグタイム(`+2d` 等)が混ざった瞬間に破綻する。正規表現を用いることで、将来的な仕様変更や多言語環境への耐性を飛躍的に高めている。
3. 外部ツール(Graphviz)との完全な疎結合化
データベースに直接ODBC接続するようなアプローチは、MSPのバージョンアップや環境依存のトラブルを招きやすい。テキストファイル(.dot)として吐き出すアーキテクト設計にすることで、Git等のバージョン管理とも親和性が高く、CI/CDパイプラインへの組み込みも容易になる。
—
5. まとめ:自動化の先にあるもの
このスクリプトによって出力された `.dot` ファイルは、Graphviz(無料のオープンソースツール)に読み込ませることで、一瞬で美しいネットワーク図(SVGやPNG)に変換できる。
業務自動化とは、単に手作業をVBAに置き換えてマウスをクリックする回数を減らすことではない。「人間には認知不可能な複雑なプロジェクトの構造を、コードの力で丸裸にし、意思決定のスピードを極限まで加速させること」である。
君たちの手元にあるその巨大なスケジュールも、この知見を実装すれば、たちどころに制御可能なシステムへと変貌を遂げるはずだ。現場のエンジニア諸君の健闘を祈る。
