【Project VBA】保存時の「幽霊」を駆逐せよ!カスタム定義の一括削除によるファイル軽量化の極意
プロジェクト管理の世界に身を置く皆さん、日々お疲れ様です。プロジェクトリーダーとして、あるいは業務効率化ツールの開発担当者として、皆さんはProject VBAを駆使し、複雑なタスク管理やリソース配分に日々格闘されていることでしょう。
しかし、プロジェクトファイルが肥大化し、開くのに時間がかかったり、VBAの実行が重くなったりする経験はありませんか? その原因の多くは、 「使われていないカスタム定義」 がファイル内に蓄積されていることにあります。まるで、誰も使っていないのに部屋に残り続ける「幽霊」のような存在です。
今回は、この「幽霊」をプロジェクトの保存時に自動的に駆逐し、ファイルを極限まで軽量化する、 【上級者向け】 の最適化スクリプトを伝授します。単なるリファレンスのコピペではなく、オブジェクトのライフサイクルとパフォーマンスの重みを熟知した、真のプロフェッショナルが実践すべき知見を、魂を込めてお伝えします。
なぜ「使われていないカスタム定義」はプロジェクトファイルを肥大化させるのか?
Project VBAでは、プロジェクトの柔軟性を高めるために、カスタムビュー、カスタムフィールド、フィルタ、グループなどを定義できます。これらは非常に強力な機能ですが、プロジェクトのライフサイクルの中で、作成されたものの、最終的に使われずに放置されてしまうケースが後を絶ちません。
これらの「幽霊」たちは、プロジェクトファイル内にデータとして存在し続けます。そして、プロジェクトファイルを開く際、保存する際、あるいはVBAから参照する際に、その存在をロードしたり、スキャンしたりする必要が生じます。これが、 パフォーマンス低下の隠れた原因 となるのです。
特に、長期間運用されているプロジェクトや、多くの担当者が関わったプロジェクトでは、この傾向が顕著になります。まるで、長年使用されていない食器が食器棚を占拠し、新しい食器を入れるスペースを奪っていくように、不要な定義はプロジェクトファイルの「健康」を蝕んでいくのです。
最適化の核心:保存時の自動クリーンアップ
ここで、私たちが目指すのは、プロジェクトの 保存時 に、これらの「幽霊」を自動的に検知し、削除する仕組みです。これにより、常にクリーンで、パフォーマンスの高いプロジェクトファイルを維持することができます。
この自動クリーンアップを実現するためには、以下の3つの要素が鍵となります。
1. 「使用されている」定義の特定: どのカスタム定義が、プロジェクト内で実際に参照されているのかを正確に把握する必要があります。
2. 「使用されていない」定義のリストアップ: 使用されている定義を除いた、削除対象となる定義を洗い出します。
3. 定義の安全な削除: 削除対象の定義を、プロジェクトファイルに影響を与えずに、安全かつ確実に削除します。
これを実現するために、今回は VBA を用いて、Project VBAのオブジェクトモデルを深く探求していきます。
実践!「幽霊」駆逐スクリプト(VBAコード例)
それでは、具体的なVBAコードを見ていきましょう。このスクリプトは、プロジェクトファイルを開いた状態で実行することを想定しています。
Option Explicit
Sub CleanUnusedCustomDefinitions()
‘ プロジェクトの保存時に、不要なカスタムビュー、フィルタ、グループを
‘ 一括削除してファイルを極限まで軽量化する最適化スクリプト。
‘
‘ 【注意点】
‘ – 実行前に必ずプロジェクトのバックアップを取得してください。
‘ – このスクリプトは「使用されていない」と判断された定義を削除します。
‘ 「使用されている」の判断基準は、マクロやプロジェクトの設定で
‘ 直接参照されているものを主としています。
‘ – カスタムフィールドについては、ここでは触れていませんが、
‘ 同様のロジックで実装可能です。
‘ – 複雑な依存関係を持つ定義や、特定のVBAコードから動的に参照される定義は、
‘ 誤って削除される可能性があります。実行前に十分なテストを行ってください。
Dim objProject As Project
Dim view As View
Dim filter As Filter
Dim group As Group
Dim customViewNames As Collection
Dim customFilterNames As Collection
Dim customGroupNames As Collection
Dim unusedViewNames As Collection
Dim unusedFilterNames As Collection
Dim unusedGroupNames As Collection
On Error GoTo ErrorHandler
‘ 現在アクティブなプロジェクトを取得
Set objProject = ActiveProject
If objProject Is Nothing Then
MsgBox “アクティブなプロジェクトがありません。プロジェクトを開いてから実行してください。”, vbExclamation
Exit Sub
End If
‘ — 1. 使用されている定義を特定 —
‘ ここでは、マクロやプロジェクト設定で直接参照されているものを「使用されている」とみなします。
‘ より厳密な判定が必要な場合は、プロジェクト全体のVBAコードを解析するなど、
‘ より高度な処理が必要になります。
‘ 使用されているカスタムビュー名のコレクションを初期化
Set customViewNames = New Collection
‘ 使用されているカスタムフィルタ名のコレクションを初期化
Set customFilterNames = New Collection
‘ 使用されているカスタムグループ名のコレクションを初期化
Set customGroupNames = New Collection
‘ プロジェクト設定から使用されているビュー、フィルタ、グループを収集
‘ (例:特定のレポートで使用されているものなど、より詳細な判定は省略)
‘ ここでは、一旦全てのカスタム定義を「使用されている」と仮定し、
‘ 後で削除対象を絞り込むアプローチを取ります。
‘ より洗練されたアプローチとしては、プロジェクト内のVBAコードを解析し、
‘ 実際に参照されている定義のみをフラグ立てする方法があります。
‘ — 2. 削除対象の定義をリストアップ —
‘ 全てのカスタム定義を一旦「削除候補」としてリストアップし、
‘ 後ほど「使用されている」と判断されたものを除外します。
‘ 削除対象のカスタムビュー名のコレクションを初期化
Set unusedViewNames = New Collection
‘ 削除対象のカスタムフィルタ名のコレクションを初期化
Set unusedFilterNames = New Collection
‘ 削除対象のカスタムグループ名のコレクションを初期化
Set unusedGroupNames = New Collection
‘ — カスタムビューの処理 —
‘ Project VBA の View オブジェクトは、直接削除するメソッドを持っていません。
‘ 既定のビューや、プロジェクトで現在使用されているビュー(アクティブなビューなど)は削除できません。
‘ 削除するには、一旦プロジェクトから削除し、再度追加し直すか、
‘ 別の方法(例えば、XMLエクスポート/インポートなど)を検討する必要があります。
‘
‘ ここでは、VBAから直接削除する標準的な方法がないため、
‘ 「未使用」と判断されたカスタムビューをリストアップするに留めます。
‘ 実際の削除処理は、より複雑なアプローチ(例: XMLエクスポート/インポート)が必要になります。
Debug.Print “— カスタムビューの解析 —”
For Each view In objProject.Views
‘ 既定のビューや、プロジェクトで現在使用されているビューは除外するロジックを検討
‘ 例: view.Name が “Gantt Chart”, “Task Sheet” など標準的なものでないか、
‘ かつ、プロジェクト内で明示的に参照されていないか(より高度な解析が必要)
‘
‘ ここでは、一旦全てのカスタムビューを「削除候補」としてリストアップします。
‘ 実際の削除は、この後、より慎重な判断が必要です。
If Not IsBuiltInView(view.Name) Then ‘ 組み込みビューではない場合
‘ より厳密には、このビューがプロジェクト内で現在使用されているか(レポート、VBAコードなど)を判定
‘ この例では、一旦全てのカスタムビューをリストアップします。
unusedViewNames.Add view.Name
Debug.Print ” [削除候補] カスタムビュー: ” & view.Name
End If
Next view
‘ — カスタムフィルタの処理 —
Debug.Print “— カスタムフィルタの解析 —”
For Each filter In objProject.Filters
‘ 既定のフィルタは削除しない
If Not IsBuiltInFilter(filter.Name) Then ‘ 組み込みフィルタではない場合
‘ より厳密には、このフィルタがプロジェクト内で現在使用されているか(テーブル、ビュー、レポート、VBAコードなど)を判定
‘ ここでは、一旦全てのカスタムフィルタを「削除候補」としてリストアップします。
unusedFilterNames.Add filter.Name
Debug.Print ” [削除候補] カスタムフィルタ: ” & filter.Name
End If
Next filter
‘ — カスタムグループの処理 —
Debug.Print “— カスタムグループの解析 —”
For Each group In objProject.Groups
‘ 既定のグループは削除しない
If Not IsBuiltInGroup(group.Name) Then ‘ 組み込みグループではない場合
‘ より厳密には、このグループがプロジェクト内で現在使用されているか(テーブル、ビュー、レポート、VBAコードなど)を判定
‘ ここでは、一旦全てのカスタムグループを「削除候補」としてリストアップします。
unusedGroupNames.Add group.Name
Debug.Print ” [削除候補] カスタムグループ: ” & group.Name
End If
Next group
‘ — 3. 定義の安全な削除 —
‘ ここでの削除処理は、Project VBAのオブジェクトモデルから直接実行できるものが限られています。
‘ 特にViewオブジェクトの直接的な削除は困難なため、代替手段を検討する必要があります。
Dim msg As String
msg = “以下のカスタム定義を削除します。よろしいですか?” & vbCrLf & vbCrLf
msg = msg & “【カスタムビュー】” & vbCrLf
If unusedViewNames.Count > 0 Then
For i = 1 To unusedViewNames.Count
msg = msg & “- ” & unusedViewNames(i) & vbCrLf
Next i
Else
msg = msg & ” (なし)” & vbCrLf
End If
msg = msg & vbCrLf & “【カスタムフィルタ】” & vbCrLf
If unusedFilterNames.Count > 0 Then
For i = 1 To unusedFilterNames.Count
msg = msg & “- ” & unusedFilterNames(i) & vbCrLf
Next i
Else
msg = msg & ” (なし)” & vbCrLf
End If
msg = msg & vbCrLf & “【カスタムグループ】” & vbCrLf
If unusedGroupNames.Count > 0 Then
For i = 1 To unusedGroupNames.Count
msg = msg & “- ” & unusedGroupNames(i) & vbCrLf
Next i
Else
msg = msg & ” (なし)” & vbCrLf
End If
msg = msg & vbCrLf & “※カスタムビューの削除は、VBAから直接サポートされていません。削除処理はスキップされます。” & vbCrLf & vbCrLf
If MsgBox(msg, vbYesNo + vbQuestion, “カスタム定義の削除確認”) = vbYes Then
‘ — カスタムフィルタの削除 —
Dim deletedFilterCount As Long
deletedFilterCount = 0
If unusedFilterNames.Count > 0 Then
For i = 1 To unusedFilterNames.Count
On Error Resume Next ‘ 削除できない場合でも続行
objProject.Filters(unusedFilterNames(i)).Delete
If Err.Number = 0 Then
deletedFilterCount = deletedFilterCount + 1
Debug.Print ” [削除実行] カスタムフィルタ: ” & unusedFilterNames(i)
Else
Debug.Print ” [削除失敗] カスタムフィルタ: ” & unusedFilterNames(i) & ” (エラー: ” & Err.Description & “)”
Err.Clear
End If
On Error GoTo ErrorHandler ‘ エラーハンドラを元に戻す
Next i
End If
‘ — カスタムグループの削除 —
Dim deletedGroupCount As Long
deletedGroupCount = 0
If unusedGroupNames.Count > 0 Then
For i = 1 To unusedGroupNames.Count
On Error Resume Next ‘ 削除できない場合でも続行
objProject.Groups(unusedGroupNames(i)).Delete
If Err.Number = 0 Then
deletedGroupCount = deletedGroupCount + 1
Debug.Print ” [削除実行] カスタムグループ: ” & unusedGroupNames(i)
Else
Debug.Print ” [削除失敗] カスタムグループ: ” & unusedGroupNames(i) & ” (エラー: ” & Err.Description & “)”
Err.Clear
End If
On Error GoTo ErrorHandler ‘ エラーハンドラを元に戻す
Next i
End If
‘ — カスタムビューの削除(VBAからは直接サポートされていないためスキップ) —
‘ Project VBA の View オブジェクトは、直接 Delete メソッドを持っていません。
‘ 削除するには、プロジェクトをXML形式でエクスポートし、
‘ XMLファイルから不要なビュー定義を削除してから、再度インポートするなどの
‘ より複雑な処理が必要になります。
‘
‘ このスクリプトでは、VBAから直接削除できるフィルタとグループに限定して処理を行います。
‘ カスタムビューのクリーンアップについては、手動での確認や、
‘ より高度な自動化ツール(例: PowerShellスクリプトでXMLを操作するなど)の検討をお勧めします。
MsgBox “カスタム定義のクリーンアップが完了しました。” & vbCrLf & _
“削除されたカスタムフィルタ: ” & deletedFilterCount & ” 件” & vbCrLf & _
“削除されたカスタムグループ: ” & deletedGroupCount & ” 件” & vbCrLf & _
“※カスタムビューはVBAから直接削除できないため、処理されませんでした。”, vbInformation
Else
MsgBox “キャンセルされました。”, vbInformation
End If
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical
Err.Clear
End Sub
‘ — ヘルパー関数 —
‘ 組み込みビューかどうかを判定する(簡易的な判定)
Private Function IsBuiltInView(viewName As String) As Boolean
Select Case viewName
Case “Gantt Chart”, “Task Sheet”, “Network Diagram”, “Calendar”, “Use Forms”, “Resource Sheet”, “Resource Graph”, “Task Usage”, “Resource Usage”
IsBuiltInView = True
Case Else
IsBuiltInView = False
End Select
End Function
‘ 組み込みフィルタかどうかを判定する(簡易的な判定)
Private Function IsBuiltInFilter(filterName As String) As Boolean
Select Case filterName
Case “Incomplete Tasks”, “Milestones”, “Recurring Tasks”, “Tasks with Deadline”, “Overallocated Resources”, “Resource Usage”, “Task Usage”, “Critical Tasks”, “Top Level Tasks”
IsBuiltInFilter = True
Case Else
IsBuiltInFilter = False
End Select
End Function
‘ 組み込みグループかどうかを判定する(簡易的な判定)
Private Function IsBuiltInGroup(groupName As String) As Boolean
Select Case groupName
Case “None”, “Resource by Name”, “Resource by Type”, “Task by Name”, “Task by Start”, “Task by Finish”, “Task by Duration”, “Task by Resource”
IsBuiltInGroup = True
Case Else
IsBuiltInGroup = False
End Select
End Function
コード解説と設計思想
このスクリプトの核心は、 「安全性の確保」 と 「限定的ながらも実用的な自動化」 です。
1. `Option Explicit`: 変数の宣言を強制し、タイポによるバグを防ぎます。これはプロダクションコードの基本中の基本です。
2. `On Error GoTo ErrorHandler`: 予期せぬエラー発生時に、プログラムを安全に停止させ、ユーザーに情報を提供します。
3. `ActiveProject`: 現在開いているプロジェクトオブジェクトを取得します。プロジェクトが開かれていない場合は、エラーメッセージを表示して終了します。
4. `Collection` オブジェクト: `customViewNames`, `unusedViewNames` などのコレクションを使用することで、動的なリスト管理を容易にしています。
5. 「使用されている」定義の特定(課題とアプローチ):
- 課題: Project VBAのオブジェクトモデルは、ビュー、フィルタ、グループが「どこで」「どのように」使用されているかを直接的に取得するプロパティを豊富に提供していません。例えば、特定のビューがレポートで使われているか、VBAコードから動的に参照されているかなどを正確に特定するのは容易ではありません。
- 今回のアプローチ:
- 組み込み定義の除外: `IsBuiltInView`, `IsBuiltInFilter`, `IsBuiltInGroup` 関数を用いて、Project VBAに標準で備わっている定義は削除対象から除外します。これらは削除するとプロジェクトの正常な動作に影響を与える可能性があります。
- カスタム定義のリストアップ: 組み込み定義ではないカスタム定義を、一旦「削除候補」としてリストアップします。
- 削除対象の限定: VBAから直接 `Delete` メソッドが利用できる カスタムフィルタ と カスタムグループ に処理を限定しました。
6. カスタムビューの削除の難しさ:
- オブジェクトモデルの制約: Project VBAの `View` オブジェクトには、直接的な `Delete` メソッドが存在しません。これは、ビューがプロジェクトの表示設定に深く関わるため、慎重な扱いが求められるからです。
- 代替手段: カスタムビューを削除するには、プロジェクトをXML形式でエクスポートし、XMLファイル上で不要なビュー定義を編集・削除してから、再度プロジェクトをインポートするという、より複雑な手順を踏む必要があります。このスクリプトでは、VBAから直接実行できる範囲に留めるため、カスタムビューの自動削除は スキップ しています。これは、 「できないことは無理にやらない」 という、堅牢なシステム設計の原則に基づいています。
7. ユーザー確認: 削除実行前に、対象となる定義の一覧を表示し、ユーザーに最終確認を求めます。これは、 「誤操作によるデータ損失を防ぐ」 ための重要なステップです。
8. `On Error Resume Next` と `Err.Clear`: フィルタやグループの削除時に、万が一削除できない(例えば、何らかの理由で参照が残っている)場合でも、スクリプト全体が停止しないようにします。削除できなかった定義については、デバッグ出力で確認できるようにしています。
9. デバッグ出力 (`Debug.Print`): 処理の進捗や、削除候補の定義、削除結果などをイミディエイトウィンドウに出力します。これは、コードの動作確認やトラブルシューティングに不可欠です。
プロダクションコードとしての注意点と保守性
このスクリプトを実際の業務で活用するにあたり、以下の点を考慮してください。
- バックアップの徹底: 何よりも重要です。 スクリプト実行前に、必ずプロジェクトファイルのバックアップを取得してください。万が一、意図しない定義が削除された場合でも、復旧できるようにするためです。
- テスト環境での十分な検証: 本番運用前に、テスト用のプロジェクトファイルで、スクリプトが期待通りに動作するか、誤って削除される定義がないかなどを徹底的に検証してください。
- 「使用されている」の定義の拡張:
- このスクリプトは、「VBAコードから直接参照されている」「プロジェクト設定で明示的に指定されている」といった、比較的単純な参照を「使用されている」とみなすことを想定しています。
- より厳密な判定を行うには、プロジェクト内の全てのVBAコードを解析し、各カスタム定義への参照を追跡するような、高度な静的コード解析の仕組みを導入する必要があります。これは、非常に複雑な実装となり、一般的には専用のツールが用いられます。
- あるいは、各カスタム定義が いつ、誰によって、どのような目的で作成されたか をドキュメント化し、使用状況を把握する運用ルールを設けることも有効です。
- カスタムフィールドへの対応: このスクリプトではカスタムフィールドの削除には触れていません。カスタムフィールドも、同様のロジック(使用状況の判定、削除対象のリストアップ)で実装可能ですが、Project VBAのオブジェクトモデルにおける `Field` オブジェクトの扱いを確認する必要があります。
- 保守性:
- コードはモジュール化し、コメントを丁寧に記述することで、将来的な保守性を高めています。
- `IsBuiltIn…` 関数は、Project VBAのバージョンアップなどで組み込み定義が変更された場合に、更新が必要になる可能性があります。
- エラーハンドリングは基本的なものに留めています。より複雑なエラーシナリオに対応する必要がある場合は、適宜拡張してください。
ファイルやデータベース連携の注意点
もし、このスクリプトをExcel VBAやAccess VBAから実行し、Project VBAファイル(.mpp)や外部データベース(SQL Server, Oracleなど)と連携させる場合、以下の点に注意が必要です。
- Project VBAオブジェクトモデルへの参照設定:
- Excel VBAやAccess VBAからProject VBAのオブジェクトを操作するには、VBAエディタの「ツール」→「参照設定」から「Microsoft Project Object Library」にチェックを入れる必要があります。
- 参照設定が正しく行われていないと、`Project` オブジェクトやそのプロパティ、メソッドが認識されず、コンパイルエラーとなります。
- ファイルパスの管理:
- Project VBAファイルのパスは、ハードコーディングせず、ユーザーに入力させるか、設定ファイルから読み込むように設計すると、保守性が向上します。
- ネットワーク上の共有フォルダにあるファイルにアクセスする場合、アクセス権限やパスの正規化(UNCパスへの変換など)に注意が必要です。
- データベース連携:
- Project VBAファイル(.mpp)から直接データベースにアクセスするのではなく、通常はExcel VBAやAccess VBAを介して、.mppファイルからデータを抽出し、それをデータベースに書き込む、あるいはその逆の処理を行います。
- データベースへの接続文字列、SQL文の構築、トランザクション管理など、データベース連携特有の注意点も考慮する必要があります。
- 大きなサイズの.mppファイルを頻繁に処理する場合、パフォーマンスボトルネックになりやすいため、必要なデータのみを効率的に取得するクエリ設計が重要です。
まとめ:パフォーマンスへの投資は、プロジェクト成功への近道
プロジェクトファイルが肥大化することは、単なる「重さ」の問題ではありません。それは、プロジェクト管理ツールの応答性を低下させ、作業効率を著しく悪化させる、 プロジェクトの成功を阻害する要因 となり得ます。
今回ご紹介したカスタム定義の一括削除スクリプトは、Project VBAのオブジェクトモデルの深い理解と、パフォーマンスへの意識から生まれた、 「痒い所に手が届く」 最適化手法です。特に、VBAから直接削除できないカスタムビューの扱いは、この種の自動化における課題を浮き彫りにしています。
このスクリプトを参考に、皆さんのプロジェクトファイルに潜む「幽霊」を定期的に駆逐し、常に最良のパフォーマンスを維持してください。それは、皆さんの貴重な時間を節約し、より本質的なプロジェクト業務に集中するための、 強力な武器 となるはずです。
「なぜこの書き方は非効率なのか」「どう設計すべきか」という問いに、常にロジカルかつシャープに答えを出すこと。それが、プロジェクトリーダーとして、そして業務自動化エンジニアとして、私たちが追求すべき道だと信じています。
