プロローグ:ファイルという「情報の孤島」を、組織の「資産」へ昇華させる
プロジェクト管理の実務において、Microsoft Project(以下MS Project)の`.mpp`ファイルは、往々にして「情報の孤島」になりがちです。
各PMが個別にファイルを保存し、進捗を更新する。しかし、PMOや部門長が「今、全プロジェクトの最新ベースラインはどうなっているか?」「最後に誰がいつ更新したのか?」を知ろうとしたとき、散らばったファイルを一つずつ開くという、極めて非生産的な作業が発生します。
Project OnlineやProject Serverを導入すれば解決する問題かもしれません。しかし、コストや導入リードタイム、そして「そこまでの重厚なシステムは求めていない」という現場のリアリティがある。
ならば、VBAという「銀の弾丸」で、保存と同時にSQL Serverへメタデータを書き込む自動ログシステムを構築すればいい。
今回は、単なるリファレンスの切り貼りではない。私が大規模プロジェクトの自動化において徹底している、「堅牢性」「保守性」「パフォーマンス」を両立させたプロフェッショナルな実装法を伝授します。
—
1. データベース設計:ログは「証跡」である
まずは受け皿となるSQL Serverのテーブル設計です。ここで重要なのは、「いつ、誰が、どのファイルを、どのような状態で」保存したかを一意に特定できることです。
— プロジェクト保存ログテーブルの作成
CREATE TABLE ProjectSaveLogs (
LogID INT IDENTITY(1,1) PRIMARY KEY,
ProjectName NVARCHAR(255) NOT NULL,
FilePath NVARCHAR(500),
LastSavedBy NVARCHAR(100),
SaveTimestamp DATETIME DEFAULT GETDATE(),
— ベースライン情報(主要な指標のみ抽出)
BaselineDurationMinutes FLOAT,
BaselineCost MONEY,
BaselineWork FLOAT,
— 運用管理用
AppVersion NVARCHAR(50)
);
アーキテクトの視点:
なぜ`.mpp`ファイルそのものをバイナリでDBに入れないのか? それはDBの肥大化を招き、パフォーマンスを劣化させるからです。ファイルはファイルサーバーへ。DBには「検索と分析に必要なメタデータ」のみを抽出して格納する。 これが疎結合な設計の鉄則です。
—
2. 実装の要:Applicationレベルのイベントトラップ
MS ProjectのVBAで「保存時」を捉えるには、`ThisProject`モジュールにコードを書くのが一般的ですが、それはアマチュアの仕事です。複数のプロジェクトファイルを扱う場合、各ファイルにコードを埋め込むのは保守性の地獄を招きます。
プロフェッショナルは、クラスモジュールを用いた「Applicationイベントの捕捉」を選択します。
クラスモジュール:`clsProjectApp`
‘—————————————————————————————
‘ Class : clsProjectApp
‘ Purpose : MS Projectアプリケーションレベルのイベントを制御する
‘—————————————————————————————
Option Explicit
Private WithEvents pApp As MSProject.Application
Private Sub Class_Initialize()
Set pApp = MSProject.Application
End Sub
‘ プロジェクト保存前に発火するイベント
Private Sub pApp_ProjectBeforeSave(ByVal pj As Project, ByVal SaveAsUi As Boolean, Cancel As Boolean)
On Error GoTo ErrorHandler
‘ 保存処理の本体を呼び出し
Call modDatabase.RecordProjectMetadata(pj)
Exit Sub
ErrorHandler:
‘ 保存そのものを止めないよう、エラーはログ出力に留める(サイレント失敗の原則)
Debug.Print “Logging Error: ” & Err.Description
End Sub
—
3. SQL Server連携:ADODBによる堅牢なデータ転送
次に、実際にDBへ接続し、データを書き込むロジックを標準モジュールに記述します。
ここで絶対にやってはいけないのは、SQL文の文字列結合です。SQLインジェクション対策はもちろん、ファイル名に含まれるシングルクォーテーション等で構文エラーを起こさないよう、必ず`ADODB.Command`オブジェクトとパラメータを使用します。
標準モジュール:`modDatabase`
‘—————————————————————————————
‘ Module : modDatabase
‘ Purpose : SQL Serverへのデータ永続化ロジック
‘—————————————————————————————
Option Explicit
Public Sub RecordProjectMetadata(ByRef pj As MSProject.Project)
Dim conn As Object ‘ ADODB.Connection
Dim cmd As Object ‘ ADODB.Command
Dim connStr As String
‘ 1. 接続文字列の設定(環境に合わせて変更)
‘ ※ 実際には暗号化して保持するか、Windows認証を推奨
connStr = “Provider=SQLNCLI11;Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;Integrated Security=SSPI;”
On Error GoTo ConnError
Set conn = CreateObject(“ADODB.Connection”)
conn.Open connStr
‘ 2. パラメータ化クエリの構築
Dim sql As String
sql = “INSERT INTO ProjectSaveLogs (ProjectName, FilePath, LastSavedBy, ” & _
“BaselineDurationMinutes, BaselineCost, BaselineWork, AppVersion) ” & _
“VALUES (?, ?, ?, ?, ?, ?, ?)”
Set cmd = CreateObject(“ADODB.Command”)
With cmd
.ActiveConnection = conn
.CommandText = sql
.CommandType = 1 ‘ adCmdText
‘ パラメータのバインド(MS Projectのオブジェクトモデルから値を取得)
.Parameters.Append .CreateParameter(“@pjName”, 202, 1, 255, pj.Name) ‘ adVarWChar
.Parameters.Append .CreateParameter(“@path”, 202, 1, 500, pj.Path)
.Parameters.Append .CreateParameter(“@user”, 202, 1, 100, Environ(“USERNAME”))
‘ ベースライン設定の有無を確認し、存在しなければ0をセット
If pj.BaselineSaved(pjBaseline0) Then
.Parameters.Append .CreateParameter(“@dur”, 5, 1, , pj.BaselineDuration(pjBaseline0)) ‘ adDouble
.Parameters.Append .CreateParameter(“@cost”, 6, 1, , pj.BaselineCost(pjBaseline0)) ‘ adCurrency
.Parameters.Append .CreateParameter(“@work”, 5, 1, , pj.BaselineWork(pjBaseline0))
Else
.Parameters.Append .CreateParameter(“@dur”, 5, 1, , 0)
.Parameters.Append .CreateParameter(“@cost”, 6, 1, , 0)
.Parameters.Append .CreateParameter(“@work”, 5, 1, , 0)
End If
.Parameters.Append .CreateParameter(“@ver”, 202, 1, 50, Application.Version)
‘ 3. 実行
.Execute
End With
CleanUp:
If Not conn Is Nothing Then
If conn.State = 1 Then conn.Close
End If
Set cmd = Nothing
Set conn = Nothing
Exit Sub
ConnError:
‘ 業務を止めないよう、エラーはログに残して静かに終了
MsgBox “ベースライン情報のDB保存に失敗しました。管理者へ連絡してください。” & vbCrLf & _
Err.Description, vbCritical, “Database Error”
Resume CleanUp
End Sub
—
4. なぜこの設計が「上級者向け」なのか
① 非同期的な「失敗」への配慮
DB連携において最も恐れるべきは、「DBがダウンしているせいで、ユーザーがファイルを保存できなくなること」です。上記のコードでは、`ProjectBeforeSave`内でエラーハンドリングを完結させ、最悪DB接続に失敗しても`Cancel = True`を返さないようにしています。プロジェクトの遂行(保存)が最優先であり、ログはあくまで副次的なものであるという優先順位の設計です。
② パラメータ化クエリの徹底
`Project.Name`には「2023年度_【重要】プロジェクトA.mpp」のように、SQLの予約語や特殊文字が含まれる可能性が非常に高いです。文字列結合でSQLを組むのは、バグを招き入れる行為に他なりません。`ADODB.Command`による型定義は、堅牢なシステムの最低条件です。
③ ベースラインの動的判定
MS Projectにおいて、ベースラインが保存されていない状態で`pj.BaselineDuration`を参照するとエラーを吐くか、予期せぬ値を返します。`pj.BaselineSaved(pjBaseline0)`で存在チェックを行うのは、Project VBAを知り尽くした者だけが辿り着く「作法」です。
—
5. 運用への組み込み:Global.mptへの配置
このコードを全ての環境で有効にするには、個別のプロジェクトファイルではなく、各ユーザーの`Global.mpt`、あるいは組織で共有しているアドイン(`.mpp`テンプレート)に実装します。
`ThisProject`モジュールで、起動時にクラスをインスタンス化するコードを忘れずに記述してください。
‘ ThisProject モジュール
Private myAppEntity As clsProjectApp
Private Sub Project_Open(ByVal pj As Project)
Set myAppEntity = New clsProjectApp
End Sub
—
エピローグ:データが意志を持ち始める
このシステムが稼働し始めると、SQL Serverには「いつ、誰が、どの程度の規模(コスト・工数)のプロジェクトを動かしているか」の生データが蓄積されます。
これをPower BIで可視化すれば、「保存頻度が極端に低いプロジェクトは炎上リスクが高い」「ベースラインが頻繁に書き換えられているプロジェクトは計画が甘い」といった、経営層が喉から手が出るほど欲しがるインサイトを、VBA一本で提供できるようになるのです。
コードを書くことは手段に過ぎません。その先にある「ガバナンス」と「意思決定の迅速化」を設計すること。それが、我々チーフアーキテクトの使命です。
