【テクニカル・上級編】【上級者向け】ベースラインの多重管理:過去の全ベースラインを別ファイルへアーカイブする – Project VBA解析バイブル

スポンサーリンク

【上級者プロジェクトVBA】ベースラインの多重管理:過去の全ベースラインを別ファイルへアーカイブする極限の知見

Microsoft Project(MSP)を用いた大規模なエンタープライズ案件において、プロジェクトファイルの肥大化とパフォーマンス劣化は、シニアエンジニアにとって永遠の課題である。

特に「ベースライン(Baseline)」の多重管理がこの問題を加速させる。MSP標準機能では最大11個(Baseline~Baseline 11)までのベースラインを保持できるが、幾度もの計画変更(リプランニング)を経る現場では、これすらも枯渇する。結果として、古い実績値や計画の変遷を無視するか、場当たり的にファイルを別名保存して肥大化した`.mpp`の残骸を量産することになる。

本稿では、Project VBAのオブジェクトモデルの深層に踏み込み、過去の全ベースラインデータを抽出し、外部の別ファイルへ完全な構造を保ったままアーカイブ(退避)させることで、メインファイルを極限まで軽量化・最適化する高度なデータ管理手法を解説する。

1. Project VBAにおけるメモリ管理とオブジェクトのライフサイクル

一般的なVBAプログラマは、`Application.FileOpen`や`ActiveProject`を呼び出すだけで満足している。しかし、数万行規模のタスクを持つMSPファイルを操作する場合、COMコンポーネント(MSPのプロセス)とVBA間のマーシャリング、そしてメモリリークの制御が勝敗を分ける。

リソース解放の鉄則

VBAはガベージコレクションの挙動がブラックボックスであり、特にMSPのCOMオブジェクトは明示的に参照を解放(`Set obj = Nothing`)しないと、背後でプロセスがゾンビ化し、メモリを食潰す。
アーカイブ処理においては、「メインファイルを開く」「アーカイブファイルを作成・開く」「タスクとベースラインのプロパティを転記する」「保存して閉じる」という一連のライフサイクルを厳密にコード化しなければならない。

2. アーカイブアーキテクチャの設計思想

今回のアーキテクチャの核心は以下の通りである。

1. ベースラインの構造的制約の回避: MSPのタスクオブジェクトには、`BaselineX_Start`、`BaselineX_Finish`、`BaselineX_Cost`などのプロパティ群が存在する。これらを動的に走査する。
2. マスター・スレーブ分離: メインファイルからは古いベースライン(例: Baseline 1 〜 Baseline 5)の値をクリア(あるいは保持最小限に)し、切り出したデータを「`[Project名]_Archive_[YYYYMMDD].mpp`」という独立したサブレコードファイルへ退避させる。
3. 一意キーによる整合性担保: タスクの識別には、GUI上の「ID」ではなく、絶対に変動しない固有の「UniqueID」を使用する。

3. 実装コード:ベースライン抽出・アーカイブ・メイン軽量化エンジン

以下のコードは、開発現場でそのまま運用できる実用レベルのプロシージャである。エラーハンドリングとオブジェクトの明示的解放を徹底している。

Option Explicit

‘ =================================================================================
‘ 11個のベースラインから指定範囲を別ファイルへアーカイブし、メインを軽量化するメインエンジン
‘ =================================================================================
Sub ArchiveOldBaselinesToExternalFile()
Dim appMain As MSProject.Application
Dim prjMain As MSProject.Project
Dim prjArch As MSProject.Project
Dim tskMain As MSProject.Task
Dim tskArch As MSProject.Task

Dim mainPath As String
Dim archPath As String
Dim i As Long, j As Long

‘ 処理対象のベースライン範囲(例: Baseline 1 から Baseline 5 までをアーカイブ対象とする)
Const ARCHIVE_START_Index As Long = 1
Const ARCHIVE_END_Index As Long = 5

On Error GoTo ErrorHandler

‘ 1. 初期化とアプリケーション参照の確立
Set appMain = ActiveApplication
Set prjMain = ActiveProject

If prjMain.Path = “” Then
MsgBox “メインプロジェクトが一度も保存されていません。先に保存してください。”, vbCritical
Exit Sub
End If

