【実務・中級編】【実務中級】Task.OutlineChildrenを用いたWBSの再帰的走査:階層構造をCSVへ書き出すツール – Project VBA解析バイブル

スポンサーリンク

【Project VBA】Task.OutlineChildrenを用いたWBSの再帰的走査:階層構造をCSVへ書き出す極限実務ガイド

こんにちは、開発プロジェクトの現場を渡り歩くチーフアーキテクトの私だ。
MS Projectを大規模なスケジュール管理に導入している組織において、避けて通れない課題が「WBS(Work Breakdown Structure)のデータ抽出と他システム連携」である。

「プロジェクトの全タスク構造をフラットなCSVに落とし込み、ExcelやBIツールで分析したい」
「しかし、親タスク・子タスク・孫タスクと無限に続く階層構造のせいで、VBAでの抽出処理がスパゲッティコードになってしまう」

このような現場の悲鳴を、私は幾度となく耳にしてきた。
一般的なネット上のスニペットをそのままコピペすると、階層が深くなった途端にメモリリークを起こしたり、マイルストーンやサマリータスクの判定漏れでデータが破損したりする。

今回は、MS Projectのオブジェクトモデルを知り尽くした私が、`Task.OutlineChildren` プロパティを武器にした「バグの起きない堅牢な再帰的走査アルゴリズム」と、プロダクション環境でそのまま使える実用コードを伝授しよう。

1. なぜ「フラット化の再帰処理」で多くのエンジニアが挫折するのか?

MS Projectのタスク構造は、リレーショナルデータベースのような綺麗な外部キーを持っていない。保持しているのは「WBSのインデントレベル」と「親子関係(OutlineChildren / OutlineParent)」という、いわば閉じたツリー構造だ。

未熟なコードでは、`For i = 1 To ActiveProject.Tasks.Count` のような単純なループで全タスクを舐め、インデントレベルの増減だけで親子関係を推測しようとする。これが地獄の始まりだ。
タスクの移動、フィルタリング、削除などが頻発する実務の現場では、インデントレベルの数値だけで階層を正しく復元することは不可能に近い。

正解:`Task.OutlineChildren` を使ったトップダウン・再帰走査

MS Projectが公式に提供している `OutlineChildren` コレクションを使用するべきだ。これは「そのタスクの直下にある子タスクのコレクション」を返す。
プロジェクトのルート(`ActiveProject.RootTask`、あるいは `ActiveProject.Tasks` のトップレベル)から出発し、子が存在する限り自分自身を呼び出す「再帰関数(Recursion)」を構築するのが、最も美しく、最もバグらない唯一の解である。

2. プロダクション品質のVBAコード:WBS階層CSVエクスポート

以下のコードは、エラーハンドリング、ファイルの排他制御、そして巨大なプロジェクトでも耐えうるメモリ管理を考慮した、実務投入可能なプロダクションコードだ。

標準モジュールに貼り付けて実行してほしい。

Option Explicit

‘ ==============================================================================
‘ 処理名 : ExportWBSToCSV
‘ 概要 : MS ProjectのWBS階層構造を再帰的に走査し、フラットなCSVとして出力する
‘ 備考 : 実務仕様(文字コードUTF-8対応、エスケープ処理、エラーハンドリング完備)
‘ ==============================================================================
Public Sub ExportWBSToCSV()
Dim fso As Object
Dim ts As Object
Dim outputPath As String
Dim targetProject As Project

‘ 実行前プレチェック
If ActiveProject.Tasks.Count = 0 Then
MsgBox “エクスポート対象のタスクが存在しません。”, vbExclamation, “処理中断”
Exit Sub
End If

Set targetProject = ActiveProject

‘ 出力パスの決定(デスクトップにタイムスタンプ付きで出力)
outputPath = CreateObject(“WScript.Shell”).SpecialFolders(“Desktop”) & _
“\WBS_Export_” & Format(Now, “yyyymmdd_HHMMSS”) & “.csv”

On Error GoTo ErrorHandler

‘ ADODB.Streamを使用してUTF-8(BOM付き)でファイルを作成(文字化け防止)
Dim stream As Object
Set stream = CreateObject(“ADODB.Stream”)
With stream
.Type = 2 ‘ adTypeText
.Charset = “UTF-8”
.Open

‘ CSVヘッダーの書き込み
.WriteText “ID,WBS,タスク名,階層レベル,開始日,終了日,担当者,進捗率(%)” & vbCrLf

‘ 再帰処理の実行(ルートタスクの直下から走査開始)
Dim t As Task
For Each t In targetProject.Tasks
‘ Nothing判定(ProjectのTasksコレクションは隙間がある場合があるため)
If Not t Is Nothing Then
‘ プロジェクト直下の最上位タスク(OutlineLevel = 1)を起点とする
If t.OutlineLevel = 1 Then
Call TraverseTasks(t, stream, 1)
End If
End If
.SaveToFile outputPath, 2 ‘ adSaveCreateOverWrite
.Close
End With

MsgBox “WBSのCSVエクスポートが完了しました。” & vbCrLf & “保存先: ” & outputPath, vbInformation, “完了”
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Number & ” – ” & Err.Description, vbCritical, “エラー”
If Not stream Is Nothing Then
If stream.State = 1 Then stream.Close
End If
End Sub

