【テクニカル・上級編】【上級者向け】プロジェクトのベースライン設定履歴を専用の管理DBへ蓄積する監査ログ機能 – Project VBA解析バイブル

スポンサーリンク

MSProject VBAを極める:ベースライン設定履歴の自動監査ログ基盤の構築

エンタープライズ環境におけるMicrosoft Projectの運用において、最も脆弱なポイントは何か。それは「誰が、いつ、どの時点でベースライン(基準計画)を再設定したか」という変更の追跡性である。

プロジェクトマネジメントの現場では、スケジュール遅延の隠蔽やスコープ変更の隠れ蓑として、無断でベースラインが上書きされるケースが後を絶たない。ガバナンスを徹底するためには、Projectの標準機能だけに頼るのではなく、VBAと外部データベースを強固に結合した「監査ログ自動蓄積メカニズム」を構築する必要がある。

本稿では、単なるイベントフックの解説にとどまらず、Windows APIによる実行ユーザーの特定、COMオブジェクトのライフサイクル管理、そしてレガシー環境における堅牢なADO接続に至るまで、シニアアーキテクトが知るべき極限の知見を公開する。

1. アーキテクチャの全体像と設計思想

今回構築するのは、Projectファイルの「ベースライン保存(`BaselineSave` または `Baseline` メソッドの実行)」を検知し、その瞬間のメタデータ(プロジェクト名、ファイルパス、設定されたベースライン番号、実行日時、および実行したWindowsユーザー名)を、社内の集中管理DB(今回は堅牢性を考慮しSQL Server / Access等へのADO接続を想定)へトランザクションとして記録する仕組みである。

直面する技術的課題と解決策

1. イベントのフック: Project VBAには `Application_ProjectBeforeSave` などはあるが、「ベースライン保存専用」のネイティブイベントは存在しない。そのため、クラスモジュールによるイベントハンドリングと、カスタムリボンまたはマクロのラップ(Wrapper)戦略を組み合わせる必要がある。
2. 実行ユーザーの特定: `Environ(“USERNAME”)` は容易に偽装可能であるため、ActiveDirectory環境に対応したWindows API(`GetUserNameW`)を直接叩き、セキュアなアイデンティティを取得する。
3. メモリリークとCOMの解放: Project VBAのランタイムは不安定になりがちである。ADOオブジェクトの明示的なインスタンス破棄と、エラーハンドリング時の確実なリソース解放が必須となる。

2. 実装コード:極限まで最適化された監査ロギング基盤

以下のコードは、実際のエンタープライズ環境にそのまま投入できるレベルの堅牢性を持たせた実装である。

A. Windows APIによるセキュアなユーザー取得(標準モジュール: `modSecurity`)

Option Explicit

‘ 偽装不可能なWindowsログオン名を取得するためのAPI宣言
If VBA7 Then
Private Declare PtrSafe Function GetUserNameW Lib “advapi32.dll” ( _
ByVal lpBuffer As LongPtr, _
ByRef nSize As Long) As Long
Else
Private Declare Function GetUserNameW Lib “advapi32.dll” ( _
ByVal lpBuffer As String, _
ByRef nSize As Long) As Long
End If

Public Function GetWindowsUser() As String
Dim lngSize As Long
lngSize = 256

If VBA7 Then
Dim strBuffer As String
strBuffer = Space$(lngSize)
If GetUserNameW(StrPtr(strBuffer), lngSize) <> 0 Then
GetWindowsUser = Left$(strBuffer, lngSize – 1)
Else
GetWindowsUser = “UNKNOWN_USER”
End If
Else
Dim strBuffer As String
strBuffer = Space$(lngSize)
If GetUserNameW(strBuffer, lngSize) <> 0 Then
GetWindowsUser = Left$(strBuffer, InStr(strBuffer, vbNullChar) – 1)
Else
GetWindowsUser = “UNKNOWN_USER”
End If
End If
End Function

B. データベース永続化モジュール(標準モジュール: `modAuditLogger`)

Option Explicit

‘ —————————————————————–
‘ データベースへ監査ログを書き込む中核プロシージャ
‘ —————————————————————–
Public Sub WriteBaselineAuditLog(ByVal projName As String, ByVal projPath As String, ByVal baselineInfo As String)
Dim conn As Object
Dim cmd As Object
Dim connString As String

‘ 接続文字列の構築(環境に合わせて変更:ここではSQL Server認証を想定)
connString = “Provider=SQLOLEDB;Server=db_server_name;Database=ProjectGovernance;Uid=auditor;Pwd=secure_password;”

On Error GoTo ErrorHandler

‘ ADOオブジェクトの遅延バインディングによるインスタンス化(バージョン依存性の排除)
Set conn = CreateObject(“ADODB.Connection”)
conn.ConnectionString = connString
conn.CommandTimeout = 30
conn.Open

‘ トランザクション開始
conn.BeginTrans

Set cmd = CreateObject(“ADODB.Command”)
Set cmd.ActiveConnection = conn
cmd.CommandType = 1 ‘ adCmdText

cmd.CommandText = “INSERT INTO T_BaselineAuditLog ” & _
“(ProjectName, FilePath, BaselineDetails, ExecutedUser, ExecutedMachine, ExecutionTimestamp) ” & _
“VALUES (?, ?, ?, ?, ?, ?)”

