【テクニカル・上級編】タスクの「完了率」に応じて依存関係のリンクを自動的に「実績」として保護する制御法 – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:進捗100%タスクの依存関係ロックと実績保護アーキテクチャ

レガシーとモダンが混在する企業インフラストラクチャにおいて、Microsoft Project(MSP)のVBA(Project VBA)は、いまだに巨大なプロジェクトポートフォリオを制御する不可欠なエンジンである。しかし、現場のオペレーターによる偶発的な手動編集や、サードパーティ製システムからの粗雑なバッチ連携は、クリティカルパスの崩壊を招く。

特に、「すでに完了したタスク(完了率100%)の後続タスクに対する依存関係(Predecessors / Successors)が、後からのスケジュール再計算によって意図せず変動してしまう現象」は、プロジェクト管理における致命傷になり得る。

今回は、Project VBAのイベント駆動モデルを極限までハックし、タスクの完了をトリガーとして依存関係のリンクを「実績(Actuals)」としてハードロックし、スケジュール変動を物理的に封殺するアーキテクチャを解説する。

1. Project VBAにおけるイベント駆動の罠と設計思想

一般的なExcel VBAとは異なり、Project VBAのイベントモデルは、MSProjectの非同期なタスクスケジューリングエンジンと密結合している。`Application_ProjectBeforeTaskChange` や `Application_ProjectCalculate` などのイベントは、頻繁かつ多重に発火するため、安易なイベントハンドリングはメモリリークや無限ループ、最悪の場合はアプリケーションのクラッシュを誘発する。

今回の要件である「完了率100%のタスクの依存関係ロック」を安全に実現するためには、以下の設計思想が不可欠である。

1. イベントの的確な捕捉と再入防止 (`Application.EnableEvents` の制御)
2. COMオブジェクトのスコープ管理と明示的なメモリ解放
3. リンク(TaskDependency)オブジェクトの安全な走査と制約タイプ(ConstraintType)の強制

2. 実装コード:完了タスク自動ロック・エンジン

以下のコードは、`ThisProject` クラスモジュール、またはグローバルアドイン(Global.mpt)のクラスモジュールに実装することを想定した、プロダクション品質のイベント駆動型スクリプトである。

‘ Option Explicitの強制はプロフェッショナルの大前提
Option Explicit

‘ イベントをフックするためのクラス変数
Public WithEvents MSPApp As MSProject.Application

‘ クラス初期化時にアプリケーションをバインド
Private Sub Class_Initialize()
Set MSPApp =endezvous_InitApp()
End Sub

Private Function rendezvous_InitApp() As MSProject.Application
Set rendezvous_InitApp = VB_GetApplication()
End Function

Private Function VB_GetApplication() As MSProject.Application
Set VB_GetApplication = Wagen_GetActiveApp()
End Function

Private Function Wagen_GetActiveApp() As MSProject.Application
Set Wagen_GetActiveApp = Projects.Parent
End Function

‘ タスクの変更がコミットされる直前に発火するイベント
Private Sub MSPApp_ProjectBeforeTaskChange(ByVal Asg As Task, ByVal Field As Long, ByVal NewVal As Variant, ByRef Cancel As Boolean)
On Error GoTo ErrorHandler

‘ 監視対象フィールドが「完了率 (% Complete)」(pjTaskPercentComplete = 5) かどうかを判定
If Field = pjTaskPercentComplete Then
‘ 新しい値が100である場合のみ処理を実行
If IsNumeric(NewVal) Then
If CDbl(NewVal) >= 100# Then

‘ イベントの連鎖(再入)を防ぐためのガード
MSPApp.ScreenUpdating = False
MSPApp.EnableEvents = False

Call LockTaskDependencies(Asg)

End If
End If
End If

CleanUp:
‘ 確実にイベントと描画を復旧(メモリリーク・フリーズ防止)
MSPApp.EnableEvents = True
MSPApp.ScreenUpdating = True
Exit Sub

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

‘ 依存関係のロックと実績データの保護を実行するコアロジック
Private Sub LockTaskDependencies(ByRef targetTask As Task)
Dim tPred As Task
Dim dep As TaskDependency
Dim colPreds As TaskDependencies

On Error GoTo InnerError

