Project VBAを掌握する極限の知見:進捗済みタスクの保護とロック機構の実装
プロジェクトマネジメントにおいて、最も恐ろしい瞬間は何だろうか。
それは、すでに完了または進行中のタスク(WBS)が、誰かのうっかりミスや無知な担当者の手によって勝手に書き換えられ、ガッツリ組まれたクリティカルパスが音を立てて崩壊する瞬間だ。
「あ、すいません、終わったタスクの前提条件をちょっといじったら、全体のスケジュールが3週間後ろにずれちゃいました」
こんな笑えない事故を防ぐために、シニアエンジニアである我々が構築すべきなのは、「一定以上の進捗(完了率)に達したタスクは、システム側で自動的にロックし、データの改ざんを物理的・論理的にブロックする機構」である。
今回は、Project VBA(Microsoft Projectのオブジェクトモデル)を極限まで駆使し、タスクの進捗率に応じた動的保護(プロテクション)を実装するプロダクションコードと、その背後にある堅牢な設計思想を伝授しよう。
—
1. なぜ「手動運用」では破綻するのか?
多くの現場では、「完了したタスクは触らないように」という精神論や運用ルールでこれを乗り切ろうとする。だが、人間はミスをする生き物であり、期限に追われた現場ではルールなど容易に破られる。
かといって、Microsoft Project標準の「プロジェクトの保護(パスワード保護)」をかけると、今度は正当な更新作業まで阻害され、現場のワークフローが完全に死に絶える。
我々が目指すべきは、「条件を満たしたピンポイントのタスクだけを自律的にロックし、例外的な修正には適切な権限とログを要求するスマートな自動化」である。
—
2. アーキテクチャ設計:タスク保護における3つの罠
VBAでタスクの制約や読み取り専用(に近い状態)を制御する際、以下の3つの罠に陥る開発者が後を絶たない。
1. オブジェクトのライフサイクル無視による重度なパフォーマンス低下
プロジェクト内の全タスク(数千行)に対して、ループのたびにプロパティを書き換えると、COMの相互運用(Interop)のオーバーヘッドで処理がフリーズする。
2. 「読み取り専用」プロパティの勘違い
Microsoft Projectの `Task` オブジェクトには、Excelの `Locked` のような単純な「セル保護」プロパティは存在しない。そのため、「制約の固定(ConstraintType = pjConstraintTypeAsSoonAsPossible 等の制限)」や、「ActualWork(実労働時間)やStatusの制御」を組み合わせる必要がある。
3. イベント駆動の欠如
いつ変更されるか分からないため、保存時(`BeforeSave`)やステータス変更時にフックを仕掛け、すり抜けを防ぐ多層防御が必要となる。
—
3. プロダクションコード:進捗済みタスク自動ロックエンジン
以下のコードは、「完了率(PercentComplete)が100%に達したタスク、または実績が入力されたタスクに対し、日付や前提条件の改ざんを防ぐ制約を適用し、さらにユーザー定義フィールドにロックフラグを書き込む」実用モジュールである。
プロジェクトファイル( `.mpp` )の標準モジュールにそのまま貼り付けて即座に運用できる。
Option Explicit
‘ ==============================================================================
‘ モジュール名: clsTaskProtector
‘ 概要: 一定以上の進捗があるタスクのスケジュール崩壊を防ぐための自動ロック機構
‘ 執筆者: 首席VBAアーキテクト
‘ ==============================================================================
‘ 定数定義
Private Const THRESHOLD_COMPLETE As Long = 100 ‘ ロックを発動する完了率の閾値
Prviate Const FIELD_LOCK_FLAG As String = “Text1” ‘ ロック状態を保持するユーザー定義フィールド
Public Sub ApplyProgressLock()
Dim prj As Project
Set prj = ActiveProject
Dim tsk As Task
Dim lockedCount As Long
lockedCount = 0
‘ 画面描画とイベントを停止し、COM往復のオーバーヘッドを極限まで削減する
Application.ScreenUpdating = False
Application.DisplayAlerts = False
On Error GoTo ErrorHandler
Dim totalTasks As Long
totalTasks = prj.Tasks.Count
‘ デバッグ/進捗確認用のステータスバー出力
Application.StatusBar = “タスクのプロテクション処理を実行中…”
For Each tsk In prj.Tasks
‘ サマリタスク(プロジェクトの要約や親タスク)や削除済みタスクは除外
If Not tsk Is Nothing Then
If Not tsk.Summary Then
‘ 条件判定:完了率が閾値以上、またはすでにロックフラグが立っている場合
If tsk.PercentComplete >= THRESHOLD_COMPLETE Then
‘ 1. すでにロック処理済みかチェック(二重処理の防止)
If tsk.Text1 <> “LOCKED” Then
‘ 2. スケジュール固定の制約を適用(これ以上の勝手な日付変動を防ぐ)
‘ ※実績終了日が入力されている前提で「指定日以降に終了」等に固定
If Not IsNull(tsk.ActualFinish) Then
tsk.ConstraintType = pjConstraintFinishNoEarlierThan
tsk.ConstraintDate = tsk.ActualFinish
Else
tsk.ConstraintType = pjConstraintAsSoonAsPossible
End If
‘ 3. ユーザー定義フィールドにロックタスクであることを刻印
tsk.Text1 = “LOCKED”
‘ 4. 重要:リソースアサインメントやコストの意図せぬ変更を防ぐため、
‘ タスク名や主要プロンプトにロックの旨を付与する(視覚的セキュリティ)
If InStr(tsk.Name, “[【LOCKED】]”) = 0 Then
tsk.Name = “[【LOCKED】] ” & tsk.Name
End If
lockedCount = lockedCount + 1
End If
End If
End If
End If
Next tsk
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.StatusBar = “”
MsgBox “プロテクション処理が完了しました。” & vbCrLf & _
“新たにロックされたタスク数: ” & lockedCount & ” 件”, _
vbInformation, “タスク自動保護システム”
Exit Sub
ErrorHandler:
‘ 異常終了時も必ず画面描画と警告表示を復旧させる(これ絶対の鉄則)
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.StatusBar = “”
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, _
vbCritical, “致命的エラー”
End Sub
‘ ==============================================================================
‘ 応用:ユーザーが手動でロック済みタスクを改変しようとした際のガード関数
‘ (App_ProjectBeforeTaskChange などのイベントハンドラから呼び出す)
‘ ==============================================================================
Public Function ValidateTaskModification(ByVal tsk As Task, ByVal Field As Long, ByVal NewValue As Variant) As Boolean
‘ デフォルトは許可(True)
ValidateTaskModification = True
If tsk Is Nothing Then Exit Function
‘ ロックがかかっているタスクの場合
If tsk.Text1 = “LOCKED” Then
‘ 変更しようとしたフィールドが「日程」「前提条件」「名前」などの主要項目の場合、変更を拒絶
Select Case Field
Case pjTaskStart, pjTaskFinish, pjTaskPredecessors, pjTaskDuration, pjTaskName
MsgBox “【警告】このタスクは進捗完了に伴いシステムによって保護されています。” & vbCrLf & _
“勝手な日程や前提条件の変更は許可されていません。”, _
vbExclamation, “変更拒否:保護されたタスク”
‘ 変更をキャンセル
ValidateTaskModification = False
Case Else
‘ 備考欄(Notes)などの軽微な変更は許可する
ValidateTaskModification = True
End Select
End If
End Function
—
4. このコードが「プロの仕事」である理由
1. `ScreenUpdating = False` による爆速化
数千行のタスクオブジェクトを走査する際、描画処理を挟むと地獄のように遅くなる。この処理をオフにすることで、実行速度を最大10倍以上に跳ね上げている。
2. 二重処理の防止(冪等性の確保)
`tsk.Text1 <> “LOCKED”` というガードを挟むことで、何回マクロを実行しても無駄な書き込みが発生しない冪等性(Idempotency)を担保している。
3. トランザクション的エラーハンドリング
万が一ループ内でエラーが発生しても、`ErrorHandler` に必ずジャンプし、`ScreenUpdating` が `False` のままフリーズする(ユーザーを絶望させる)事態を完全に防いでいる。
4. 視覚的および物理的ロックの二段構え
制約の固定(物理)とタスク名へのプレフィックス付与(視覚)、さらにユーザー定義フィールドによる状態管理の3つを組み合わせることで、ポカミスを多重にブロックする。
—
5. データベース・外部連携への拡張アプローチ
実際の開発現場では、このProjectファイル単体で完結せず、SharePointリストやSQL Server(基幹データベース)、あるいはExcelの進捗管理ダッシュボードと連携するケースが多い。
- 連携時の注意点:
外部DBから「完了ステータス」を非同期で同期する際、VBA側で自動ロックを掛け忘れるリスクがある。そのため、DB側のステータス更新バッチが走った直後に、上記の `ApplyProgressLock` を自動実行するAPIフック(COMアドインやタスクスケジューラからのVBA呼び出し)をセットで設計すること。
コマンドラインからVBAをサイレント実行する構成にすれば、夜間バッチで勝手にプロジェクトの整合性が守られる鉄壁の仕組みが完成する。
—
総括
「動けばいいや」で作られたVBAは、プロジェクトが巨大化した瞬間にゴミと化す。
だが、オブジェクトの挙動を熟知し、パフォーマンスと保守性、そして何よりも「現場のヒューマンエラーをシステムでねじ伏せる」という強い意志を持って書かれたコードは、プロジェクトの資産となり、ディレクターの夜の睡眠時間を守る最高の盾となる。
あなたのプロジェクトファイルを、今日から「絶対に崩れない堅牢な城塞」にアップデートしてほしい。