‘ ==============================================================================
‘ 関数名 : TraverseTasks (再帰プロシージャ)
‘ 概要 : タスクを再帰的に走査し、StreamへCSV行を出力する
‘ ==============================================================================
Private Sub TraverseTasks(ByRef parentTask As Task, ByRef stream As Object, ByVal currentLevel As Long)
Dim childTask As Task
Dim csvLine As String

‘ 1. 自タスクのデータをCSV形式に整形して書き出し
csvLine = FormatCSVField(parentTask.ID) & “,” & _
FormatCSVField(parentTask.WBS) & “,” & _
FormatCSVField(parentTask.Name) & “,” & _
currentLevel & “,” & _
FormatCSVField(parentTask.Start) & “,” & _
FormatCSVField(parentTask.Finish) & “,” & _
FormatCSVField(parentTask.ResourceNames) & “,” & _
parentTask.PercentComplete

stream.WriteText csvLine & vbCrLf

‘ 2. 子タスクが存在するかチェック (OutlineChildrenを活用)
If parentTask.OutlineChildren.Count > 0 Then
For Each childTask In parentTask.OutlineChildren
If Not childTask Is Nothing Then
‘ 再帰呼び出し(階層レベルをインクリメント)
Call TraverseTasks(childTask, stream, currentLevel + 1)
End If
Next childTask
End If
End Sub

‘ ==============================================================================
‘ 関数名 : FormatCSVField
‘ 概要 : CSVのフィールド値にカンマやダブルクォーテーションが含まれる場合のエスケープ処理
‘ ==============================================================================
Private Function FormatCSVField(ByVal field As Variant) As String
Dim valStr As String
If IsNull(field) Then
FormatCSVField = “”
Exit Function
End If

valStr = CStr(field)

‘ ダブルクォーテーションは二重にエスケープする
If InStr(valStr, “”””) > 0 Then
valStr = Replace(valStr, “”””, “”””””)
End If

‘ カンマ、改行、ダブルクォーテーションが含まれる場合は全体をダブルクォーテーションで囲む
If InStr(valStr, “,”) > 0 Or InStr(valStr, vbCr) > 0 Or InStr(valStr, vbLf) > 0 Or InStr(valStr, “”””) > 0 Then
FormatCSVField = “””” & valStr & “”””
Else
FormatCSVField = valStr
End If
End Function

3. チーフアーキテクトが解説する「設計の急所」

上記のコードには、単なる「動くコード」を超えた、エンタープライズレベルの堅牢性を担保するための技術的こだわりが詰まっている。現場で応用するための知見を共有しよう。

① `ADODB.Stream` による文字化けの完全排除

VBA標準の `Open … For Output As #` を使うと、日本語環境では Shift-JIS (CP932) で出力されてしまう。昨今のクラウドBIツール(Power BI, Tableau, Snowflake等)やPythonのデータ分析パイプラインに投入する場合、Shift-JISは百害あって一利なしだ。
`ADODB.Stream` を採用することで、強制的に UTF-8 (BOM付き) で出力させ、グローバルなデータ連携基盤との互換性を完璧に担保している。

② タスクの疎結合性(Nothing判定)への配慮

MS Projectの `Tasks` コレクションは、タスクの削除が行われた際などにインデックスの欠番(空きスロット)が生じることがある。
ループ内で `If Not t Is Nothing Then` を挟まないコードは、大規模プロジェクトにおいて確実に `Object variable or With block variable not set`(実行時エラー 91)を引き起こす。このパ防衛的プログラミングは実務では必須だ。

③ 堅牢なCSVエスケープ関数 (`FormatCSVField`)

現場のプロジェクトマネージャーが入力するタスク名には、「打合せ, 議事録確認」といったカンマや、特殊文字が含まれることが多い。
これらをそのまま出力するとCSVの列がズレてしまい、上流システムへの連携バグに直結する。RFC 4180に準拠したエスケープ処理を実装しているため、データ破損のリスクをゼロに抑え込んでいる。

4. ファイル・データベース連携における実務上の注意点

このツールで出力したCSVを、さらにRDBや外部システムへ自動連携する場合のアーキテクチャ上の注意点を述べておこう。

1. タスクIDの揮発性(Volatility)
MS Projectの `Task.ID` は、タスクの並び順や再計算によって動的に変化する。もし外部DBと「タスクID」を主キーとして1対1で同期したい場合は、IDではなく、変更されない固有の識別子である `Task.UniqueID` を抽出項目に加えるべきだ。必要に応じて上記のコードのヘッダーおよびプロパティを `parentTask.UniqueID` に書き換えて運用してほしい。
2. 実行パフォーマンスの最適化
数千行規模のプロジェクトになると、画面の再描画(ScreenUpdating)やイベントがボトルネックになることがある。必要であれば処理の冒頭で `Application.ScreenUpdating = False` を挟むと、実行速度が劇的に向上する(※エラーハンドリングで確実に `True` に戻すこと)。

総括

今回紹介した `Task.OutlineChildren` を用いた再帰的走査は、MS Project VBAにおける最もエレガントで破綻しないアーキテクチャの一つである。
単に「動くマクロ」を作るフェーズは卒業し、保守性、堅牢性、そして他システムとの親和性を考慮したプロダクションコードを組み上げることで、あなたの評価は開発者から「信頼できるインフラ・アーキテクト」へと確実にシフトするだろう。

現場の自動化を、次のステージへ引き上げてくれ。

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