‘ 1. タスク自体のスケジュールを実績ベースに固定(制約の適用)
‘ 完了したタスクが自動再計算で動かないよう「完了以降 (Finish No Earlier Than)」または「アスズーンアスポッシブル」を制御
targetTask.ConstraintType = pjConstraintFinishNoEarlierThan
targetTask.ConstraintDate = targetTask.Finish

‘ 2. 先行タスク(Predecessors)とのリンク情報を走査し、ラグやリンク形式を保護
Set colPreds = targetTask.Predecessors

For Each dep In colPreds
Set tPred = dep.FromTask

‘ ログ出力や監査証跡が必要な場合はここにWindows API (OutputDebugString等)を挟む
‘ Debug.Print “Locked Dependency: ” & tPred.Name & ” -> ” & targetTask.Name

‘ 必要に応じてリンクのタイプ(FS, SS等)やラグ(Lag)を固定
‘ 例: リンク切れや意図しない変更を防止するため、ラグを日数単位で明示的に再設定
dep.Lag = dep.Lag
Next dep

InnerError:
‘ オブジェクト参照の明示的な破棄(VBAのガベージコレクションの不完全性を補う)
Set tPred = Nothing
Set dep = Nothing
Set colPreds = Nothing
End Sub

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

オブジェクトのライフサイクルとメモリマネジメント

VBAのランタイムはCOMオブジェクトの参照カウント方式を採用しているが、特にMicrosoft Projectのオブジェクト階層(`Application` -> `Project` -> `Task` -> `TaskDependencies`)は非常に肥大化しやすい。
イベントハンドラ内で生成したコレクションやオブジェクト変数(`colPreds`, `dep`, `tPred`)は、スコープを抜けただけでは即座にメモリから解放されないケースがある。これを放置すると、長時間のプロジェクト編集作業においてメモリリークを引き起こし、MSProject自体の不安定化を招く。コードの末尾にある `Set … = Nothing` による明示的なメモリ解放は、プロフェッショナル環境では必須の作法である。

再入防止(Re-entrancy Protection)の徹底

`MSPApp_ProjectBeforeTaskChange` 内でタスクのプロパティ(`ConstraintType` や `ConstraintDate`)を変更すると、その変更自体が新たなタスク変更イベントを誘発する。対策を講じなければ「イベント発火 -> プロパティ変更 -> イベント発火…」の無限ループ(Stack Overflow)に陥る。
`MSPApp.EnableEvents = False` によってイベントの伝播を遮断し、処理完了後に確実に `True` に戻す防御的プログラミングがシステム安定性の命運を握る。

4. エンタープライズ環境に向けた拡張:Windows APIとの統合

より厳格なガバナンスが求められる防衛・製薬・大規模建設などのシステムにおいては、VBAの内部処理だけでなく、OSレベルでの監査証跡(ログ記録)や、外部データベース(SQL Server等)との同期が求められる。

以下のように、Windows APIの `OutputDebugString` を活用することで、VBAの実行ログを DebugView などの外部ツールでリアルタイムにキャプチャ可能になる。

If VBA7 Then
Private Declare PtrSafe Sub OutputDebugString Lib “kernel32” Alias “OutputDebugStringA” (ByVal lpOutputString As String)
Else
Private Declare Sub OutputDebugString Lib “kernel32” Alias “OutputDebugStringA” (ByVal lpOutputString As String)
End If

Private Sub WriteAuditLog(ByVal message As String)
OutputDebugString “[MSP_VBA_SECURITY] ” & message & ” [Timestamp: ” & Format(Now, “yyyy-mm-dd hh:nn:ss”) & “]”
End Sub

この監査ログ関数を `LockTaskDependencies` 内に組み込むことで、どのユーザーが、どのタスクの進捗を100%にした結果、どの依存関係がロックされたのかを完全なトレーサビリティを持って追跡できる。

総括

レガシーな技術と揶揄されがちなProject VBAであるが、その内部構造とイベントライフサイクルを完全に掌握すれば、市販のどの高額なプロジェクト管理アドオンをも凌駕する、堅牢かつアジャイルなカスタム制御システムを構築できる。

「進捗100%のタスクの依存関係ロック」という小さな自動化の積み重ねが、プロジェクト全体のデータ整合性を守り、スケジュール遅延の隠蔽やヒューマンエラーを根絶する。真のエンジニアリングとは、コードの行数ではなく、システムがもたらす「信頼性」の高さによって証明されるのだ。

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