MS Projectの闇を暴け:Applicationイベント駆動型「変更監査ロガー」の極意
プロジェクトマネジャーの頭痛の種。それは、「誰が勝手にスケジュールをいじったのか分からない」という恐怖だ。
ベースラインを引いたはずなのに、いつの間にか誰かがタスクの依存関係を書き換え、リソースをオーバーブッキングさせ、クリティカルパスが音もなく崩壊している——。
一般的なExcelベースのツールであればシートの保護や変更履歴機能で追跡できるが、Enterprise環境ではないスタンドアロンのMicrosoft Project(.mpp)運用において、変更履歴の自動監査は長年の課題とされてきた。
今回は、MS Projectの底に眠る`Application`オブジェクトのイベントハンドラを完全掌握し、プロジェクトファイルへの変更を一切の漏れなく外部ログへ叩き込む「監査ロガー」のプロダクションコードを授けよう。
安易なタイマー処理や、ユーザーの善意に頼った保存時マクロとは決別する。オブジェクトのライフサイクルを理解した真のプロフェッショナルだけが書ける、堅牢かつ高速な自動化アーキテクチャを解説する。
—
1. なぜ「普通のVBAコード」では監査に失敗するのか?
多くのVBAエンジニアが犯す最大の過ちは、`ThisProject`モジュールにイベント(`Project_BeforeClose`など)を記述することだ。
甘い。それでは不十分だ。
`ThisProject`のイベントは、そのプロジェクトが開かれている間しか機能しない。ユーザーが新規プロジェクトを開いた瞬間、あるいは別のプロジェクトをアクティブにした瞬間、あなたの監視網はすり抜けられる。さらに言えば、タスクの追加や変更をリアルタイムでフックするには、グローバルなスコープを持つ`Application`レベルのイベントを捕捉しなければならない。
ここで必要になるのが、クラスモジュールを用いたイベントのフック(WithEvents)である。
—
2. 堅牢な監査ロガー設計の要件
プロダクション環境で耐えうるロガーを作るには、以下の3点を満たす必要がある。
1. 非同期・軽量な書き込み: ログファイル(CSVまたは外部DB)への書き込みでMS Project本体をフリーズさせない。
2. 多重起動・インスタンス消滅の防止: クラスのスコープが途中でドロップし、イベントがデタッチされる事故を防ぐ。
3. 正確なデルタ(差分)の取得: 何がどう変わったのか(旧値→新値)を記録する。
今回は最も実用的な「CSVファイルへの追記方式」を採用する。ネットワーク上の共有フォルダにタイムスタンプ付きの監査ログを吐き出させることで、改ざん不能な変更履歴を実現する。
—
3. プロダクションコード実装
以下の構成でVBAプロジェクトを構築する。
- 標準モジュール (`basEventListener`): アプリケーション起動時のイベントフック初期化
- クラスモジュール (`clsAppEvents`): MS Project全体のイベントを監視する心臓部
- 標準モジュール (`basLogger`): ログ出力のI/O処理
① クラスモジュール: `clsAppEvents`
MS Projectのアプリケーションイベントをキャッチする。
‘ Option Explicitの強制(プロフェッショナルの基本)
Option Explicit
‘ WithEventsを使用してApplicationオブジェクトを監視
Public WithEvents MSApp As MSProject.Application
‘ プロジェクトオープン時
Private Sub MSApp_ProjectOpen(ByVal pw As MSProject.Project)
Call LogAction(pw, “PROJECT_OPEN”, “ファイルが開かれました”)
End Sub
‘ プロジェクト保存前
Private Sub MSApp_ProjectBeforeSave(ByVal pw As MSProject.Project, ByVal SaveAsUI As Boolean, Cancel As Boolean)
Call LogAction(pw, “PROJECT_SAVE”, “ファイルが保存されます”)
End Sub
‘ タスク変更時(プロパティ変更の検知)
Private Sub MSApp_ProjectBeforeTaskChange(ByVal tsk As MSProject.Task, ByVal Field As MSProject.PjField, ByVal NewValue As Variant, Cancel As Boolean)
If tsk Is Nothing Then Exit Sub
Dim oldValue As Variant
On Error Resume Next
oldValue = tsk.GetField(Field)
On Error GoTo 0
Dim details As String
details = “Task ID: ” & tsk.ID & ” [” & tsk.Name & “] | Field: ” & Field & ” | Before: ” & CStr(oldValue) & ” -> After: ” & CStr(NewValue)
Call LogAction(tsk.Project, “TASK_CHANGE”, details)
End Sub
② 標準モジュール: `basLogger` (ログ出力I/O)
安全かつ確実なファイルI/Oを提供する。エラーでMS Projectを巻き込んでクラッシュさせない配慮が不可欠だ。
Option Explicit
Public Const LOG_FILE_PATH “C:\ProjectAuditLogs\AuditLog.csv”
Public Sub LogAction(ByVal prj As MSProject.Project, ByVal actionType As String, ByVal details As String)
On Error GoTo ErrorHandler
Dim fileNum As Integer
fileNum = FreeFile
‘ ログディレクトリの自動生成(存在しない場合)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(“C:\ProjectAuditLogs”) Then
fso.CreateFolder “C:\ProjectAuditLogs”
End If
‘ CSVへの追記モードでオープン
Open LOG_FILE_PATH For Append As #fileNum
‘ フォーマット: タイムスタンプ, ユーザー名, プロジェクト名, アクション, 詳細
Print #fileNum, Format(Now, “yyyy-mm-dd hh:nn:ss”) & “,” & _
Environ$(“UserName”) & “,” & _
Chr(34) & prj.Name & Chr(34) & “,” & _
actionType & “,” & _
Chr(34) & Replace(details, Chr(34), “”””””) & Chr(34)
Close #fileNum
Exit Sub
ErrorHandler:
‘ 監査ログの書き込み失敗で本体の作業を妨げてはならないため、エラーはイミディエイトに出して握りつぶす
Debug.Print “Log Write Error: ” & Err.Description
If fileNum > 0 Then Close #fileNum
End Sub
③ 標準モジュール: `basEventListener` (初期化のトリガー)
このモジュールが、グローバルイベントのインスタンスを生存させ続ける。
Option Explicit
‘ クラスのインスタンスを保持し続けるためのグローバル変数
Public AppListener As clsAppEvents
‘ MS Project起動時に自動実行される特殊プロシージャ
Public Sub Auto_Open()
Call InitializeEventListener
End Sub
Public Sub InitializeEventListener()
Set AppListener = New clsAppEvents
Set AppListener.MSApp = Application
Debug.Print “Project Audit Logger: Initialized successfully.”
End Sub
—
4. チーフアーキテクトからの実践的アドバイス
この仕組みを導入するにあたり、現場で必ず直面する壁と対策を共有しよう。
- マクロのセキュリティポリシー: 組織内で配布する場合、グローバルテンプレート(`Global.mpt`)にこのコードを埋め込むか、アドイン(.ppa)として配布する必要がある。信頼できる場所(Trusted Locations)の設定をグループポリシーで強制しておこう。
- パフォーマンスへの影響: `ProjectBeforeTaskChange`は、ユーザーがガントチャート上でガシガシとタスクを編集するたびに発火する。ログ書き込み時のファイルオープン・クローズの頻度が高すぎると体感速度に影響が出るため、極限までI/Oの回数を削るか、必要に応じてメモリ上にキューイングしてバッチ処理的に書き出す高度な設計も検討してほしい。
終わりに
「誰がいつ触ったかわからない」という言い訳は、今日で終わりだ。
オブジェクトモデルのライフサイクルを理解し、イベントを支配した者だけが、プロジェクトの整合性を守る真のエンジニアと名乗れる。
あなたの現場のPMOツールに、この堅牢な監査ロガーを組み込み、完全なる統制を手に入れてほしい。