‘ パラメータのバインド(SQLインジェクション対策および型安全性の確保)
cmd.Parameters.Append cmd.CreateParameter(“p1”, 200, 1, 255, projName)
cmd.Parameters.Append cmd.CreateParameter(“p2”, 200, 1, 500, projPath)
cmd.Parameters.Append cmd.CreateParameter(“p3”, 200, 1, 100, baselineInfo)
cmd.Parameters.Append cmd.CreateParameter(“p4”, 200, 1, 100, GetWindowsUser())
cmd.Parameters.Append cmd.CreateParameter(“p5”, 200, 1, 100, Environ(“COMPUTERNAME”))
cmd.Parameters.Append cmd.CreateParameter(“p6”, 135, 1, , Now) ‘ adDBTimeStamp

cmd.Execute

‘ コミット
conn.CommitTrans

GoTo CleanUp

ErrorHandler:
If Not conn is Nothing Then
If conn.State = 1 Then conn.RollbackTrans
End If
‘ 致命的なエラーはイベントビューアまたはローカルフォールバックログへ記録する設計に昇華させること
MsgBox “監査ログの書き込みに失敗しました: ” & Err.Description, vbCritical, “Governance System Error”

CleanUp:
‘ オブジェクトの明示的解放(メモリリークの完全防止)
On Error Resume Next
If Not cmd Is Nothing Then Set cmd = Nothing
If Not conn Is Nothing Then
If conn.State = 1 Then conn.Close
Set conn = Nothing
End If
End Sub

C. ベースライン設定のインターセプター(標準モジュール: `modBaselineController`)

ユーザーが標準のUIからベースラインを設定するアクションをラップし、ログ記録を強制するエントリポイント。

Option Explicit

Public Sub ExecuteMonitoredBaseline()
Dim baselineNum As Integer

‘ ここでは例としてベースライン0(標準ベースライン)を設定するダイアログを模す、
‘ もしくは特定のダイアログを呼び出す。
‘ 実運用では、Projectのネイティブダイアログ表示前後に介入する。

On Error GoTo ErrorHandler

‘ MSProject標準のベースライン設定ダイアログを表示
‘ (注意: Ribbonのコマンドを直接叩くか、Dialogs(pjDialogBaselineSave)を使用)
If Dialogs(pjDialogBaselineSave).Show() Then

Dim pName As String
Dim pPath As String
Dim bInfo As String

pName = ActiveProject.Name
pPath = ActiveProject.FullName
bInfo = “Baseline Set / Cleared”

‘ 監査ログ基盤へ投擲
Call WriteBaselineAuditLog(pName, pPath, bInfo)

MsgBox “ベースラインが正常に設定され、監査ログがサーバーへ記録されました。”, vbInformation, “ガバナンス監査”
End If

Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub

3. シニアエンジニアが知るべき実装上の極限の罠

このアーキテクチャを現場に導入する際、以下の「VBAの暗部」に直面することになる。これらを事前に潰すことがプロフェッショナルの仕事である。

1. ガベージコレクションとCOMの参照カウント

VBAには高度な自動ガベージコレクションが存在しない。特に `ADODB.Connection` や `ADODB.Command` をループ内や頻繁に呼ばれるプロシージャ内で生成・破棄する場合、`Set obj = Nothing` を怠ると、わずかなメモリリークが蓄積し、長時間の稼働でMSProject本体をクラッシュさせる。
前述のコードのように、必ず `CleanUp` ラベルを用意し、エラー時であっても確実に参照を切る構造を強制的すること。

2. ネットワーク切断時の耐障害性(オフライン対策)

社内DBへの接続が一時的に途切れた場合(VPN未接続や社内LANの瞬断)、ベースラインの保存自体がブロックされては業務に支障をきたす。
真にエンタープライズなシステムであれば、DB書き込み失敗時にエラーで処理を止めるのではなく、ローカルの暗号化された一時ファイル(JSONやSQLite、あるいは難読化されたテキスト)にキューイングし、次回接続時にバッチで同期する「オフラインフォールバック機構」を実装すべきである。

3. グローバルテンプレート(Global.mpt)の汚染管理

このマクロを特定のプロジェクトファイル(`.mpp`)だけに閉じ込めるのか、あるいは全社共通の `Global.mpt` に組み込むのか。
ガバナンスを強制するという観点では `Global.mpt` への組み込みが望ましいが、バージョン管理や配布のコストが増大する。CI/CDパイプラインまたはスクリプトによる `Global.mpt` の自動アップデート機構を併せて設計することが、インフラ管理者としての必須条件となる。

結言

VBAは、しばしば「レガシーで非力な言語」と揶揄される。しかし、Windows APIの深い理解、COMのライフサイクル管理、そして堅牢なエラーハンドリングの哲学を注ぎ込むことで、VBAはエンタープライズの厳格な監査要件に耐えうる「強力なガバナンス・エンジン」へと昇華する。

コンプライアンスとトレーサビリティが重視される現代のプロジェクト管理において、本稿で示した監査ログ基盤は、組織の資産を守るための強力な盾となるはずだ。

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