【Project VBAを掌握する極限の知見】ファイルプロパティの強制調停:メタデータ自動同期アーキテクチャ
レガシーとモダンが混在する企業インフラストラクチャにおいて、ドキュメントのメタデータ管理は常にシステム管理者の頭痛の種である。特にMicrosoft Project(MPT/MPD/MPP)における「作成者」や「会社名」といったドキュメントプロパティは、属人化された手動入力に依存しているがために、監査証跡の欠落や、外部提出時のガバナンス違反を引き起こす温床となっている。
本稿では、Project VBAのオブジェクトライフサイクルを極限まで理解した上で、ファイル保存イベントをフックし、組織の標準フォーマットへとメタデータを強制的に同期・調停する実戦的アーキテクチャを解説する。
—
1. Project VBAにおけるドキュメントプロパティの構造的罠
一般的に、Office VBAにおけるプロパティ操作といえば `BuiltinDocumentProperties` コレクションを思い浮かべるだろう。しかし、Microsoft Projectのアーキテクチャは、ExcelやWordといった他のOfficeスイートとは一線を画す。
Projectの内部データベース構造において、メタデータは単なる文字列の保持領域ではない。ファイル保存時のI/Oバッファ、セキュリティコンテキスト、そしてOLE2コンパウンドドキュメントのストリーム構造に深く結びついている。
ここで、安易なプロパティ書込を行うと以下の問題が生じる:
- ダーティフラグの誤認: 保存処理中にプロパティを書き換えることで、無限の保存ループや予期せぬ「保存しますか?」ダイアログの発生。
- メモリリーク: ProjectのCOMオブジェクトモデルは厳密な参照管理を要求するため、不適切な変数スコープはメモリ断片化を招く。
これらを完全に制御するためには、アプリケーションレベルのイベントハンドラと、COMの解放作法を徹底したコード設計が不可欠である。
—
2. 実装:イベントドリブン・メタデータ調停エンジン
以下のコードは、プロジェクトの保存時(`ProjectBeforeSave`)に割り込み、社内規定のフォーマットへ「作成者」と「会社名」を強制上書きする実装である。
このコードは、標準モジュールではなく、必ず `ThisProject` クラスモジュール または専用のイベントハンドラクラスに実装する必要がある。
‘ =================================================================ライセンス・ヘッダ
‘ Project Architecture Governance Module: 0x01_MetadataEnforcer
‘ Author: Chief Architect
‘ Description: Projectファイルの保存トリガーを監視し、組織標準のメタデータを強制適用する
‘ =================================================================
Option Explicit
‘ アプリケーションイベントをフックするための宣言
Private WithEvents AppEvents As Application
‘ クラス初期化時にイベントをバインド
Private Sub Class_Initialize()
Set AppEvents = Application
End Sub
‘ 保存前イベントのインターセプト
Private Sub AppEvents_ProjectBeforeSave(ByVal pj As Project, ByVal SaveAsUI As Boolean, Cancel As Boolean)
On Error GoTo ErrorHandler
‘ デバッグおよびパフォーマンス最適化のための画面描画停止
Application.ScreenUpdating = False
‘ メタデータの強制上書き実行
Call EnforceCorporateMetadata(pj)
Exit Sub
ErrorHandler:
‘ 致命的なエラーを捕捉し、システムの強制終了を防ぎつつログを出力
MsgBox “Metadata Enforcement Error: ” & Err.Description, vbCritical, “Architecture Governance”
‘ 必要に応じてCancel = Trueを設定して保存を中断する設計も可能
‘ Cancel = True
Application.ScreenUpdating = True
End Sub
‘ メタデータ調停のコアロジック
Private Sub EnforceCorporateMetadata(ByVal targetProject As Project)
Dim builtInProps As Office.DocumentProperties
Dim authorName As String
Dim companyName As String
‘ ガバナンス要件に基づく標準値の取得(Active Directoryや環境変数からの取得に拡張可能)
authorName = GetSystemAuthorIdentity()
companyName = “Enterprise PMO Governance Office”
‘ Projectの BuiltinDocumentProperties へのアクセス
‘ 注: Microsoft Projectの型ライブラリ依存関係を排除するため、動的バインディングまたはOffice Object Libraryを参照
On Error Resume Next
Set builtInProps = targetProject.BuiltInDocumentProperties
On Error GoTo 0
If builtInProps Is Nothing Then
Err.Raise 50001, “EnforceCorporateMetadata”, “BuiltInDocumentProperties コレクションの取得に失敗しました。”
End If
‘ プロパティの強制書き換え(存在しない場合はエラーになるため個別にトラップ)
SetPropertySafe builtInProps, “Author”, authorName
SetPropertySafe builtInProps, “Company”, companyName
SetPropertySafe builtInProps, “Last Author”, authorName
‘ オブジェクトの明示的解放(メモリ最適化の極意)
Set builtInProps = Nothing
End Sub
‘ 安全なプロパティ設定ラッパー関数
Private Sub SetPropertySafe(ByRef props As Office.DocumentProperties, ByVal propName As String, ByVal propValue As String)
Dim prop As Office.DocumentProperty
On Error Resume Next
Set prop = props(propName)
If Err.Number <> 0 Then
‘ プロパティが存在しない場合は新規追加
Err.Clear
props.Add name:=propName, LinkToProperty:=False, Type:=msoPropertyTypeString, Value:=propValue
Else
‘ 存在する場合は値を更新
prop.Value = propValue
End If
On Error GoTo 0
Set prop = Nothing
End Sub
‘ 実行主体(作成者)のアイデンティティを解決するスタブ(API連携への拡張ポイント)
Private Function GetSystemAuthorIdentity() As String
‘ 実際のエンタープライズ環境では、ここでWin32 API (GetUserNameEx等) を呼び出し、
‘ 正確なWindowsログインIDやCN(Common Name)を取得する。
GetSystemAuthorIdentity = Environ$(“USERNAME”) & ” [Governance-Managed]”
End Function
—
3. チーフアーキテクトが解説するコードの急所
① `WithEvents` によるライフサイクルの掌握
グローバルなイベントプロシージャに頼る古いVBAの記述方法は、メモリリークや意図しないスコープ汚染の温床となる。`Class_Initialize` でインスタンス化された `AppEvents` は、プロジェクトのライフサイクルと完全に同期し、不要になった時点でガベージコレクトされる設計にしている。
② 明示的なオブジェクトの解放 (`Set … = Nothing`)
VBAのランタイムは参照カウント方式を採用しているが、COMオブジェクト(特にOfficeのDocumentPropertiesやDocumentProperty)をローカル変数に保持したまま放置すると、Excel/Projectプロセス内にCOM RCW (Runtime Callable Wrapper) の残骸が残り、ファイルロックやメモリ肥大化を引き起こす。
本コードでは、処理の直後に `Set builtInProps = Nothing` および `Set prop = Nothing` を実行し、即座にメモリポインタを解放している。
③ 例外のハンドリングとフェイルセーフ
保存イベント内でのエラーは、最悪の場合「プロジェクトファイルが永久に保存できなくなる」という致命的なインシデントに繋がる。そのため、`On Error Resume Next` を極小のスコープに限定して使用し、万が一メタデータの書き込みに失敗した場合でも、保存処理自体を止めるか、ログを残してスルーするかの制御権を担保している。
—
4. エンタープライズ展開への展望:レガシーの先へ
このVBAマクロ単体でもローカルガバナンスとしての効果は絶大であるが、真のシニアエンジニアであれば、これを起点としたシステム間連携を視野に入れるべきだ。
- Active Directory / LDAP連携: `GetSystemAuthorIdentity()` の部分を拡張し、ドメインコントローラーから正確な部署名とフルネームを動的に取得する。
- CI/CDパイプラインとの統合: サーバーサイドで動くVB.NET / PowerShellスクリプト(COMオートメーション経由でのProject操作)から同様のロジックを流し込み、Gitリポジトリに格納される前の段階でメタデータを完全にクレンジングする。
手動による運用ルールは必ず形骸化する。コードによって「ルールを強制する仕組み」を構築することこそが、真にスケーラブルなプロジェクト管理基盤の第一歩である。
