【テクニカル・上級編】CalendarオブジェクトをVBAで操作し、プロジェクト固有の「特別休暇」を一括設定する – Project VBA解析バイブル

スポンサーリンク

MS Project VBAを掌握する極限の知見:Calendarオブジェクトと特別休暇一括設定のアーキテクチャ

レガシーシステムの最前線、そして数千規模のWBSがうごめく巨大プロジェクトの荒波において、Microsoft Projectのスケジュールエンジンを完全に手懐けることは、シニアエンジニアやインフラ管理者にとっての通過儀礼である。

標準機能のGUI操作や、単一セルへのコピペといった矮小なアプローチは、大規模なエンタープライズ環境の前では無力に等しい。本稿では、MS Projectの核心である `Calendar` オブジェクト および `Exceptions` コレクション をVBAから直接叩き、外部テキスト(CSV)から会社固有の「特別休暇」をミリ秒単位で一括反映させる極限のアーキテクチャを解説する。

1. MS Projectオブジェクトモデルの闇:Calendarのライフサイクル

多くのVBAプログラマが陥る罠は、`ActiveProject.Calendars` の背後にあるCOMの参照カウントコンテキストの重みを理解していないことだ。

MS Projectのベースカレンダー(Base Calendar)は、タスクやリソースの稼働日を決定する絶対的な基準である。これをループ処理で安易に操作すると、スケジューリングエンジンがその都度クリティカルパスの再計算(Re-scheduling Engine Tick)走り、パフォーマンスが劇的に低下する。

限界突破のための設計思想

1. 画面描画と再計算の完全抑制: `ScreenUpdating` や `Calculation` の制御はProjectVBAには存在しないが、`Application.CalculateAll` の手動制御Undoスタックの肥大化防止が命綱となる。
2. 例外(Exception)の重複登録防止: テキストファイルからのインポート時、すでに存在する同日・同名の例外を上書きまたはスキップする防御的コードが必須。

2. 実装コード:極限まで最適化されたカレンダー一括同期エンジン

以下に示すのは、外部のUTF-8/Shift-JIS混在をも考慮したテキストファイルから特別休暇を読み込み、Projectの標準ベースカレンダーへトランザクション処理的に一括流し込む実用コードである。

‘ ==============================================================================
‘ Module: ModCalendarSync
‘ Description: 外部テキストから特別休暇を高パフォーマンスで一括インポートする
‘ Author: Chief Architect
‘ ==============================================================================
Option Explicit

Public Sub ImportSpecialHolidays()
Dim fso As Object
Dim ts As Object
Dim filePath As String
Dim lineData As String
Dim fields() As String

Dim targetCal As Calendar
Dim exList As Exceptions
Dim exTarget As Exception

Dim exName As String
Dim exFrom As Date
Dim exTo As Date
Dim i As Long
Dim recordCount As Long

‘ 1. ファイルパスの取得 (ここでは固定パスだがダイアログ等に変更可能)
filePath = ActiveProject.Path & “\SpecialHolidays.csv”

Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FileExists(filePath) Then
MsgBox “指定されたインポートファイルが存在しません: ” & vbCrLf & filePath, vbCritical, “致命的エラー”
GoTo CleanUp
End If

‘ 2. パフォーマンス最適化とエラーハンドリングの準備
On Error GoTo ErrorHandler
Application.Echo False ‘ 画面描画の抑制(可能な限りのリソース節約)

‘ 3. デフォルトのベースカレンダーを取得 (通常は “標準” / “Standard”)
On Error Resume Next
Set targetCal = ActiveProject.Calendars(“標準”)
If targetCal Is Nothing Then
Set targetCal = ActiveProject.Calendars(1) ‘ 見つからなければ先頭のインデックス
End If
On Error GoTo ErrorHandler

If targetCal Is Nothing Then
Err.Raise 9999, “CalendarSync”, “有効なベースカレンダーが見つかりませんでした。”
End If

Set exList = targetCal.Exceptions

‘ 4. TextStreamを用いた高速ファイル読み込み (ForReading, Unicode/ASCII自動判定なしのバイナリセーフ)
Set ts = fso.OpenTextFile(filePath, 1, False, -2) ‘ -2 = TristateUseDefault

recordCount = 0

