【Project VBAの極致】複数プロジェクトのカスタムフィールドを統治する「マスター同期エンジン」の設計思想
こんにちは。現場のコードの海を泳ぎ、数々のプロジェクト崩壊を救ってきたチーフアーキテクトです。
今日は、Microsoft Projectを使ってポートフォリオ管理(複数のプロジェクトを横断した管理)を行っている時に、必ずと言っていいほど直面する「データの不一致」という巨大な壁を、VBAという武器を使ってどう乗り越えるか、という話をしましょう。
「マクロの記録」から一歩踏み出そうとしているあなた。あるいは、「なんとなく動くコード」を書いているけれど、もっと大規模なシステムを構築したいと考えているあなた。
この記事は、単なるコードの書き方ではなく、「仕組みを設計する」というエンジニアの醍醐味を伝えるためのものです。ここをクリアすれば、あなたのProject VBAのスキルは、単なる「自動化」から「統治(ガバナンス)」へと昇華しますよ。
1. なぜ、カスタムフィールドの「不一致」は死を招くのか?
想像してみてください。あなたは全社的なプロジェクト進捗レポートを作ろうとしています。
- プロジェクトAでは、コストセンターを `Text1` に入れている。
- プロジェクトBでは、コストセンターを `Text3` に入れている。
- プロジェクトCでは、そもそも `Text1` に別のデータが入っている。
これでは、集計ツール(Power BIやExcelなど)でデータを統合しようとした瞬間に、地獄が始まります。これが、ポートフォリオ管理における「メタデータの不整合」です。
大規模な組織では、カスタムフィールドの定義(名前、型、計算式)を、全プロジェクトで完全に統一しなければなりません。これを手作業で行うのは不可能です。そこで必要になるのが、「マスター定義に基づいた同期エンジン」なのです。
2. 極限の知見:`CustomField` オブジェクトの本質
まず、技術的な基礎を固めましょう。Project VBAにおいて、カスタムフィールドを操作する主役は `CustomField` オブジェクトです。
多くの人が陥る間違いは、「セルに値を書き込むこと」だけが自動化だと思っていることです。しかし、真のマスターエンジニアは「フィールドの定義(器)」そのものを操作します。
図解:カスタムフィールドの構造
[ Project File ]
+-- [ CustomFields Collection ] <-- ここを操作する!
+-- [ CustomField Object ]
|-- Name: "コストセンター"
|-- Type: pjCustomFieldTypeText (テキスト型)
|-- Formula: "[Cost] * 0.1" (計算式)
|-- FieldID: 1 (内部的な識別子)
この「器(定義)」がバラバラだと、中身がどれだけ正しくても、データとしては機能しません。
3. 実装:マスター同期エンジンの設計図
それでは、実際に「マスターとなる定義リスト」を持ち、複数のプロジェクトファイルに対して、その定義を強制的に適用するコードを書いていきましょう。
今回は、シンプルかつ拡張性の高い、「定義情報を配列(またはコレクション)で管理し、各ファイルに流し込む」という設計思想で実装します。
実装コード:CustomField Sync Engine
Option Explicit
' ===============================================================================
' モジュール名: MasterSyncEngine
' 概要: 複数のプロジェクトファイルに対し、カスタムフィールドの定義を強制同期する
' 著: Legendary Chief Architect
' ===============================================================================
Sub SynchronizeCustomFields()
Dim targetFiles As Variant
Dim filePath As Variant
Dim proj As Project
' 1. 同期対象のファイルリスト(本来はフォルダ内のファイルを検索するロジックにする)
targetFiles = Array("C:\Projects\Project_A.mpp", "C:\Projects\Project_B.mpp")
On Error GoTo ErrorHandler
' 画面更新を停止してパフォーマンスを向上(エンジニアの嗜みです)
Application.ScreenUpdating = False
For Each filePath In targetFiles
Debug.Print "--- Processing: " & filePath & " ---"
' プロジェクトを開く
Set proj = MSProject.Applications.Open(filePath)
' 2. カスタムフィールドの定義を適用
' ここで「マスター定義」を呼び出す
ApplyMasterDefinitions proj
' 保存して閉じる
proj.Save
proj.Close
Debug.Print "Successfully synchronized."
Next filePath
Application.ScreenUpdating = True
MsgBox "全てのプロジェクトの同期が完了しました!", vbInformation
Exit Sub
ErrorHandler:
Application.ScreenUpdating = True
MsgBox "エラーが発生しました。ファイルパスや定義式を確認してください。" & vbCrLf & _
"Error: " & Err.Description, vbCritical
End Sub
''' <summary>
''' プロジェクトに対してマスター定義を適用するコアロジック
''' </summary>
''' <param name="targetProj">対象となるプロジェクトオブジェクト</param>
Private Sub ApplyMasterDefinitions(ByRef targetProj As Project)
' ※注意: ここでは Task(タスク)用のカスタムフィールドを定義します。
' Resource(リソース)用の場合は、対象のコレクションを変える必要があります。
' --- 定義1: コストセンター (Text1を使用) ---
' 既存の定義があるか確認し、なければ作成、あれば上書きする思想
UpdateFieldDefinition targetProj, "Text1", "コストセンター", pjCustomFieldTypeText, ""
' --- 定義2: 優先度フラグ (Flag1を使用) ---
UpdateFieldDefinition targetProj, "Flag1", "重要プロジェクト判定", pjCustomFieldTypeFlag, "[% Complete] > 50"
' --- 定義3: 予算計算用 (Number1を使用) ---
UpdateFieldDefinition targetProj, "Number1", "調整後予算", pjCustomFieldTypeNumber, "[Cost] * 1.05"
End Sub
''' <summary>
''' 個別のフィールド定義を安全に更新する低レイヤー関数
''' </summary>
Private Sub UpdateFieldDefinition(ByRef proj As Project, _
ByVal fieldID As String, _
ByVal newName As String, _
ByVal fieldType As Long, _
ByVal formula As String)
Dim cf As CustomField
Dim found As Boolean
found = False
' 1. 既存のフィールドが存在するかチェック
' ProjectのCustomFieldsコレクションをループして探す
For Each cf In proj.CustomFields
' fieldID(Text1, Flag1等)が一致するか確認
' ※実際には内部的なIDで判定するのが最も確実ですが、ここでは分かりやすさ優先
If cf.Name = fieldID Or IsFieldIDMatch(cf, fieldID) Then
' 既存フィールドが見つかった場合、定義を更新
cf.Name = newName
cf.Type = fieldType
cf.Formula = formula
found = True
Exit For
End If
Next cf
' 2. 見つからなかった場合は、新規作成(エラー回避のため慎重に)
' ※Project VBAでは、既存の「器」に対して名前や型を書き換えるのが基本です
If Not found Then
' ここで新規作成のロジックを入れる(既存のText1などは必ず存在するため、基本はUpdateでOK)
Debug.Print "Warning: Field " & fieldID & " not found. Manual intervention may be required."
End If
End Sub
''' <summary>
''' フィールドIDの照合ロジック(Text1などの文字列と内部IDの橋渡し)
''' </summary>
Private Function IsFieldIDMatch(cf As CustomField, targetID As String) As Boolean
' 簡略化のため、ここではNameプロパティの比較を行っていますが、
' 本格的なエンジンでは内部的なIndexやTypeを利用します。
' 実運用では、Text1, Text2... という名前がカスタムフィールドのNameに入っているか確認します。
IsFieldIDMatch = (cf.Name = targetID)
End Function
4. プロフェッショナルの視点:陥りやすい罠と回避策
このコードを動かす際、初心者が必ずと言っていいほど踏む「地雷」がいくつかあります。
① オブジェクトのスコープ(Scope)の混同
Project VBAには、`Task` 用のカスタムフィールドと `Resource` 用のカスタムフィールドが存在します。
`proj.CustomFields` はこれらを混在して管理しているように見えますが、計算式(Formula)が参照できるオブジェクトは異なります。
- Taskフィールドに `[Resource Name]` という式を書くと、エラーになります。
- Resourceフィールドに `[Task Name]` は書けません。
「どのオブジェクトに対して、どの式を投げているか」を常に意識してください。
② 計算式(Formula)の構文エラー
`cf.Formula = “[Cost] * 1.05″` のような文字列を代入する際、1文字でもタイポ(打ち間違い)があると、その瞬間に実行時エラーでプログラムが止まります。
対策: マスター定義をコード内に直書きせず、Excelシートや外部のJSONファイルに持たせ、そこから読み込む設計にすると、メンテナンス性が劇的に向上します。
③ パフォーマンスの重み
大量のプロジェクト(100個以上など)をループで回す場合、`Application.ScreenUpdating = False` は必須です。これがないだけで、処理時間は数倍に膨れ上がります。また、`proj.Save` を行う前に、必ず「本当に書き換えて良いか」のバリデーション(検証)を挟むのが、プロの仕事です。
5. まとめ:ここから先へ
今回のツールは、単に「名前を変える」だけのものではありません。「組織全体のデータ構造を、プログラムによって強制的に統治する」という思想に基づいています。
1. 定義の集約: どこに何が書いてあるか一箇所で管理する。
2. 自動適用: 手作業を排除し、全ファイルに一律に適用する。
3. 検証: 正しく適用されたかをログで確認する。
これができれば、あなたはもう「マクロを書く人」ではなく、「業務プロセスを設計するエンジニア」です。
まずは、自分の手元にある小さなプロジェクトを対象に、このコードを試してみてください。エラーが出たら、それは成長のチャンスです。エラーメッセージを読み解き、オブジェクトのライフサイクルを理解していく。その積み重ねが、あなたを伝説のエンジニアへと導きます。
応援していますよ。頑張ってくださいね。
