【テクニカル・上級編】タスクの「制約条件」をVBAで一括解除・設定する運用ルール策定 – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:MS Projectの制約条件(Constraint)汚染をVBAで秒速浄化するアーキテクチャ

現場のPMOから「なぜかスケジュールが微動だにしない」「クリティカルパスが完全に破壊されている」という悲鳴交わりのチケットが上がってきたとき、原因の9割は現場担当者が善意でした設定した「制約条件(Constraint Type)」のスパゲッティ化にある。

MS Project(Project Client)は、デフォルトでタスクを入力すると「できるだけ早く(ASAP)」を割り当てる。しかし、Excelからのインポートや、熟練していないメンバーの手グセによって、「指定日以降に開始(SNET)」「指定日以前に終了(FNLT)」、そして最悪の癌である「ن(MSO: 領域を固定する「会議の日」などの例外を除き、一切使うべきではない「MSO:日付固定」)」が無秩序にばら撒かれる。これらはスケジューリング・エンジン(CPMアルゴリズム)の自動最適化を根底から粉砕する。

今回は、この腐敗したタスク群の制約条件を検知し、一括して「できるだけ早く(ASAP)」へ強制リセット、あるいはガバナンスに準拠した日付制約へ再構築するための極限まで最適化されたVBAオートメーション・アーキテクチャを解説する。

1. MS Projectオブジェクトモデルの闇:なぜ「愚直なループ」は死を招くのか

VBAからMS Projectを操作する際、多くの初学者は以下のようなコードを書く。

‘ 【アンチパターン】絶対に書いてはならない破滅のループ
Dim t As Task
For Each t T In ActiveProject.Tasks
t.ConstraintType = pjConstraintAsSoonAsPossible
Next t

このコードは、数千行規模のエンタープライズ規模のWBSにおいてシステムをハングアップさせる、あるいは致命的なメモリリークとパフォーマンス劣化を引き起こす

圧倒的なオーバーヘッドの正体

1. COM相互運用性のコスト: `ActiveProject.Tasks` コレクションへのアクセス、および `Task` オブジェクトの取得は、VBAとCOM境界(Marshaling)を毎ループ跨ぐため、膨大なコンテキストスイッチが発生する。
2. 再計算エンジンの暴走: デフォルトでは、`ConstraintType` を1つ変更するたびに、MS Projectのスケジューリング・エンジンが全体のネットワークダイアグラムを再計算(Calculate)しようとする。これを数千回繰り返せば、処理が数分で終わらないのは当然だ。

伝説のアーキテクトが採るべきアプローチ

  • 計算エンジンの完全停止: 一括処理の前に `Calculation` モードを手動(あるいはコード上)にし、全処理完了後に一括再計算させる。
  • オブジェクトの厳密なスコープ管理と明示的解放: COMオブジェクトの参照を適切に破棄し、VBAのガベージコレクション頼みにしない。
  • Windows APIを活用したUIフリーズ: 処理中の画面描画(Repaint)を完全にシャットアウトし、CPUリソースをCOMオートメーションに全振りする。

2. 実装コード:制約条件クリーンアップ・エンジン

以下のコードは、実務の現場で耐えうる堅牢性と極限のパフォーマンスを両立させた、プロダクション・レディのVBAモジュールである。

Option Explicit

‘ ==============================================================================
‘ モジュール名: ModConstraintCleaner
‘ 概要 : MS Projectのタスク制約条件を解析・一括リセットする高速処理エンジン
‘ 著者 : チーフ・アーキテクト
‘ ==============================================================================

