MS Project VBAを極める:タスク依存関係の「変更理由」をメモ欄へ完全自動監査する要塞型ロジック
プロジェクトマネジメントにおいて、スケジュール変更は日常茶飯事だ。しかし、「なぜこのタスクとこのタスクに先行・後続の縛り(依存関係)を入れたのか?」という変更の文脈(コンテキスト)が後から追えなくなる現場があまりにも多すぎる。担当者が変わり、理由不明のリンクが張り巡らされたガントチャートは、もはやプロジェクトの羅針盤ではなく「解読不能の呪い」と化す。
今回は、MS Project VBAを用いて「タスク間の依存関係が変更された瞬間を捉え、その変更の文脈(誰が・いつ・なぜ)をタスクのメモ(Notes)フィールドに自動的に監査ログとして刻み込む」仕組みを解説する。
安易なイベントハンドラの乱用がいかにプロジェクトファイルを崩壊させるか、そしてプロフェッショナルはどう堅牢性を担保するのか。その極限の知見を授けよう。
—
1. なぜ「メモ欄の自動監査」が必要なのか?
多くの現場では、変更管理といえばExcelのマクロや外部DB連携を思い浮かべるだろう。だが、スケジュールを直に触るプレイヤーにとって、情報が散在する外部ツールは見向きもされない。
「情報源(Single Source of Truth)はあくまでMS Projectのファイル内、それもタスクのすぐ傍に置くべきだ」
タスクの「メモ(Notes)」フィールドに依存関係の履歴を集約させることで、以下のメリットが生まれる。
- コンテキストの属人化防止: ガントチャートを引き継いだ後任者が、リンクの意図を瞬時に把握できる。
- 変更の透明性担保: 監査証跡(Audit Trail)がプロジェクトファイル内に直接刻まれるため、事後検証が容易になる。
しかし、MS Projectのオブジェクトモデルにおいて、タスクのリンク(Predecessors / Successors)を監視し、その変更理由を動的に取得・記録するのは、一歩間違えば無限ループやメモリリークを引き起こす危険な領域だ。この設計の急所をロジカルに解き明かしていこう。
—
2. 堅牢な設計:バグを生む「愚直なアプローチ」と決別する
まず、アマチュア開発者が陥りがちなアンチパターンを指摘する。
- × アプローチ1: `TaskBeforeAddDependency` などの標準イベントをそのまま信じる
MS Projectのイベントは、UIからの操作とVBAからの操作で発火タイミングや引数の状態が微妙に異なる。特にマルチユーザー環境(Server/Online)や大規模WBSでは、イベントの多重発火によるデッドロックが起きやすい。
- × アプローチ2: 変更理由を都度ポップアップ(InputBox)で強要する
作業者のリズムを阻害する入力要求は、結局「適当な文字を入力して逃げる」というゴミデータを生む温床になる。
プロフェッショナルのアプローチ:トランザクション的アプローチと履歴蓄積
本稿で提案するのは、「依存関係の変更を検知するラッパープロシージャ」と、「メモ欄のパース(構造化保持)」を組み合わせたハイブリッド設計だ。既存のメモを上書きして消し飛ばすような愚行は犯さない。過去のログの末尾に、タイムスタンプ付きの監査証跡を美しくアペンド(追記)していく。
—
3. プロダクションコード:依存関係監査ログエンジン
以下のコードを、MS ProjectのVBE(Visual Basic Editor)内の標準モジュールに配置してほしい。
実務でそのまま組み込めるよう、エラーハンドリングとメモリ管理を徹底的に施したプロダクションコードだ。
Option Explicit
‘ ==============================================================================
‘ ódulo名: MdlDependencyAuditor
‘ 概要: タスクの依存関係変更時に、理由を添えてメモ欄へ自動監査ログを追記する
‘ 依存関係のライフサイクルを安全に管理するための極限設計コード
‘ ==============================================================================
Public Sub AddTaskDependencyWithAudit(ByRef targetTask As Task, ByRef predecessorTask As Task, Optional ByVal linkType As PjTaskLinkType = pjLinkFinishToStart, Optional ByVal lag As Variant = 0)
On Error GoTo ErrorHandler
Dim appProj As Application
Set appProj = ActiveProject.Application
‘ 1. バリデーション:無効なタスク参照の弾薬処理
If targetTask Is Nothing Or predecessorTask Is Nothing Then
MsgBox “指定されたタスクが無効です。”, vbCritical, “監査エンジンエラー”
Exit Sub
End If
If targetTask.ID = predecessorTask.ID Then
MsgBox “自分自身を先行タスクに設定することはできません。”, vbExclamation, “論理エラー”
Exit Sub
End If
‘ 2. 変更理由のヒアリング(実務ではUserFormや外部コンテキストからの入力を推奨)
Dim auditReason As String
auditReason = InputBox( _
“【変更管理 監査プロンプト】” & vbCrLf & _
“タスク [” & predecessorTask.Name & “] から [” & targetTask.Name & “] への依存関係設定理由を入力してください:”, _
“依存関係の変更理由入力”, _
“特記事項なし(自動設定)”)
If StrPtr(auditReason) = 0 Then
‘ キャンセルされた場合は処理を中断
Exit Sub
End If
If Trim(auditReason) = “” Then
auditReason = “理由未入力”
End If
‘ 3. トランザクション処理の開始(UNDOブロックの確保)
appProj.UndoContext “依存関係の追加と監査ログ記録”
appProj.ScreenUpdating = False
‘ 4. 依存関係(Link)の実際の追加
‘ TaskLinksコレクションに追加する
targetTask.TaskLinks.Add Prédecesseur:=predecessorTask, Type:=linkType, Lag:=lag
‘ 5. メモ(Notes)フィールドへの監査ログのフォーマットと追記
Call AppendAuditLogToTaskNotes(targetTask, predecessorTask.Name, auditReason)
‘ 6. クリーンアップ
appProj.ScreenUpdating = True
MsgBox “依存関係の設定と監査ログの記録が正常に完了しました。”, vbInformation, “成功”
Exit Sub
ErrorHandler:
‘ 異常系ハンドリング:画面描画の復旧とロールバック的措置
appProj.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error No: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ ==============================================================================
‘ 内部プロシージャ: タスクのメモ欄へ構造化された監査ログを追記する
‘ ==============================================================================
Private Sub AppendAuditLogToTaskNotes(ByRef t As Task, ByVal predName As String, ByVal reason As String)
Dim currentNotes As String
Dim logEntry As String
Dim userName As String
currentNotes = t.Notes
userName = Environ$(“UserName”) ‘ Windowsのログオンユーザー名を取得
‘ 監査ログのフォーマット作成
‘ [日時] [ユーザー] 操作内容 (理由: XXX)
logEntry = “— [監査ログ] ” & Format$(Now, “yyyy/mm/dd hh:nn:ss”) & ” —” & vbCrLf & _
“操作者: ” & userName & vbCrLf & _
“追加された先行タスク: ” & predName & vbCrLf & _
“変更理由: ” & reason & vbCrLf
If Len(currentNotes) > 0 Then
‘ 既存のメモがある場合は、新しいログを上部に配置し、過去のログと区切る
t.Notes = logEntry & vbCrLf & “====================” & vbCrLf & currentNotes
Else
t.Notes = logEntry
End If
End Sub
—
4. チーフアーキテクトが教える、実運用における注意点
この仕組みを実際の企業環境にデプロイする際、以下のインフラ的・運用的な罠に気をつけてほしい。
1. ユーザー名 (`Environ$(“UserName”)`) の限界
ローカル環境では問題ないが、仮想デスクトップ(VDI)環境や共有アカウント運用を行っている現場では、`Environ$(“UserName”)` が実際の個人を特定できない場合がある。より厳格な監査が必要な場合は、ActiveDirectoryのUPNや、独自に入力させるダイアログボックスへ拡張すべきだ。
2. メモフィールドの文字数制限とパフォーマンス
MS Projectの `Notes` フィールド(RTF形式として内部処理される文字列)は、長大になりすぎるとファイルサイズを圧迫し、パフォーマンスの劣化を招く。
しかし、一般的なプロジェクトライフサイクルにおいて、タスクあたりの依存関係変更が高々数十回程度であれば、実用上のパフォーマンス影響は完全に無視できるレベルである。もし懸念がある場合は、一定数(例: 直近5件)を超えた古いログを自動トリミングするロジックを `AppendAuditLogToTaskNotes` 内に組み込むと完璧だ。
3. Undo/Redo (アンドゥ) との調和
`ActiveProject.UndoContext` を明示的に使っている点に注目してほしい。MS Projectのオブジェクト操作は、VBAから行うと一連の操作が単一のアンドゥスタックに綺麗に乗らないことがある。ユーザーが「Ctrl + Z」を押した際に、依存関係の削除とメモの改変が綺麗に同期して巻き戻るよう、トランザクションの境界を明示することがプロのコードの条件だ。
—
5. おわりに:ツールに「意志」を宿せ
VBAを書くというのは、単に作業をサボるためのコードを並べることではない。「組織のプロセスやプロジェクトのガバナンスを、コードの力で半強制的に、かつ美しく担保する」ことこそが、真の業務自動化エンジニアの仕事である。
今回紹介した依存関係の監査ログ機構は、単なる便利マクロではない。メンバー一人ひとりに「なぜこのリンクを繋ぐのか」というエンジニアリングの基本(思考のプロセス)を意識させ、プロジェクトの透明性を極限まで高めるための「要塞」である。
ぜひ自社のプロジェクトにこの知見を組み込み、混沌としたスケジュール管理から脱却してほしい。あなたの書くコードが、プロジェクトを成功へと導く最強の防壁となることを期待している。