mainPath = prjMain.FullName
archPath = Left(mainPath, InStrRev(mainPath, “.”) – 1) & “_Archive_” & Format(Now, “yyyymmdd_hhnnss”) & “.mpp”

appMain.ScreenUpdating = False
appMain.DisplayAlerts = False

‘ 2. アーカイブ用プロジェクトの新規作成(構造の複製)
‘ ※実運用ではテンプレートや既存アーカイブへの追記ロジックに変更可能
Set prjArch = appMain.FileNew(False)
prjArch.ProjectSummaryTask.Name = prjMain.Name & ” – Baseline Archive”

‘ 3. メインプロジェクトの全タスクを走査し、アーカイブ側へ構造とベースラインを転記
For Each tskMain In prjMain.Tasks
If Not tskMain Is Nothing Then
‘ アーカイブファイル側に同名のタスク(UniqueIDベース)を作成
Set tskArch = prjArch.Tasks.Add(tskMain.Name, tskMain.ID)

‘ 固有IDとマイルストーン状態、期間の同期
tskArch.UniqueID = tskMain.UniqueID
tskArch.Duration = tskMain.Duration
tskArch.Start = tskMain.Start
tskArch.Finish = tskMain.Finish

‘ 指定された範囲のベースラインデータを退避先へコピー
For i = ARCHIVE_START_Index To ARCHIVE_END_Index
CopyBaselineData tskMain, tskArch, i
Next i
End If
Next tskMain

‘ 4. アーカイブファイルの保存
prjArch.SaveAs archPath
prjArch.Close pjDoNotSave
Set prjArch = Nothing

‘ 5. メインプロジェクト側の古いベースラインデータをクリア(軽量化の断行)
‘ ※MSPにはベースラインをリセットするネイティブメソッドがないため、日付/コストをゼロ・NAクリアする
For Each tskMain In prjMain.Tasks
If Not tskMain Is Nothing Then
For j = ARCHIVE_START_Index To ARCHIVE_END_Index
ClearBaselineData tskMain, j
Next j
End If
Next tskMain

‘ メインファイルの保存
prjMain.Save

appMain.ScreenUpdating = True
appMain.DisplayAlerts = True

MsgBox “ベースラインのアーカイブが完了しました。” & vbCrLf & _
“保存先: ” & archPath, vbInformation, “アーキテクチャ最適化完了”
Exit Sub

ErrorHandler:
appMain.ScreenUpdating = True
appMain.DisplayAlerts = True

‘ 異常終了時のオブジェクトクリーンアップ
If Not prjArch Is Nothing Then
On Error Resume Next
prjArch.Close pjDoNotSave
Set prjArch = Nothing
End If

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

‘ =================================================================================
‘ サブプロシージャ: タスク間のベースラインプロパティの転記
‘ =================================================================================
Private Sub CopyBaselineData(ByRef srcTask As MSProject.Task, ByRef destTask As MSProject.Task, ByVal blIndex As Long)
On Error Resume Next
Select Case blIndex
Case 0:
destTask.BaselineCost = srcTask.BaselineCost
destTask.BaselineWork = srcTask.BaselineWork
destTask.BaselineStart = srcTask.BaselineStart
destTask.BaselineFinish = srcTask.BaselineFinish
Case 1:
destTask.Baseline1Cost = srcTask.Baseline1Cost
destTask.Baseline1Work = srcTask.Baseline1Work
destTask.Baseline1Start = srcTask.Baseline1Start
destTask.Baseline1Finish = srcTask.Baseline1Finish
Case 2:
destTask.Baseline2Cost = srcTask.Baseline2Cost
destTask.Baseline2Work = srcTask.Baseline2Work
destTask.Baseline2Start = srcTask.Baseline2Start
destTask.Baseline2Finish = srcTask.Baseline2Finish
Case 3:
destTask.Baseline3Cost = srcTask.Baseline3Cost
destTask.Baseline3Work = srcTask.Baseline3Work
destTask.Baseline3Start = srcTask.Baseline3Start
destTask.Baseline3Finish = srcTask.Baseline3Finish
Case 4:
destTask.Baseline4Cost = srcTask.Baseline4Cost
destTask.Baseline4Work = srcTask.Baseline4Work
destTask.Baseline4Start = srcTask.Baseline4Start
destTask.Baseline4Finish = srcTask.Baseline4Finish
Case 5:
destTask.Baseline5Cost = srcTask.Baseline5Cost
destTask.Baseline5Work = srcTask.Baseline5Work
destTask.Baseline5Start = srcTask.Baseline5Start
destTask.Baseline5Finish = srcTask.Baseline5Finish
‘ 必要に応じてBaseline 6〜11まで拡張可能
End Select
On Error GoTo 0
End Sub