‘ 画面描画抑止のためのWindows API (必要に応じてUser32.dllを利用)
If VBA7 Then
Private Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As Long) As LongPtr
Private Declare PtrSafe Function LockWindowUpdate Lib “user32” (ByVal hwndLock As LongPtr) As Long
Else
Private Declare Function SendMessage Lib “user32” Alias “SendMessageA” (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function LockWindowUpdate Lib “user32” (ByVal hwndLock As Long) As Long
End If

Const WM_SETREDRAW = &HB

Public Sub ExecuteConstraintCleanup()
Dim startTime As Double
startTime = Timer

‘ 1. ガバナンス・環境の退避と最適化設定
Dim originalCalc As Long
originalCalc = Application.Calculation

On Error GoTo ErrorHandler

‘ 自動計算をオフ(最重要:これがないとパフォーマンスが出ない)
Application.Calculation = pjManual

‘ UI描画の完全停止
Application.ScreenUpdating = False

Dim tsk As Project.Task
Dim targetTaskCount As Long
Dim modifiedCount As Long
targetTaskCount = 0
modifiedCount = 0

‘ サマリータスク(プロジェクトの親など)やマイルストーンを除外するかはプロジェクトポリシーによるが、
‘ 今回は「すべての実タスク(Summaryではないもの)」を対象とする
Dim tskColl As Tasks
Set tskColl = ActiveProject.Tasks

Dim i As Long, totalTasks As Long
totalTasks = tskColl.Count

‘ ステータスバーへの進捗表示用
AppActivate ActiveProject.Application.Caption

‘ 2. 高速イテレーションループ
For i = 1 To totalTasks
‘ Nothing判定(削除されたタスク番号のスキップ)
On Error Resume Next
Set tsk = tskColl(i)
On Error GoTo ErrorHandler

If Not tsk Is Nothing Then
‘ サマリータスク、外部タスク、およびマイルストーンの特例処理
‘ 例外ルール: 「固定日(MSO/MFO)」であっても特定のフラグ(Textフィールド等)が立っているものは保護するなどの拡張が可能
If Not tsk.Summary Then
targetTaskCount = targetTaskCount + 1

‘ 現状の制約条件をチェックし、ASAP(できるだけ早く)以外であればログ出力または強制変更
If tsk.ConstraintType <> pjConstraintAsSoonAsPossible Then

‘ 【運用ルール】特定の制約(例: 契約上のマイルストーンなど)以外はASAPに強制移行
‘ ここでは安全のため、MSO (Must Start On) 以外の不要な制約をリセットする例
If tsk.ConstraintType = pjConstraintMustStartOn Or _
tsk.ConstraintType = pjConstraintMustFinishOn Then
‘ 致命的な制約はログに記録(イミディエイトウィンドウ)
Debug.Print “Protected Constraint Found: ID=” & tsk.ID & ” Name=” & tsk.Name & ” Type=” & tsk.ConstraintType
Else
‘ 制約を標準の「できるだけ早く」へ変更
tsk.ConstraintType = pjConstraintAsSoonAsPossible
modifiedCount = modifiedCount + 1
End If

End If
End If
End If

‘ 参照の明示的解放(メモリリーク防止)
Set tsk = Nothing

‘ 1000件ごとに進捗をステータスバーに表示
If i Mod 1000 = 0 Then
Application.StatusBar = “Processing tasks… ” & i & ” / ” & totalTasks
End If
Next i

‘ 3. 変更の確定と再計算
Application.Calculation = originalCalc
ActiveProject.Calculate

‘ 4. 正常終了処理
Application.ScreenUpdating = True
Application.StatusBar = “”

MsgBox “制約条件のクリーンアップが完了しました。” & vbCrLf & _
“スキャン対象タスク数: ” & targetTaskCount & vbCrLf & _
“修正されたタスク数: ” & modifiedCount & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, _
vbInformation, “VBA Architecture – Success”

Exit Sub

ErrorHandler:
‘ 異常終了時のフォールバック
Application.Calculation = originalCalc
Application.ScreenUpdating = True
Application.StatusBar = “”
Set tsk = Nothing

MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “VBA Architecture – Error”
End Sub

3. コードの深層解説:なぜこの実装なのか?

① `Application.Calculation = pjManual` の破壊力

MS Projectは、タスクのプロパティ(制約、依存関係、カレンダーなど)が1つ書き換えられるたびに、依存グラフ全体の再計算をバックグラウンドで走らせる。これを無効化 (`pjManual`) することで、メモリ上のデータ構造を一括書き換えし、最後に `ActiveProject.Calculate` で一度だけCPM(クリティカルパス法)を走らせる。これにより、処理時間が数分から数秒(体感100倍以上)へと劇的に短縮される。

② コレクション走査における `Nothing` 対策

MS Projectの `Tasks` コレクションは1オリジン(1始まり)であり、タスクが途中で削除されるとインデックスの欠番(あるいはプレースホルダー)として `Nothing` を返すケースがある。`On Error Resume Next` と `If Not tsk Is Nothing Then` の組み合わせ、そしてループごとの `Set tsk = Nothing` による参照カウントの即時デクリメントが、VBAランタイムにおけるメモリ肥大化(Bloat)を防ぐ唯一の防壁となる。

③ ガバナンス要件の組み込み

すべての制約を機械的に「ASAP」にしてしまうと、顧客との契約納期(Must Finish On)や、物理的な資材搬入日(Must Start On)といったビジネス上絶対動かしてはならないアンカーまで破壊してしまう。
上記のコードでは、あらかじめ保護すべき致命的な制約(`pjConstraintMustStartOn` 等)を検知してログに退避させ、無秩序に蔓延した「SNET(指定日以降に開始)」などのバッドプラクティスだけを外科手術的に排除する設計にしている。

4. エンタープライズ運用への拡張:システム間連携と保守性

このVBAマクロを単なる「個人のローカルツール」で終わらせてはならない。組織全体のPMOガバナンスに組み込むための要諦を記す。

  • アドイン(.global / .ppa)化の推奨:

各プロジェクトファイル(.mpp)にこのマクロを埋め込むのではなく、MS Projectのグローバルテンプレート(`Global.mpt`)に配置するか、署名済みのVBAアドインとして組織に配布すること。これにより、どのプロジェクトマネージャーがファイルを開いても、ワンクリックでスケジュールを「健康な状態」に維持できる。

  • ログの外部出力:

イミディエイトウィンドウへの出力だけでなく、修正されたタスクIDや変更前の制約内容をCSVやデータベース(SQL Server / SharePoint Lists)へ非同期にログ送信する仕組みを組み合わせることで、「誰がいつ、どのようなスケジュール汚染を持ち込んだか」の監査証跡(Audit Trail)を確立できる。

結びにかえて

VBAはレガシーな言語と揶揄されることがある。しかし、MS ProjectのCOMオートメーションの深層を理解し、OSのリソース管理とアルゴリズムの挙動を完全に掌握したエンジニアが書いたコードは、現代のどの高水準言語のスクリプトをも凌駕する圧倒的な実行速度と信頼性を発揮する。

スケジュール汚染に悩むすべてのシニアエンジニアに、このエンジンを捧げる。あなたのプロジェクトのクリティカルパスに、もはや無用なノイズは存在しない。

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