【実務・中級編】タスクの依存関係をCSVから読み込み、リンクを一括設定する自動化スクリプト – Project VBA解析バイブル

スポンサーリンク

【Project VBAを掌握する極限の知見】CSVから一撃でリンクを貫通させよ:タスク依存関係の超高速・一括構築術

プロジェクトマネジメントの現場において、Microsoft Projectの真価を殺すも活かすも、WBSの「タスク間の依存関係(リンク)」の精度にかかっている。数千行に及ぶ巨大なスケジュールを構築する際、GUIでマウスをチマチマと動かして先行タスク(Predecessors)を設定するなど、プロのエンジニアリングの観点から言えば言語道断の非効率である。

ExcelやCSVでマスタデータを管理し、それをVBAで一気にProjectへ流し込む――これは大規模開発を統括するリーダーにとって必須のスキルだ。

今回は、CSVからFS(Finish-to-Start)やSSなどの依存関係データを読み込み、MS Projectのタスクオブジェクト群へ極限のパフォーマンスで反映させるプロダクションコードを伝授する。

1. なぜ「愚直なリンク設定」は破綻するのか?(非効率な実装の排除)

多くのVBA初心者が陥る罠が、CSVの行をループさせながら、その都度 `Task.Predecessors.Add` メソッドを叩くという実装だ。

‘ 【アンチパターン】これぞ最悪の非効率コード
For i = 1 To UBound(csvData)
Dim t As Task
Set t = ActiveProject.Tasks(csvData(i, 1))
‘ ループのたびにCOMオブジェクトを跨ぎ、スケジュール計算が走る
t.Predecessors.Add Predecessor:=ActiveProject.Tasks(csvData(i, 2))
Next i

この書き方が現場で地獄を生む理由

1. 暗黙の再計算(Calculation)の暴発: Projectはリンクが1つ追加されるたびに、全体のクリティカルパスやスケジュールを再計算しようとする。これを数千回繰り返せば、処理は数分から数十分でフリーズする。
2. オブジェクトモデルの往復コスト: VBAからMS ProjectのCOMコンポーネントへの頻繁なアクセス(Marshalling)は、想像以上に実行速度を殺す。

我々が目指すべきアプローチ

  • 一時的にプロジェクトの自動再計算を抑制するか、あるいは適切なオブジェクト参照のキャッシュとエラーハンドリングを行い、一括で叩き込む。
  • CSV側のID(一意のキー、例えばWBSコードや独自のタスクID)と、Project側の `UniqueID` または `ID` との正確なマッピングを担保する。

2. 堅牢な設計:CSVの構造と依存関係の定義

今回扱うCSV(例: `dependencies.csv`)のフォーマットは以下の通りとする。

TargetUniqueID,PredUniqueID,LinkType,Lag
102,101,FS,0
103,102,FS,2d
105,101,SS,0

  • TargetUniqueID: 後続タスク(Successor)の Project `UniqueID`
  • PredUniqueID: 先行タスク(Predecessor)の Project `UniqueID`
  • LinkType: `FS`(終了-開始), `SS`(開始-開始), `FF`(終了-終了), `SF`(開始-終了)
  • Lag: ラグ(遅延時間。例: `2d`, `30m`, `-1h` など)

> Architect’s Note:
> 通常の `ID`(行番号が変わると変動する volatile な値)ではなく、絶対に変動しない `UniqueID` をキーとして使用すること。これが大規模プロジェクトにおけるデータ整合性を守る鉄則だ。

3. 【プロダクションコード】CSV依存関係一括設定エンジン

以下のコードは、エラーハンドリング、ファイルI/Oの安全性、そしてパフォーマンスへの配慮を極限まで高めた実戦投入可能なVBAモジュールである。

Option Explicit

‘ ==============================================================================
‘ 模块名: ModTaskDependencyImporter
‘ 概要 : CSVファイルからタスクの依存関係(Predecessors)を読み込み、
‘ Microsoft Project上のタスクへ高速かつ堅牢に一括設定する。
‘ ==============================================================================
Public Sub ImportTaskDependenciesFromCSV()

‘ 1. 定数・パスの設定
Const CSV_PATH As String = “C:\ProjectData\dependencies.csv”

‘ 2. 実行前最適化:画面描画と自動計算の停止(パフォーマンス劇的向上)
Dim origCalc As Boolean
origCalc = Application.Calculation
Application.Calculation = pjCalculationManual
Application.ScreenUpdating = False

On Error GoTo ErrorHandler

‘ 3. CSVファイルの存在確認と読み込み
If Dir(CSV_PATH) = “” Then
MsgBox “指定されたCSVファイルが存在しません: ” & vbCrLf & CSV_PATH, vbCritical, “ファイルエラー”
GoTo Finally
End If

Dim fileNum As Integer
fileNum = FreeFile
Open CSV_PATH For Input As #fileNum

Dim lineBuffer As String
Dim csvRecords() As String
Dim recordCount As Long
recordCount = 0

‘ ヘッダー行をスキップ
If Not EOF(fileNum) Then Line Input #fileNum, lineBuffer

‘ データをメモリ上の配列へ一括取り込み(I/O回数の最小化)
Do While Not EOF(fileNum)
Line Input #fileNum, lineBuffer
If Trim(lineBuffer) <> “” Then
ReDim Preserve csvRecords(recordCount)
csvRecords(recordCount) = lineBuffer
recordCount = recordCount + 1
End If
Loop
Close #fileNum

If recordCount = 0 Then
MsgBox “CSVファイルに有効なデータが存在しません。”, vbExclamation, “データ警告”
GoTo Finally
End If

