Project VBAを掌握する極限の知見:マルチプロジェクト環境におけるリソースカレンダー同期の自動化
エンタープライズ領域におけるMicrosoft Project(以下、MS Project)の運用において、最大のボトルネックは何か?
それは「リソース管理のサイロ化」である。
各プロジェクトマネージャー(PM)が勝手に独自の稼働日や例外休日を設定した結果、全社横断のポートフォリオ管理画面を開いた途端にリソースのオーバーアロケーション(過負荷)が乱立し、どのデータが真実(Single Source of Truth)なのか分からなくなる――。この地獄のような光景を、あなたも一度は目撃したことがあるはずだ。
組織全体の稼働基準を統一するため、マスターとなるカレンダーを他のプロジェクトファイルへ一括同期する。今回は、この泥臭くも極めて重要な課題に対して、Project VBAのライフサイクルとオブジェクトモデルの深部を突いた「堅牢かつ洗練されたプロダクションコード」を伝授する。
—
なぜ「単純なコピペ」や「手動設定」では破綻するのか?
多くのVBA初心者が陥る罠は、GUIの操作をそのままコードに翻訳しようとすることだ。「リソース画面を開き、カレンダーをエクスポートし……」といったアプローチは、Projectの非同期的な画面描画サイクルや、グローバル/ローカル・ベースカレンダーの概念を無視しているため、必ず実行時エラーを引き起こす。
1. ベースカレンダーとリソース個別カレンダーの混同
MS Projectのカレンダーには、プロジェクト全体の土台となる「ベースカレンダー(Base Calendar)」と、各リソース固有の例外(有給休暇など)を持つ「リソースカレンダー」が存在する。同期すべき対象がどちらなのかを明確に意識しなければ、組織の稼働日基準(標準時間や祝日)が破壊される。
2. ファイル間オブジェクト参照の危険性
別ファイルの `Project` オブジェクトや `Calendar` オブジェクトを安易に直接操作すると、ファイル間のロック競合やメモリリークを引き起こす。
正解は、「マスターファイルからベースカレンダーの定義(例外日や稼働時間)を抽出し、ターゲットファイルの同名ベースカレンダーに対して差分適用・または上書きを行う」というトランザクション的な設計である。
—
堅牢な同期ツールのアーキテクチャ設計
今回構築する自動化ツールの要件定義は以下の通りだ。
1. マスターファイルの指定: 組織の基準となるカレンダー(例:「全社標準カレンダー」)を持つプロジェクトファイルを特定する。
2. ターゲットの自動走査: 指定したフォルダ内、あるいは現在開いている複数のサブプロジェクトに対して一括処理を行う。
3. 例外処理とアトミック性: ターゲットファイルに同名のベースカレンダーが存在しない場合は新規作成し、存在する場合は既存の例外日(Exceptions)を一度クリアした上で最新のマスターデータを流し込む。
4. 画面描画の抑制 (`ScreenUpdating`): 処理速度の最大化とチラつき防止。
—
プロダクションコード:マスター・カレンダー同期エンジン
以下のコードは、実務の現場でそのままコピー&ペーストして利用できる、エラーハンドリングを網羅したVBAモジュールである。
Option Explicit
‘ ==============================================================================
‘ 処理名: 組織標準ベースカレンダー一括同期エンジン
‘ 概要 : マスタープロジェクトから指定したベースカレンダーの定義(例外日・稼働時間)
‘ を抽出し、ターゲットとなる複数プロジェクトへ安全に同期する。
‘ ==============================================================================
Sub SyncEnterpriseBaseCalendar()
‘ — 定数定義 —
Const MASTER_FILE_PATH As String = “C:\MSProject\Master\Master_Schedule.mpp”
Const TARGET_FOLDER_PATH As String = “C:\MSProject\SubProjects\”
Const TARGET_CALENDAR_NAME As String = “標準稼働カレンダー”
‘ — 変数宣言 —
Dim appMaster As MSProject.Application
Dim prjMaster As MSProject.Project
Dim calMaster As MSProject.Calendar
Dim fso As Object
Dim targetFolder As Object
Dim targetFile As Object
Dim prjTarget As MSProject.Project
Dim calTarget As MSProject.Calendar
Dim excMaster As MSProject.Exception
Dim excTarget As MSProject.Exception
Dim i As Long
Dim successCount As Long
Dim errorCount As Long
‘ 性能最適化と安全のための設定
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
Application.DisplayAlerts = False
successCount = 0
errorCount = 0
‘ 1. マスターファイルのサイレントオープン
Set appMaster = New MSProject.Application
appMaster.Visible = False
If Dir(MASTER_FILE_PATH) = “” Then
MsgBox “マスターファイルが見つかりません: ” & MASTER_FILE_PATH, vbCritical, “致命的エラー”
GoTo CleanUp
End If
appMaster.FileOpenEx Name:=MASTER_FILE_PATH, ReadOnly:=True
Set prjMaster = appMaster.ActiveProject
‘ マスター側カレンダーの存在確認
On Error Resume Next
Set calMaster = prjMaster.BaseCalendars(TARGET_CALENDAR_NAME)
On Error GoTo ErrorHandler
If calMaster Is Nothing Then
MsgBox “マスターファイル内に指定カレンダーが存在しません: ” & TARGET_CALENDAR_NAME, vbCritical, “エラー”
GoTo CleanUp
End If
‘ 2. ターゲットフォルダ内のMPPファイルを走査
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(TARGET_FOLDER_PATH) Then
MsgBox “ターゲットフォルダが存在しません: ” & TARGET_FOLDER_PATH, vbCritical, “エラー”
GoTo CleanUp
End If
Set targetFolder = fso.GetFolder(TARGET_FOLDER_PATH)
For Each targetFile In targetFolder.Files
If LCase(fso.GetExtensionName(targetFile.Name)) = “mpp” Then
‘ ターゲットファイルを開く
On Error Resume Next
FileOpenEx Name:=targetFile.Path, ReadOnly:=False
Set prjTarget = ActiveProject
If Err.Number <> 0 Then
errorCount = errorCount + 1
Err.Clear
GoTo NextFile
End If
On Error GoTo ErrorHandler
‘ 3. ターゲット側のベースカレンダーを取得または作成
On Error Resume Next
Set calTarget = prjTarget.BaseCalendars(TARGET_CALENDAR_NAME)
On Error GoTo ErrorHandler
If calTarget Is Nothing Then
‘ 存在しない場合は新規作成(標準カレンダーをベースにする)
Set calTarget = prjTarget.BaseCalendars.Add(TARGET_CALENDAR_NAME)
End If
‘ 4. 例外日(Exceptions)の同期プロセス
‘ 整合性を保つため、一度ターゲット側の例外日を全削除してマスターから再構築する
For Each excTarget In calTarget.Exceptions
excTarget.Delete
Next excTarget
‘ マスターの例外日をコピー
For Each excMaster In calMaster.Exceptions
‘ 稼働日・非稼働日のパターン、名称、期間を完全に同期
calTarget.Exceptions.Add _
Type:=excMaster.Type, _
Name:=excMaster.Name, _
Start:=excMaster.Start, _
Finish:=excMaster.Finish, _
Shift1:=excMaster.Shift1, _
Shift2:=excMaster.Shift2, _
Shift3:=excMaster.Shift3
Next excMaster
‘ 変更を保存して閉じる
prjTarget.Save
FileClose pjDoNotSave
successCount = successCount + 1
End If
NextFile:
Next targetFile
‘ 終了処理
appMaster.FileClose pjDoNotSave
appMaster.Quit
Application.ScreenUpdating = True
Application.DisplayAlerts = True
MsgBox “カレンダー同期が完了しました。” & vbCrLf & _
“成功: ” & successCount & ” ファイル” & vbCrLf & _
“失敗: ” & errorCount & ” ファイル”, vbInformation, “処理完了”
Exit Sub
ErrorHandler:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
If Not appMaster Is Nothing Then
appMaster.FileClose pjDoNotSave
appMaster.Quit
End If
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error No: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, vbCritical, “システムエラー”
End Sub
—
開発現場で知るべき「プロの心得」と落とし穴
このコードを実運用に乗せるにあたり、チーフアーキテクトとしていくつか重要な実務上の注意点を添えておく。
1. 排他制御(ファイルロック)の壁
エンタープライズ環境では、他のPMがサーバー上で該当の `.mpp` ファイルを開いている可能性が常にある。`FileOpenEx` が失敗した場合に、スクリプト全体が停止するのではなく、該当ファイルだけをスキップしてログ(またはカウント)に記録する「耐障害性(Fault Tolerance)」が不可欠である。上記のコードでは `On Error Resume Next` と `Err.Number` の監視によってこれを担保している。
2. 稼働時間(Working Times)の粒度
今回のコードは「例外日(祝日や特別休日)」の同期に主眼を置いているが、もし「週ごとの曜日別稼働時間(例:金曜日は午前中のみ稼働など)」まで完全に同期させたい場合は、`calMaster.WeekDays` プロパティを走査し、各曜日の `Working` プロパティや `Shift` 情報をターゲットに転記するロジックを追加する必要がある。
3. グローバルテンプレート(Global.mpt)の活用検討
もしプロジェクトファイル単位ではなく、組織全体で完全にカレンダーを強制したい場合は、各 `.mpp` ファイルへのバラマキ方式ではなく、組織の `Global.mpt`(エンタープライズ環境であれば Project Server / Project Online の Enterprise Global)にベースカレンダーを定義するのがアーキテクチャとしては最も美しい。しかし、オンプレミスのファイルベース運用や、スタンドアロンの `.mpp` が乱立する現場においては、今回紹介したVBAスクリプトによる一括同期が最も現実的かつ強力なソリューションとなる。
—
終わりに
VBAは、単なる「手作業の代行ツール」ではない。
組織のプロセス・ガバナンスをコードによって強制し、ヒューマンエラーの余地を徹底的に排除するための「インフラストラクチャ」である。
今回提供したコードをベースに、自社のディレクトリ構造や運用ルールに合わせたカスタムを加え、あなたのプロジェクトマネジメント環境を次のステージへと引き上げてほしい。