‘ =================================================================================
‘ サブプロシージャ: メインプロジェクト側の古いベースラインデータの消去(軽量化)
‘ =================================================================================
Private Sub ClearBaselineData(ByRef tsk As MSProject.Task, ByVal blIndex As Long)
On Error Resume Next
‘ ※MSPの仕様上、明示的なBaselineクリアはアプリケーション側の機能(EditSetBaselineなど)を使うか、
‘ プロパティにNA(ナラティブ/ヌルに相当する値)を代入することで領域を解放する。
Select Case blIndex
Case 1
tsk.Baseline1Start = “NA”
tsk.Baseline1Finish = “NA”
tsk.Baseline1Cost = 0
tsk.Baseline1Work = 0
Case 2
tsk.Baseline2Start = “NA”
tsk.Baseline2Finish = “NA”
tsk.Baseline2Cost = 0
tsk.Baseline2Work = 0
Case 3
tsk.Baseline3Start = “NA”
tsk.Baseline3Finish = “NA”
tsk.Baseline3Cost = 0
tsk.Baseline3Work = 0
Case 4
tsk.Baseline4Start = “NA”
tsk.Baseline4Finish = “NA”
tsk.Baseline4Cost = 0
tsk.Baseline4Work = 0
Case 5
tsk.Baseline5Start = “NA”
tsk.Baseline5Finish = “NA”
tsk.Baseline5Cost = 0
tsk.Baseline5Work = 0
End Select
On Error GoTo 0
End Sub

4. シニアエンジニアが押さえるべき実運用上の罠と最適化の極意

このアーキテクチャを導入するにあたり、現場のシステム管理者が直面する「罠」と、それを突破するための知見を共有する。

① 画面描画の抑止(`ScreenUpdating`)とパフォーマンス

数千行規模のタスクループ内で `Tasks.Add` やプロパティ書込を行うと、MSPのGUI描画エンジンが毎回インタラプトを起こし、処理が数十分単位で膠着する。必ず処理の冒頭で `appMain.ScreenUpdating = False` をかけ、完了時に `True` へ戻すこと。これにより実行速度を最大20倍以上に引き上げられる。

② 外部ファイル間の「参照整合性(リレーションシップ)」の断絶

今回のコードではベースライン値とタスク名を別ファイルに退避させているが、もしメインファイル側のタスクが「先行タスク(Predecessors)」として外部アーカイブファイルのタスクとリンクしている場合、別ファイルに分離した瞬間にリンクが切断される(あるいは外部参照エラーになる)。
大原則として、タスクのロジカルリンク(依存関係)はメインに残し、あくまで「蓄積された過去のコスト・日時のスナップショット(=ベースライン)」のみを退避させること。

③ レガシー環境とメモリプレッシャー

古いOffice/Project環境(例: Project 2010〜2016等のオンプレミス環境)では、VBAからCOMオブジェクトを連続生成するとメモリリークが蓄積しやすい。もしバッチ処理として複数プロジェクトを連続アーカイブする場合は、ループの要所において `DoEvents` を挟み、OS側のメモリ解放サイクルに猶予を与える配慮が必要となる。

総括

プロジェクトファイルの肥大化は、単なるストレージの無駄遣いではなく、スケジュール計算(CPM:クリティカルパス法)のエンジンそのもののパフォーマンス低下を招く。

今回提示した「ベースラインの多重管理と外部アーカイブ化」のロジックをマスターすれば、何年にもわたる長期プロジェクトであっても、メインファイルを常に軽快な状態に保ちつつ、監査や事後検証に必要な過去の全ベースラインデータを完全な状態で担保し続けることが可能となる。

現場のインフラとVBAの限界を突き詰めるエンジニアにとって、このアーキテクチャは強力な武器となるはずだ。

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