Microsoft Project(以下、MS Project)を用いた大規模なエンタープライズ・プロジェクト管理において、PMO(Project Management Office)やシステム管理者が直面する最大の障壁の一つが、「プロジェクト開始時におけるガバナンスの崩壊」です。
開発、インフラ、移行、運用保守――プロジェクトの性質ごとに本来適用すべきWBS構造、カレンダー、カスタムフィールド、リソースプールは異なるはずです。しかし、現場のリーダーが手作業でテンプレート(`.mpt`)を選択し、コピーして新規作成する運用を続ける限り、不適切なカレンダー設定、ベースラインの未設定、古いバージョンのテンプレートの誤用といった「人為的ミス」を完全に排除することはできません。
本稿では、プロジェクトの属性(性質、規模、組織等)に応じて最適なテンプレートを動的に選択・適用し、初期化からベースライン設定までを完全自動化する「動的テンプレートファクトリ」の設計と実装を解説します。
MS Project VBA、COMオブジェクトモデル、そしてWindows APIを極限まで使いこなし、ゾンビプロセスを発生させずにマルチユーザー環境での運用に耐えうる堅牢なシステムを構築するための知見をここに開示します。
—
1. テンプレート動的差し替えにおける「3つの技術的罠」
MS ProjectのVBAによるプロジェクト生成は、ExcelやWordの感覚でコーディングすると高確率で深刻なトラブルを引き起こします。設計に先立ち、まずは克服すべき3つの罠を理解する必要があります。
① `Global.mpt` との競合およびダイアログの発生
MS Projectは起動時に必ずローカル、またはエンタープライズの `Global.mpt` をロードします。
自作のテンプレートからプロジェクトを新規生成する際、テンプレート内のカレンダーやビュー、カスタムフィールドが `Global.mpt` 内の既存要素と競合すると、「名前が重複しています」「置き換えますか?」といったモーダルダイアログがバックグラウンドで発生し、自動化プロセスが完全にフリーズします。
② MS Projectのゾンビプロセス化(メモリリーク)
MS ProjectのCOMオブジェクトは非常にデリケートです。非表示(`Visible = False`)でアプリケーションインスタンスを生成して処理を行う際、参照カウンタの解放漏れが1箇所でもあると、コード実行が終了しても `WINPROJ.EXE` がタスクマネージャーに残り続けます。
これが蓄積すると、サーバーやクライアントPCのメモリを圧迫し、最終的にOLE自動化エラーを引き起こします。
③ 共有ネットワーク上の `.mpt` ファイルロック
テンプレートファイルを共有ネットワーク(NASやSharePoint同期フォルダ)に配置する場合、ファイルシステム上の排他制御やネットワーク遅延を考慮しなければなりません。
ファイルが存在するかどうか(`Dir` 関数)だけでなく、「今、そのファイルが他プロセスによってロックされていないか、読み込み可能な状態か」をAPIレベルで事前に判定する必要があります。
—
2. 動的ファクトリパターンのアーキテクチャ設計
本システムでは、以下の3つのレイヤーで構成される「動的テンプレートファクトリ」を構築します。
+————————————————————-+
| 1. クライアント / 呼出元 |
| (プロジェクトコード、性質、開始日、バジェット等のパラメータを渡す) |
+————————————————————-+
│
▼
+————————————————————-+
| 2. clsProjectFactory (ファクトリクラス) |
| – Windows APIによるテンプレートファイルの健全性検証 |
| – MS Projectインスタンスのライフサイクル管理 (Try-Catch-Finally) |
| – テンプレートの動的選定・インスタンス化 |
+————————————————————-+
│
▼
+————————————————————-+
| 3. MS Project インスタンス (.mpt) |
| – カレンダー設定、ベースラインの強制適用、カスタムフィールド初期化|
+————————————————————-+
—
3. 極限のVBA実装:動的テンプレートファクトリ
以下に、実務で即座に運用可能なレベルまで堅牢性を高めたソースコードを示します。
このシステムは、標準モジュール `modFileAPI` と、ファクトリクラスモジュール `clsProjectFactory` の2つで構成されます。
3.1. 標準モジュール: `modFileAPI`
Windows API(Kernel32)を直接呼び出し、ファイルシステムレベルでテンプレートファイルの存在と「開ける状態か(排他ロックされていないか)」を高速かつ確実に検証します。
Option Explicit
If VBA7 Then
Private Declare PtrSafe Function CreateFileW Lib “kernel32” ( _
ByVal lpFileName As LongPtr, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As LongPtr, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As LongPtr) As LongPtr
Private Declare PtrSafe Function CloseHandle Lib “kernel32” ( _
ByVal hObject As LongPtr) As Long
Else
Private Declare Function CreateFileW Lib “kernel32” ( _
ByVal lpFileName As Long, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib “kernel32″ ( _
ByVal hObject As Long) As Long
End If
Private Const GENERIC_READ As Long = &H80000000
Private Const FILE_SHARE_READ As Long = &H1
Private Const OPEN_EXISTING As Long = 3
Private Const INVALID_HANDLE_VALUE As Long = -1
”’
”’
Public Function IsTemplateFileAccessible(ByVal filePath As String) As Boolean
#If VBA7 Then
Dim hFile As LongPtr
#Else
Dim hFile As Long
#End If
If Dir(filePath) = “” Then
IsTemplateFileAccessible = False
Exit Function
End If
‘ Unicodeパスに対応したCreateFileWの呼び出し
hFile = CreateFileW(StrPtr(filePath), GENERIC_READ, FILE_SHARE_READ, 0&, OPEN_EXISTING, 0&, 0&)
If hFile = INVALID_HANDLE_VALUE Then
IsTemplateFileAccessible = False
Else
IsTemplateFileAccessible = True
CloseHandle hFile
End If
End Function
3.2. クラスモジュール: `clsProjectFactory`
このクラスがファクトリの本体です。MS Projectのインスタンス生成、テンプレートの動的マッピング、ダイアログ抑制、初期パラメータ(開始日、ベースラインなど)の適用、そしてデストラクションにいたるすべてのライフサイクルを制御します。
Option Explicit
‘ テンプレートタイプを定義する列挙型
Public Enum EpTemplateType
epTypeSoftwareDevelopment = 1
epTypeInfrastructureDeployment = 2
epTypeMigrationProject = 3
End Enum
‘ クラスのプライベートメンバ
Private m_appProj As MSProject.Application
Private m_isExistingInstance As Boolean
Private Sub Class_Initialize()
m_isExistingInstance = False
End Sub
”’
”’
Public Function CreateProject( _
ByVal projectType As EpTemplateType, _
ByVal templateDir As String, _
ByVal projectTitle As String, _
ByVal startDate As Date, _
ByRef outErrorMessage As String) As MSProject.Project
On Error GoTo ErrorHandler
Dim templatePath As String
templatePath = ResolveTemplatePath(projectType, templateDir)
‘ 1. テンプレートファイルの検証
If Not modFileAPI.IsTemplateFileAccessible(templatePath) Then
outErrorMessage = “指定されたテンプレートファイルが存在しないか、アクセスできません: ” & templatePath
Exit Function
End If
‘ 2. MS Project インスタンスの確保
InitializeProjectApplication
‘ 3. ダイアログの徹底的な排除
m_appProj.DisplayAlerts = False
m_appProj.ScreenUpdating = False
‘ 4. テンプレートからの新規作成
‘ FileNewの引数:Template:=テンプレートのフルパス
Dim isSuccess As Boolean
isSuccess = m_appProj.FileNew(Template:=templatePath)
If Not isSuccess Then
Err.Raise vbObjectError + 513, “clsProjectFactory”, “テンプレートからのプロジェクト生成に失敗しました。”
End If
Dim activeProj As MSProject.Project
Set activeProj = m_appProj.ActiveProject
‘ 5. メタデータおよびパラメータのインジェクション
With activeProj
.Title = projectTitle
.ProjectStart = startDate
‘ カレンダー設定の強制同期(テンプレート内に「標準_カスタム」が定義されている前提)
On Error Resume Next
.Calendar = “標準_カスタム”
On Error GoTo ErrorHandler
‘ 開発標準としてのベースラインの強制(初期保存)
‘ ※初期状態をBaseline 0(無印)に保存する
m_appProj.BaselineSave Into:=pjBaseline
End With
Set CreateProject = activeProj
GoTo CleanExit
ErrorHandler:
outErrorMessage = “Err [” & Err.Number & “]: ” & Err.Description & ” (Source: ” & Err.Source & “)”
Set CreateProject = Nothing
CleanExit:
‘ 画面描画と警告の復旧
If Not m_appProj Is Nothing Then
m_appProj.DisplayAlerts = True
m_appProj.ScreenUpdating = True
End If
End Function
”’
”’
Private Function ResolveTemplatePath(ByVal projectType As EpTemplateType, ByVal baseDir As String) As String
Dim fileName As String
Select Case projectType
Case epTypeSoftwareDevelopment
fileName = “Template_SoftwareDev.mpt”
Case epTypeInfrastructureDeployment
fileName = “Template_InfraDep.mpt”
Case epTypeMigrationProject
fileName = “Template_Migration.mpt”
Case Else
fileName = “Template_Default.mpt”
End Select
If Right(baseDir, 1) <> “\” Then baseDir = baseDir & “\”
ResolveTemplatePath = baseDir & fileName
End Function
”’
”’
Private Sub InitializeProjectApplication()
On Error Resume Next
‘ 既に起動しているWINPROJ.EXEを取得を試みる
Set m_appProj = GetObject(, “MSProject.Application”)
On Error GoTo ErrorHandler
If m_appProj Is Nothing Then
‘ 新規インスタンスの生成
Set m_appProj = New MSProject.Application
m_appProj.Visible = False ‘ バックグラウンドで実行
m_isExistingInstance = False
Else
m_isExistingInstance = True
End If
Exit Sub
ErrorHandler:
Err.Raise Err.Number, “InitializeProjectApplication”, Err.Description
End Sub
”’
”’
Private Sub Class_Terminate()
On Error Resume Next
If Not m_appProj Is Nothing Then
‘ 新規生成したインスタンスであれば終了させる
If Not m_isExistingInstance Then
m_appProj.Quit pjDoNotSaveChanges
End If
Set m_appProj = Nothing
End If
On Error GoTo 0
End Sub
3.3. クライアントコード(呼び出し例)
以下は、上記ファクトリクラスを利用して、実際にテンプレートを動的に指定し、プロジェクトを自動生成して保存する標準モジュールの実行コードです。
Public Sub Execute_DynamicProjectCreation()
Dim factory As clsProjectFactory
Set factory = New clsProjectFactory
Dim newProj As MSProject.Project
Dim errMessage As String
Dim templateDir As String
Dim savePath As String
‘ 環境に合わせたパスの設定
templateDir = “C:\Enterprise\Templates”
savePath = “C:\Enterprise\ActiveProjects\PRJ_2024_001_CoreSystem.mpp”
‘ ファクトリ経由で「ソフトウェア開発用テンプレート」をベースにしたプロジェクトを生成
Set newProj = factory.CreateProject( _
projectType:=epTypeSoftwareDevelopment, _
templateDir:=templateDir, _
projectTitle:=”基幹システム更改プロジェクト(Phase 1)”, _
startDate:=DateValue(“2024-11-01”), _
outErrorMessage:=errMessage)
If newProj Is Nothing Then
MsgBox “プロジェクト生成に失敗しました。” & vbCrLf & errMessage, vbCritical, “エラー”
Exit Sub
End If
‘ 生成されたプロジェクトをファイルシステムへ保存
Dim app As MSProject.Application
Set app = newProj.Application
‘ 同名ファイルが存在する場合は事前に削除(FileSaveAsの警告防止)
If Dir(savePath) <> “” Then Kill savePath
‘ 保存処理
app.FileSaveAs Name:=savePath
MsgBox “新規プロジェクトが正常に作成され、保存されました。” & vbCrLf & “パス: ” & savePath, vbInformation, “完了”
‘ オブジェクトの明示的解放(Terminateイベントの確実なトリガー)
Set newProj = Nothing
Set factory = Nothing
End Sub
—
4. ディープダイブ:カレンダー同期とベースライン自動保存の極限
テンプレートを動的に適用した直後、エンタープライズ運用を円滑に回すために施すべき「2つのアプローチ」があります。これらはVBA自動化エンジニアが最も頭を悩ませる領域です。
1. ベースラインの自動設定
一般的に、プロジェクトは開始された後にベースライン(計画の確定値)を保存します。しかし、「初期テンプレート適用時点のWBSと期間を、マスタ(Baseline 0)として自動保存しておく」ことで、その後の進捗遅れや計画変更(スコープクリープ)を初期段階から追跡可能になります。
コード内で実行している `m_appProj.BaselineSave Into:=pjBaseline` は、テンプレートの初期タスク群に対して、開始日・終了日・コストの基準値を強制的に適用します。これにより、現場リーダーが「ベースラインを保存し忘れて、進捗率だけを入力してしまった」という致命的なミスを防ぎます。
2. カレンダーとリソースの自動同期
テンプレートに埋め込まれたカレンダーが、全社共通の「組織カレンダー(祝日情報含む)」と乖離している場合、プロジェクトのスケジュール計算が狂います。
テンプレート内のローカルカレンダーを強制更新する場合、VBAでは `OrganizerMoveItem` メソッドを使用して、常にマスタである `Global.mpt` から最新のカレンダーをコピー(上書き)する処理を挿入します。
‘ Global.mpt から指定されたカスタムカレンダーを新規プロジェクトに強制コピーするコード例
On Error Resume Next
m_appProj.OrganizerMoveItem _
Type:=pjCalendars, _
FileName:=”Global.mpt”, _
ToFileName:=activeProj.Name, _
Name:=”標準_2024年度祝祭日対応”
On Error GoTo 0
この処理をファクトリメソッド内に組み込むことで、「常に最新の会社カレンダーが適用された状態で、プロジェクトが開始される」という、極めて統制の取れたポートフォリオ管理が実現します。
—
5. レガシーシステムとモダンシステムを繋ぐアーキテクトの視点
本稿で解説した「動的テンプレートファクトリ」は、ただの省力化ツールではありません。
これは、「属人的なプロジェクト管理」から「エンタープライズ・ポートフォリオ・マネジメント(EPM)」への移行を実現するための、基盤テクノロジーです。
VBAはレガシーな技術と見なされがちですが、MS Projectというデスクトップ製品が持つ強力なスケジューリングエンジンを、外部システム(ERP、Redmine、Jira、Power Platformなど)と連携させる「グルー(接着剤)コード」として、これ以上の適任はありません。
このファクトリ設計をベースに、Web API経由でプロジェクト起票リクエストを受け取り、バックグラウンドで `.mpp` を自動生成してSharePointにパブリッシュする仕組みを構築すれば、現場の負担をゼロにしつつ、全社レベルのガバナンスを担保することが可能になります。
オブジェクトのライフサイクル、Windows APIによるファイル制御、そしてMS Project独自のオブジェクトモデルの癖。これらを完全に掌握したコードだけが、止まらないエンタープライズシステムを支えるのです。
