【極限のProject VBA】複数プロジェクトのカスタムフィールド定義を統制する「同期エンジン」の設計と実装
ポートフォリオ管理に携わるエンジニアが必ずぶち当たる壁がある。それは「プロジェクトファイル(.mpp)が乱立した瞬間、カスタムフィールドの定義がバラバラになる」という地獄だ。
あるファイルでは `Text1` が「進捗ステータス」で、別のファイルでは「顧客名」になっている。計算式(Formula)を修正したいとき、100個のファイルを開いて手動で書き換えるのか? そんな非効率な作業に時間を溶かすのは、プロの仕事ではない。
本記事では、複数のプロジェクトファイル間でカスタムフィールドの定義(名称、型、計算式)を完全に同期させる「カスタムフィールド同期エンジン」のアーキテクチャと実装を伝授する。
1. なぜ「単純なループ処理」では失敗するのか
多くの初心者は、「ファイルを順番に開き、フィールドを書き換えて保存する」という単純なコードを書く。しかし、プロダクションレベルでそれをやると以下の3つの問題に直面する。
① パフォーマンスの崩壊
MS Projectのファイルオープン(`FileOpen`)は、極めて重い処理だ。不適切にループ内でファイルを制御すると、メモリリークやCOMオブジェクトの解放漏れが発生し、途中でアプリケーションがクラッシュする。
② 計算式の「ローカライズ」と「構文」の罠
`CustomField.Formula` に代入する文字列は、Projectの内部形式に従う必要がある。また、言語設定によって関数名が変わるリスクがある。ここをハードコードすると、OSの言語環境が変わった瞬間にツールはゴミになる。
③ データの破壊リスク
定義を上書きした際、既存のデータが予期せず消えたり、計算結果が不可逆的に変更されたりすることがある。「冪等性(Idempotency)」——つまり、何度実行しても同じ正しい状態になり、かつ不要な書き換えを行わない設計が不可欠だ。
2. アーキテクチャ設計:定義の分離と注入
堅牢なツールを作るための正解は、「定義(Master)」と「実行(Engine)」を完全に分離することだ。
設計コンセプト
1. 定義マスター(Excel/JSON/CSV): どのフィールドに、どの名称を付け、どの計算式を割り当てるかを一元管理する。
2. 同期エンジン(VBA): マスターを読み込み、ターゲットとなる `.mpp` ファイル群に「差分がある場合のみ」定義を注入する。
3. バリデーション層: 計算式が正しい形式か、フィールド型が一致しているかを適用前にチェックする。
3. 【実装】プロダクションレベルの同期エンジン
以下に、実務でそのまま利用可能な設計思想に基づいたコードを示す。ここでは保守性を高めるため、定義を構造体(Type)で管理し、処理をモジュール化している。
Option Explicit
' --- 定義構造体: カスタムフィールドの仕様を保持 ---
Private Type FieldDefinition
FieldName As String ' 例: "Text1", "Cost1"
DisplayName As String ' 例: "プロジェクト優先度"
Formula As String ' 例: "IIf([% Complete]=100, ""完了"", ""進行中"")"
FieldType As Long ' pjCustomTaskText, pjCustomTaskCost 等
End Type
' --- メインエントリポイント ---
Public Sub SyncPortfolioCustomFields()
Dim masterList As Collection
Dim projectFiles As Variant
Dim filePath As Variant
Dim projApp As MSProject.Application
On Error GoTo ErrorHandler
' 1. 定義マスターのロード (本来は外部Excel等から読み込むべきだが、ここでは例としてハードコード)
Set masterList = LoadFieldDefinitions()
' 2. 同期対象ファイルのリストアップ (実際にはフォルダ走査などで取得)
projectFiles = Array("C:\Projects\Project_A.mpp", "C:\Projects\Project_B.mpp")
Set projApp = Application
' パフォーマンス最適化: 画面更新の停止
projApp.ScreenUpdating = False
For Each filePath In projectFiles
Debug.Print "Processing: " & filePath
' ファイルを読み取り専用で開かず、書き込み権限を持って開く
projApp.FileOpen ExName:=filePath, ReadOnly:=False
' 各フィールドの同期処理を実行
Dim def As FieldDefinition
For Each def In masterList
ApplyFieldDefinition def
Next def
' 保存して閉じる (保存しないと意味がない)
projApp.FileSave
projApp.FileClose
Next filePath
projApp.ScreenUpdating = True
MsgBox "全プロジェクトのカスタムフィールド同期が完了しました。", vbInformation
Exit Sub
ErrorHandler:
projApp.ScreenUpdating = True
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub
' --- フィールド定義の読み込みロジック ---
Private Function LoadFieldDefinitions() As Collection
Dim col As New Collection
Dim def As FieldDefinition
' 定義1: Text1をステータス管理に使用
def.FieldName = "Text1"
def.DisplayName = "プロジェクト状態"
def.Formula = "IIf([% Complete]=100, ""完了"", ""進行中"")"
def.FieldType = pjCustomTaskText
col.Add def
' 定義2: Cost1を予算乖離率に使用
def.FieldName = "Cost1"
def.DisplayName = "予算乖離額"
def.Formula = "[Baseline Cost] - [Actual Cost]"
def.FieldType = pjCustomTaskCost
col.Add def
Set LoadFieldDefinitions = col
End Function
' --- 個別フィールドの適用ロジック(ここがエンジンの心臓部) ---
Private Sub ApplyFieldDefinition(def As FieldDefinition)
Dim targetField As CustomField
' カスタムフィールドオブジェクトの取得
' ※ project.CustomFields は読み取り専用に近い挙動をするため注意
Set targetField = ActiveProject.CustomFields(def.FieldName)
' 【重要】冪等性の確保: 変更が必要な場合のみ書き換える
' これにより、不必要な計算再評価(Recalculation)を防ぎ、パフォーマンスを向上させる
Dim needsUpdate As Boolean
needsUpdate = False
If targetField.Title <> def.DisplayName Then
needsUpdate = True
End If
If targetField.Formula <> def.Formula Then
needsUpdate = True
End If
If needsUpdate Then
With targetField
.Title = def.DisplayName
.Formula = def.Formula
' 型の変更はAPI制限があるため、不一致の場合はエラーを出す設計にする
End With
Debug.Print "Updated: " & def.FieldName
End If
End Sub
4. チーフアーキテクトによる「極限の知見」:実装の急所
このコードを単にコピペして動かすだけでは、まだ「中級者」だ。真に堅牢なシステムにするために、以下の3点を深く理解せよ。
① `ScreenUpdating` の制御とCOMの解放
MS Projectは、一つのフィールドを変更するたびにプロジェクト全体の再計算(Recalculation)を走らせる傾向がある。`ScreenUpdating = False` は必須だが、それでも重い。大量のファイルを処理する場合、`Application.Calculation = pjManual`(手動計算モード)への切り替えを検討せよ。
② Formula(計算式)のエスケープ処理
VBA内で計算式を定義する場合、ダブルクォーテーションの重複(`””完了””`)に注意しろ。また、計算式に日本語のフィールド名を使わず、必ず `[Text1]` のような内部名を使用することだ。ユーザーがフィールド名を変えても、内部名が変わらなければ計算式は壊れない。
③ 型の不整合に対する防御策
`Text1` を `Cost1` に変えることはできない。もしマスター定義で型を変更した場合、既存のファイルで型不整合が起き、ランタイムエラーで停止する。実運用では、`On Error Resume Next` で逃げるのではなく、「現在の型」と「定義の型」を比較し、不一致ならログに出力してスキップするというバリデーションロジックを組み込め。
5. まとめ:自動化の先にある「ガバナンス」
技術的に同期エンジンを作ることは簡単だ。しかし、本当に重要なのは「誰がマスター定義を決定し、いつ同期をかけるか」という運用ガバナンスである。
1. 定義のバージョン管理: マスター定義(Excel等)をGitやSharePointでバージョン管理せよ。
2. 影響調査: 計算式を変更する前に、そのフィールドを参照している他のレポートやビューがないかを確認せよ。
3. バックアップ: 同期エンジンを実行する直前に、全ファイルのバックアップを自動で取る仕組みを組み込め。
このレベルまで設計して初めて、あなたは「単なるVBA書き」ではなく、「業務自動化エンジニア」と呼べるだろう。
以上だ。実装に取り掛かれ。
