プロジェクトの混沌を断つ:MS Project VBAによる全タスクASAP一括強制変換エンジンの極意
現場のPMOから「スケジュールがグチャグチャになった。どのタスクも勝手に『できるだけ早く(ASAP)』始めてほしいのに、過去の誰かが入れた複雑な『できるだけ遅く(ALAP)』や『指定日必着』の制約が邪魔をして一括変更できない。どうにかしてくれ」と泣きつかれたことはないだろうか。
MS ProjectのUI上から手動で数千行に及ぶWBSの制約条件をポチポチと書き換えるなど、エンジニアの恥だ。さらに、安易に `ActiveProject.Tasks` を丸ごと舐めるだけのスパゲッティコードを書けば、COMのマーシャルオーバーヘッドとUndoスタックの爆発により、巨大プロジェクトファイルは見事にメモリリークを起こし、ExcelやProjectごと沈黙する。
今回は、数万タスクを抱える巨大エンタープライズ案件でも秒速で動作し、複雑な制約の呪縛を完全に剥ぎ取る「ASAP一括変換クレンジングエンジン」のアーキテクチャと実装を解説する。
—
1. MS Projectスケジューリングエンジンの裏側と「制約」の罠
MS Projectのタスクオブジェクトには、スケジュール計算エンジンを縛る「制約条件(ConstraintType)」が存在する。
- `pjAsSoonAsPossible` (ASAP: できるだけ早く)
- `pjAsLateAsPossible` (ALAP: できるだけ遅く)
- `pjMustStartOn` / `pjMustFinishOn` (強制系)
- `pjStartNoEarlierThan` / `pjFinishNoEarlierThan` (限定系)
これらの制約が混在したプロジェクトに対し、単純に開始日をスライドさせようとすると、エンジンが依存関係と制約のコンフリクトを起こし、意図しないガントチャートの歪み(ネガティブスラッグの発生など)を生む。特に、サマリータスク(概要タスク)やマイルストーン、外部連携タスクが絡むと、UIからの操作は完全に無力化する。
我々VBAエンジニアが取るべきアプローチは明確だ。「イベントの完全抑制」「オブジェクトの遅延バインディングと明示的解放」「トランザクション的な一括処理」の3点を遵守し、ProjectのCOM層を直接叩くことだ。
—
2. 実装:限界性能を追求したASAP一括変換スクリプト
以下に、実務の現場でそのまま運用可能なプロダクションコードを提示する。エラーハンドリング、UI描画の凍結、そしてCOMオブジェクトの正確なライフサイクル管理を実装している。
‘ ==============================================================================
‘ Module: ModScheduleCleanser
‘ Description: プロジェクト開始日を基準に全タスクの制約をASAPへ強制統一するエンジン
‘ Author: Chief Architect
‘ ==============================================================================
Option Explicit
Public Sub ForceConvertAllTasksToASAP()
Dim prj As Project
Set prj = ActiveProject
‘ 1. 実行前の安全確認とパフォーマンス最適化の準備
If prj.Tasks.Count = 0 Then
MsgBox “処理対象のタスクが存在しません。”, vbExclamation, “エンジン停止”
Exit Sub
End If
If MsgBox(“この操作は、プロジェクト内の全タスクの制約条件を「できるだけ早く (ASAP)」に強制変更します。” & vbCrLf & _
“予期せぬリンク切れや日程の再計算が発生する可能性があります。実行しますか?”, _
vbYesNo + vbExclamation, “ASAPクレンジング・エンジン”) <> vbYes Then
Exit Sub
End If
‘ 【極限最適化】画面描画と自動計算を完全停止し、COM往復コストを極小化する
Application.ScreenUpdating = False
Application.Calculation = pjManual
Dim startTime As Double
startTime = Timer
On Error GoTo ErrorHandler
Dim t As Task
Dim targetDate As Date
targetDate = prj.ProjectStartDate
Dim successCount As Long
Dim skipCount As Long
successCount = 0
skipCount = 0
Dim i As Long
Dim totalTasks As Long
totalTasks = prj.Tasks.Count
‘ ステータスバーへの進捗出力用
Application.StatusBar = “ASAP変換エンジン稼働中: 0 / ” & totalTasks
‘ 2. メモリ効率を考慮したループ処理
‘ ※For Eachではなくインデックスアクセスを使用し、コレクションのオーバーヘッドを回避
For i = 1 To totalTasks
Set t = Nothing
On Error Resume Next
Set t = prj.Tasks(i)
On Error GoTo ErrorHandler
If Not t Is Nothing Then
‘ 外部タスク、サマリータスク、あるいはマイルストーンの特殊処理
‘ ※必要に応じて除外条件を入れるが、今回は「完全強制統一」を執行する
If Not t.ExternalTask Then
‘ 制約タイプをASAPへ変更
‘ 注意: 制約変更に伴いConstraintDateもリセットされるケースがあるため順序に注意
t.ConstraintType = pjAsSoonAsPossible
‘ プロジェクト開始日より前の日付に制約が引っ張られないよう整合性を担保
If t.Start < targetDate Then
t.Start = targetDate
End If
successCount = successCount + 1
Else
skipCount = skipCount + 1
End If
End If
' 100件ごとに進捗を更新(UIのフリーズ感を緩和)
If i Mod 100 = 0 Then
Application.StatusBar = "ASAP変換エンジン稼働中: " & i & " / " & totalTasks
DoEvents ' OSに制御を返す
End If
Next i
' 3. 変更の確定と再計算の実行
Application.Calculation = pjAutomatic
prj.Calculate
Application.ScreenUpdating = True
Application.StatusBar = ""
Dim elapsedTime As Double
elapsedTime = Timer - startTime
MsgBox "ASAPクレンジングが完了しました。" & vbCrLf & _
"----------------------------------" & vbCrLf & _
"処理成功タスク数: " & successCount & vbCrLf & _
"スキップ(外部): " & skipCount & vbCrLf & _
"処理時間: " & Format(elapsedTime, "0.00") & " 秒", _
vbInformation, "処理完了"
Exit Sub
ErrorHandler:
' 異常系ハンドリング:確実に環境を復元する
Application.Calculation = pjAutomatic
Application.ScreenUpdating = True
Application.StatusBar = ""
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"Error No: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, _
vbCritical, "エンジン異常終了"
' オブジェクトの明示的解放
Set t = Nothing
Set prj = Nothing
End Sub
---
3. シニアエンジニアが知るべきアーキテクチャ上の3大鉄則
上記のコードが、単なるネットのコピペと何が違うのか。その裏にある「VBAとCOMの生態系を知り尽くした者のみが知る知見」を解説する。
① `Application.Calculation = pjManual` によるスケジューリング爆発の回避
MS Projectは、1つのタスクの制約や日付を変更するたびに、裏でクリティカルパスの再計算や依存関係の伝播(トポロジカルソートの再実行)を走らせる。数千行のタスクに対してこれを毎回やらせると、処理時間が $O(N^2)$ に跳ね上がり、VBAがフリーズしたように見える(実質的な無限ループに近い状態に陥る)。
手動計算モード(`pjManual`)に切り替え、すべての書き換えをメモリ上で一気に完結させた後、最後に `prj.Calculate` を1回だけ叩く。これが大規模データを扱う際の絶対的な鉄則だ。
② `For Each` の呪縛とインデックスアクセスの優位性
VBAの `For Each t In prj.Tasks` は手軽だが、内部でIEnumVARIANTインターフェイスを呼び出すため、巨大なCOMコレクションにおいてはパフォーマンスが著しく劣化する。さらに、途中でタスクが削除・追加された場合にイテレータが破損するリスクがある。
`For i = 1 To prj.Tasks.Count` によるダイレクトなインデックスアクセスを採用することで、メモリ消費量を一定に抑え、ポインタの迷子を防ぐ堅牢性を手に入れている。
③ 容赦なえないオブジェクトの破棄とエラー時のクリーンアップ
VBAはガベージコレクション(GC)の挙動がブラックボックスであり、特にMS ProjectのオブジェクトモデルはCOMの参照カウンタがリークしやすい。エラー発生時に画面描画や自動計算モードが `False` のまま死んだ場合、ユーザーは二度と操作できないゾンビファイルと対面することになる。
`ErrorHandler` ラベルを必ず用意し、異常終了時であっても確実にアプリケーションの状態を原状復帰(復元)させるフェールセーフ構造を組み込むことが、プロフェッショナルとアマチュアを分ける境界線である。
—
4. まとめ
レガシーなプロジェクト管理の現場において、VBAは単なる「マクロ」ではない。それはシステム間の不整合を断ち切り、プロジェクトを健全な状態へと強制送還するための「手術用メス」である。
今回紹介したASAP一括変換エンジンをあなたのツールボックスに組み込むことで、手動作業によるヒューマンエラーを根絶し、真に価値のあるプロジェクト統制業務にリソースを集中させることが可能になるはずだ。コードの意図を完全に理解し、現場のインフラストラクチャとして静かに、しかし確実に機能させてほしい。
