Project VBAを掌握する極限の知見:混入したタスク制約を『できるだけ早く』へ強制一括クレンジングする技術
プロジェクトマネジメントの現場において、Microsoft Project(以下、Project)のスケジュールが「身動きの取れないガチガチの状態」に陥る原因の大部分は、現場の担当者が良かれと思って設定したタスクの制約(Constraint)にある。
「○月○日開始」「指定日終了」といった硬直した制約が放置されたWBSは、上位タスクの遅延やリソースの再割り当てに対する柔軟性を完全に失う。結果として、スケジュール変更のたびにプロジェクト全体が破綻し、管理者はデスマーチへと引きずり込まれることになる。
今回は、Project VBAのオブジェクトモデルを熟知したアーキテクトの視点から、プロジェクト全体に蔓延する不適切な制約をスキャンし、『できるだけ早く(As Soon As Possible: ASAP)』へ強制一括置換する堅牢なクレンジングツールの設計手法とプロダクションコードを伝授する。
—
1. なぜ「愚直なループ処理」では実務で破綻するのか?
多くのVBAエンジニアが陥る最初の罠は、アクティブプロジェクトのタスクを単純に上から `For Each` で舐め、制約プロパティを書き換えるだけのコードを書くことだ。
【愚直なアンチパターン】
Dim t As Task
For Each t In ActiveProject.Tasks
t.ConstraintType = pjAsSoonAsPossible
Next t
このコードは、小規模なファイルであれば動くように見える。しかし、実務で数千行を超える巨大なWBSや、他システムと連携したリソースプールを持つファイルにこれを適用した瞬間、以下の致命的な問題に直面する。
1. パフォーマンスの崩壊:
Projectはタスクのプロパティが変更されるたびに、依存関係(先行・後続)の連鎖計算とクリティカルパスの再評価をバックグラウンドで走らせる。ループ内でこれをやると、OSがフリーズしたかのような重篤な処理遅延を引き起こす。
2. 「要約タスク(Summary Task)」への無謀なアクセス:
Projectの要約タスクは、子タスクの集計値によって自動制御される。ここに無理やり独自の制約を書き込もうとすると、エラーが発生するか、スケジュールエンジンとの整合性が崩壊する。
3. 読み取り専用タスクや外部プロジェクトリンク:
共有リソースプールやサブプロジェクトが混在する環境では、編集権限のないタスクに対して書き込みを行おうとしてランタイムエラー(Error 1100: その操作はこのタスクでは許可されていません)でマクロが強制終了する。
プロのエンジニアであれば、「画面描画の抑制」「トランザクション的な一括処理」「安全なフィルタリング」を組み合わせた堅牢な設計が不可欠である。
—
2. 堅牢なクレンジングツールの設計思想
プロダクション環境で耐えうるツールを作るための要件は以下の3点に集約される。
- スクリーンアップデートの完全無効化: `Application.ScreenUpdating = False` により、再計算の描画コストを極限まで削る。
- 対象の厳格な絞り込み: 要約タスク、およびマイルストーン(性質上制約を維持すべきものもあるが、今回は純粋な通常タスクにフォーカス)を除外し、かつ「編集可能(ExternalTask = False)」なものだけをターゲットにする。
- エラーハンドリングと実行ログの出力: どのタスクがどのように変更されたかをイミディエイトウインドウ、あるいはログシートに出力し、ブラックボックス化させない。
—
3. 【コピペ即実戦投入】制約強制一括置換スクリプト
以下のコードは、現行のアクティブプロジェクトに対し、すべての有効な通常タスクの制約を『できるだけ早く (ASAP)』に強制置換する、実務仕様のVBAコードである。
Option Explicit
‘================================================================================
‘ Module:
‘ Purpose: プロジェクト内の全通常タスクの制約タイプを「できるだけ早く」に強制一括置換する
‘ Author: Project VBA Architecture Team
‘================================================================================
Public Sub ForceCleanConstraintsToASAP()
Dim prj As Project
Set prj = ActiveProject
‘ プロジェクトが開かれていない場合のガード
If prj Is Nothing Then
MsgBox “アクティブなプロジェクトが存在しません。”, vbCritical, “強制クレンジングエラー”
Exit Sub
End If
‘ 実行確認(破壊的変更を伴うため)
Dim confirm As VbMsgBoxResult
confirm = MsgBox(“この操作は、プロジェクト内のすべての通常タスクの制約を” & vbCrLf & _
“強制的に『できるだけ早く (ASAP)』に変更します。” & vbCrLf & _
“実行してもよろしいですか?”, _
vbYesNo + vbExclamation, “制約クレンジングの確認”)
If confirm = vbNo Then Exit Sub
‘ パフォーマンス最適化と安全性のための設定
Dim oldCalcMode As Long
oldCalcMode = Application.Calculation
On Error GoTo ErrorHandler
‘ 自動計算を手動に切り替え、描画を停止して爆速化を図る
Application.Calculation = pjManual
Application.ScreenUpdating = False
Dim t As Task
Dim targetCount As Long
Dim skippedCount As Long
targetCount = 0
skippedCount = 0
‘ 処理開始ログ
Debug.Print “=== 制約クレンジング処理開始: ” & Now & ” ===”
‘ ループ処理
For Each t In prj.Tasks
‘ 存在しないタスク(削除済み行など)のスキップ
If Not t Is Nothing Then
‘ 条件1: 要約タスクではないこと(要約タスクは自動計算されるため直接変更不可)
‘ 条件2: 外部プロジェクトのタスクではないこと
‘ 条件3: マイルストーンではないこと(※プロジェクトポリシーに応じてここは調整可能)
If Not t.Summary And Not t.ExternalTask Then
‘ すでにASAPかつ制約日付が空であればスキップ(無駄な書き込みを避ける)
If t.ConstraintType <> pjAsSoonAsPossible Then
‘ エラーハンドリングを個別に挟むことで、一部の保護タスクで止まるのを防ぐ
On Error Resume Next
t.ConstraintType = pjAsSoonAsPossible
If Err.Number = 0 Then
targetCount = targetCount + 1
‘ Debug.Print “変更完了: ID=” & t.ID & ” [” & t.Name & “]”
Else
skippedCount = skippedCount + 1
Debug.Print “スキップ(書込保護等): ID=” & t.ID & ” [” & t.Name & “] Err: ” & Err.Description
Err.Clear
End If
On Error GoTo ErrorHandler
End If
End If
End If
Next t
‘ 計算モードを戻し、変更を一度に再計算させる
Application.Calculation = oldCalcMode
Application.ScreenUpdating = True
‘ 強制再計算の実行
CalculateAll
Debug.Print “=== 制約クレンジング処理終了: ” & Now & ” ===”
MsgBox “クレンジングが完了しました。” & vbCrLf & _
“・変更されたタスク数: ” & targetCount & ” 件” & vbCrLf & _
“・スキップされた件数: ” & skippedCount & ” 件”, _
vbInformation, “完了”
Exit Sub
ErrorHandler:
‘ 異常終了時のリカバリ処理
Application.Calculation = oldCalcMode
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
—
4. コードのアーキテクチャ的解説
このコードが「単なるマクロ」を超えたプロダクションコードたる所以を、エンジニアリングの観点から解説する。
① `Application.Calculation = pjManual` の重要性
Project VBAにおいて、タスクのプロパティを変更するたびにスケジュールエンジンがフル稼働する。これを一時的に手動モードに切り替えることで、数千行規模のWBSであっても数秒以内で処理が完了するようになる。処理の最後に `CalculateAll` を明示的に呼ぶことで、データの整合性を一気に担保する。
② 多重のエラーガード
外部ファイルとのリンク(リソースプールやクロスプロジェクトリンク)が存在する場合、`ExternalTask = True` で弾いてもなお、権限エラーや読み取り専用制約にブチ当たるケースがある。ここでは `On Error Resume Next` を局所的に挟み、個別のタスクの書き込み失敗がプロジェクト全体の処理停止を生まない「耐障害性(Resilience)」を実装している。
③ 無駄な書き込みの抑制
すでに `pjAsSoonAsPossible` になっているタスクに対して再度代入を行わないよう、条件分岐(`If t.ConstraintType <> pjAsSoonAsPossible`)を設けている。これにより、ProjectのUNDOスタックの肥大化を防ぎ、メモリ消費を最小限に抑える。
—
5. 運用上の注意点とさらなる高みへ
このスクリプトを組織の運用に組み込む場合、以下のガバナンス上の配慮が必要となる。
- 事前のバックアップ: 強制置換は強力であるがゆえに、意図しないスケジュール変動(クリティカルパスの変動による納期の前倒し・後ろ倒し)を引き起こす可能性がある。必ず実行前に `.mpp` ファイルのバックアップを取得させるか、マクロの冒頭で自動バックアップ保存処理を挟むと完璧だ。
- マイルストーンの扱い: 今回のコードは通常タスクを主眼に置いているが、「指定日完了(Finish No Earlier Than など)」を意図的に守るべきマイルストーンが存在する場合は、`t.Milestone` プロパティを判定条件に加え、除外するロジックを追加してほしい。
制約のクレンジングは、形骸化したスケジュールを「生きた計画」に蘇らせるための外科手術である。このVBAスクリプトを武器に、混沌としたプロジェクトから硬直性を排除し、真の俊敏性(Agility)を手に入れてほしい。