‘ 4. 依存関係の設定ループ
Dim i As Long
Dim fields() As String
Dim targetTask As Task
Dim predTask As Task
Dim targetUID As Long
Dim predUID As Long
Dim linkStr As String
Dim successCount As Long
Dim errorLog As String

successCount = 0
errorLog = “”

For i = 0 To recordCount – 1
fields = Split(csvRecords(i), “,”)

If UBound(fields) >= 3 Then
targetUID = CLng(Trim(fields(0)))
predUID = CLng(Trim(fields(1)))
linkStr = UCase(Trim(fields(2))) & Trim(fields(3)) ‘ 例: “FS” + “2d” -> “FS2d”

‘ オブジェクトの安全な取得(Error trapping via On Error Resume Next inside block)
On Error Resume Next
Set targetTask = Nothing
Set predTask = Nothing

Set targetTask = ActiveProject.Tasks.UniqueID(targetUID)
Set predTask = ActiveProject.Tasks.UniqueID(predUID)
On Error GoTo ErrorHandler

If Not targetTask Is Nothing And Not predTask Is Nothing Then
‘ すでに同じリンクが存在するか等の重複チェックが必要な場合はここに挟む
‘ Predecessors.Add の構文: Add(Predecessor, [Type], [Lag])
‘ 今回はリンク文字列(例: “101FS+2d”)を組み立てて渡すのが最も確実

Dim linkExpression As String
linkExpression = predUID & linkStr

targetTask.Predecessors.Add Predecessor:=predTask, Type:=GetLinkTypeEnum(Trim(fields(2))), Lag:=Trim(fields(3))
successCount = successCount + 1
Else
errorLog = errorLog & “行 ” & (i + 2) & “: UID ” & targetUID & ” または ” & predUID & ” が見つかりません。” & vbCrLf
End If
End If
Next i

Finally:
‘ 5. 環境の復元
Application.Calculation = origCalc
Application.ScreenUpdating = True

‘ 6. 結果レポート
Dim msg As String
msg = “依存関係のインポートが完了しました。” & vbCrLf & _
“成功件数: ” & successCount & ” 件”

If errorLog <> “” Then
msg = msg & vbCrLf & vbCrLf & “【エラー・警告】” & vbCrLf & errorLog
MsgBox msg, vbExclamation, “完了(一部エラーあり)”
Else
MsgBox msg, vbInformation, “処理成功”
End If
Exit Sub

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

‘ ==============================================================================
‘ 補助関数: 文字列のリンク種別をMS ProjectのPjTaskLinkType列挙体に変換
‘ ==============================================================================
Private Function GetLinkTypeEnum(ByVal typeStr As String) As PjTaskLinkType
Select Case UCase(typeStr)
Case “FS”: GetLinkTypeEnum = pjLinkFinishToStart
Case “SS”: GetLinkTypeEnum = pjLinkStartToStart
Case “FF”: GetLinkTypeEnum = pjLinkFinishToFinish
Case “SF”: GetLinkTypeEnum = pjLinkStartToFinish
Case Else: GetLinkTypeEnum = pjLinkFinishToStart ‘ デフォルトはFS
End Select
End Function

4. コードの深層解説:なぜこの実装が「プロ」なのか?

① `Application.Calculation = pjCalculationManual` の重要性

前述した通り、MS Projectは既定で「自動計算」が有効になっている。タスクリンクを貼るたびにクリティカルパスの再計算走査が行われるため、これを手動(Manual)に切り替えることで、数千件の処理であっても数秒で終わる爆発的なパフォーマンス向上をもたらす。処理の最後には必ず元の設定へ復元(`origCalc` の適用)する防御的プログラミングを忘れてはならない。

② `ActiveProject.Tasks.UniqueID(UID)` による確実なタスク捕捉

通常の `ActiveProject.Tasks(index)` は表示上の行番号(ID)を指すため、ユーザーがタスクの並び替えを行ったり、行を削除したりした瞬間に完全に破綻する。
我々は `UniqueID` メソッドを用いることで、データベースのプライマリキーを引くが如く、タスクの物理的な位置に依存しない堅牢なオブジェクト参照を実現している。

③ メモリ上での配列処理(I/O負荷の最小化)

CSVファイルを1行ずつ読み込みながらProject側へクエリを投げるのではなく、一度VBA側のメモリ配列(`csvRecords()`)に全データをキャッシュし、ファイル閉鎖後に高速ループを回す設計にしている。これにより、ファイルロックの時間を最小限に抑え、I/Oボトルネックを回避している。

5. 現場で使える運用の知見・まとめ

本稿で紹介したスクリプトを実務の現場(PMOやの大規模プロジェクト管理)に導入する際は、以下の運用ルールを敷くこと。

  • ベースラインの設定: 依存関係をプログラムで一括適用する前には、必ずプロジェクトのバックアップ(またはベースライン保存)を取るクセをつけよ。一括処理は「正しく動けば神、バグれば大惨事」の諸刃の剣である。
  • CSVの文字コード: VBAの `Open` ステートメントで読み込む場合、CSVは 「Shift-JIS (CP932)」 または 「BOMなしUTF-8(要ADODB.Stream等での読み替え)」 である必要がある。文字化けによるUIDの破損を防ぐため、CSV生成元のシステム仕様を必ず確認すること。

プロジェクトの自動化において、コードの美しさはそのままスケジュールの美しさに直結する。泥臭い手作業をコードで駆逐し、真に価値のあるプロジェクト統括にリソースを集中させよ。

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