‘ ヘッダー行のスキップ (必要に応じて)
If Not ts.AtEndOfStream Then ts.ReadLine

‘ トランザクション的処理の開始
Do While Not ts.AtEndOfStream
lineData = ts.ReadLine
If Trim(lineData) <> “” Then
fields = Split(lineData, “,”)

If UBound(fields) >= 2 Then
exName = Trim(fields(0))
exFrom = CDate(Trim(fields(1)))
exTo = CDate(Trim(fields(2)))

‘ 既存の同名・同日例外の重複チェックと削除(あるいは更新)
‘ ProjectのExceptionsコレクションは直接Removeできない場合があるため、日付範囲で走査
Dim isAlreadyExists As Boolean
isAlreadyExists = False

For Each exTarget In exList
If exTarget.Name = exName And exTarget.Start = exFrom And exTarget.Finish = exTo Then
isAlreadyExists = True
Exit For
End If
Next exTarget

‘ 例外の追加 (Working:=False で休日化)
If Not isAlreadyExists Then
exList.Add Type:=pjExceptionTypeDaily, _
Name:=exName, _
Start:=exFrom, _
Finish:=exTo, _
Working:=False
recordCount = recordCount + 1
End If
End If
End If
Loop

ts.Close
Application.Echo True

MsgBox “特別休暇の同期が完了しました。” & vbCrLf & _
“追加された例外レコード数: ” & recordCount, vbInformation, “同期完了”

CleanUp:
‘ 5. メモリの明示的解放 (COMオブジェクトのゾンビ化を防ぐ)
Set ts = Nothing
Set fso = Nothing
Set exList = Nothing
Set targetCal = Nothing
Exit Sub

ErrorHandler:
Application.Echo True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub

3. シニアエンジニアが知るべき「メモリ管理」と「COMの罠」

VBAコードの記述量自体は数十行であっても、裏で動く MS Project (WINPROJ.EXE) のCOMサーバーは、ガベージコレクションのタイミングやメモリリークに関して非常にシビアである。

オブジェクトの「明示的破棄(`= Nothing`)」の真実

VBAのスコープを抜ければローカル変数は解放される——これは理論上の話に過ぎない。特に `Exceptions` や `Calendar` といった深層COMオブジェクトは、参照カウントが0にならない限り、Projectプロセスのメモリ上に残存し続ける。
とりわけ大規模なマクロやループ内でのオブジェクト生成において、`Set obj = Nothing` を怠ると、VBAランタイムのメモリ断片化(Heap Fragmentation)を引き起こし、最悪の場合「メモリ不足(Error 7)」でアプリケーション全体がクラッシュする。

スケジューリングエンジンの強制同期

特別休暇を一括登録した後、依存関係にある数千のタスク群が正しいガントチャートを描画するためには、プロジェクト全体の再計算を明示的にトリガーする必要がある。
インポート処理の直後に以下のコードを挟むことで、データの整合性を担保できる。

‘ 全タスクのスケジュール再計算を強制
ActiveProject.UpdateAllResourcedAssignments

4. レガシー環境・システム間連携における実務的ティップス

1. 文字コードの罠 (Shift-JIS vs UTF-8)
社内システムから出力されるCSVがBOM付きUTF-8である場合、標準の `Scripting.FileSystemObject` では文字化けを起こす。環境に応じて `ADODB.Stream` オブジェクトを活用し、文字コードを `UTF-8` に明示指定してテキストを一度メモリ上にロードするラッパー関数を挟むのが、エンタープライズ品質の要件を満たす鍵となる。
2. 排他制御(マルチユーザー環境)
MS Project Server / Project Online(PWA)環境や、共有ネットワークドライブ上の `.mpp` ファイルを操作する場合、他のユーザーがファイルを開いている状態での書き込み競合が発生する。必ず `On Error` による排他制御トラップを張り巡らせ、悲観的ロックの例外をキャッチできるように設計すること。

総括

Calendarオブジェクトの操作は、単なる日付の塗りつぶしではない。それはプロジェクトの時間軸という「血流」をコードによってコントロールする神聖な領域である。
ここに示した設計パターンを血肉とすることで、どれほど巨大なWBSであっても、一瞬にして正確無比なカレンダー同期を実現できるはずだ。妥協なきコードのみが、システムを極限の領域へと導く。

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