【実務・中級編】【上級者向け】COMアドインを利用した「保存時強制クリーンアップ」機能の配布 – Project VBA解析バイブル

スポンサーリンク

【上級者向け】COMアドインを利用した「保存時強制クリーンアップ」機能の配布

開発プロジェクトのリーダーである私たちが日々直面する最大のフラストレーションの一つ。それは、「エンドユーザーが勝手に不要なオブジェクトを残し、コードを汚し、巨大化したプロジェクトファイルを平然と共有してくる」という悪夢だ。

`Workbook_BeforeSave` にクリーンアップコードを書けば解決する?
甘い。そんなものは、ユーザーがマクロを有効にしていなければ無力だし、何より「マクロのセキュリティ無効化」「コードの改ざん・削除」という致命的なリスクの前には紙くず同然だ。

真に堅牢なエンタープライズ環境を構築したいのであれば、VBAの枠組み自体を疑う必要がある。今回は、VB.NETを用いたCOMアドイン(COM Add-in)を開発し、全ユーザーのProject(またはExcel)の保存プロセスに強制介入してクリーンアップを実行する、極限のアーキテクチャを伝授する。

なぜVBAマクロではダメなのか? ―― アーキテクチャの限界

多くの開発者は、イベントプロシージャ(`BeforeSave`など)に依存した設計を行う。しかし、これには明確な構造的欠陥がある。

1. マクロが無効化された瞬間、監視網が消滅する
2. ユーザーがコードを容易に覗き見、改ざん・削除できる
3. エラーハンドリングがユーザー依存になり、異常終了時にゴミデータが残る

これを解決するためには、アプリケーションのライフサイクル外(COMアドイン層)から強制的にフックをかけなければならない。COMアドインは、ホストアプリケーションの起動と同時に読み込まれ、ユーザーの意図に関わらずバックグラウンドで厳格に動作する。これこそが、プロフェッショナルが選ぶ唯一の解だ。

全体設計:COMアドインによるイベントインターセプト

今回は、Visual Studio (VB.NET) を使用して、Officeアプリケーション(今回は汎用性の高いProject/Excelを想定したCOMアドインのコア概念)の保存イベントを監視し、保存直前に「見えないゴミ(一時オブジェクト、不要なカスタムプロパティ、キャッシュなど)」を根こそぎパージする仕組みを構築する。

実装のステップ

1. COMVisibleなクラスの作成: VBA/ホストアプリから安全に制御を受け渡す。
2. イベントシンク(Event Sinking)の実装: 保存イベント(`BeforeSave`)を非同期かつ強制的にトラップ。
3. クリーンアップロジックの実行: メモリリークや肥大化の原因となる不要なメタデータをプログラム側から強制削除。
4. デプロイメント(配布): MSIインストーラーまたはレジストリ直書きによるサイレント配布。

プロダクションコード:強靭なCOMアドインの実装

以下のコードは、Visual Studioの「Shared Add-in」または「COM Add-in」プロジェクトで使用するVB.NETのコアロジックだ。実務で即座に耐えうるよう、例外処理とCOMオブジェクトの解放(解放漏れ対策)を徹底している。

Imports System.Runtime.InteropServices
Imports Extensibility
Imports Microsoft.Office.Core
‘ ※対象ホストに応じて Microsoft.Office.Interop.Excel または MS ProjectのInteropを参照すること




Public Class Connect
Implements IDTExtensibility2

‘ ホストアプリケーションのインスタンス
Private applicationObject As Object
Private addInInstance As Object

‘ イベントハンドラをフックするための変数(ホストに合わせた型定義が必要)
‘ 例: Excelの場合、WithEvents を使ったアプリケーションイベントの監視
‘ Private WithEvents appEvents As Excel.Application

Public Sub OnConnection(application As Object, connectMode As ext_ConnectMode, addInInst As Object, ByRef custom As Array) Implements IDTExtensibility2.OnConnection
applicationObject = application
addInInstance = addInInst

If connectMode <> ext_ConnectMode.ext_cm_Startup Then
‘ アドインが後からロードされた場合の初期化
OnStartupComplete(custom)
End If

‘ ここでホストのイベントオブジェクトをバインドする
‘ 例: appEvents = CType(applicationObject, Excel.Application)
End Sub

Public Sub OnDisconnection(disconnectMode As ext_DisconnectMode, ByRef custom As Array) Implements IDTExtensibility2.OnDisconnection
‘ 終了時のクリーンアップとCOM参照の確実な解放
ReleaseComObject(applicationObject)
End Sub

Public Sub OnAddInsUpdate(ByRef custom As Array) Implements IDTExtensibility2.OnAddInsUpdate
End Sub

Public Sub OnStartupComplete(ByRef custom As Array) Implements IDTExtensibility2.OnStartupComplete
End Sub

Public Sub OnBeginShutdown(ByRef custom As Array) Implements IDTExtensibility2.OnBeginShutdown
‘ シャットダウン時の処理
End Sub

”’

”’ 【コアロジック】保存直前に強制介入するイベントハンドラ
”’

Private Sub InterceptBeforeSave(ByVal Wb As Object, ByRef Cancel As Boolean)
Try
‘ ログ出力や監査証跡の記録(必要に応じて)
System.Diagnostics.Debug.WriteLine(“=== 強制クリーンアップ処理を開始します ===”)

‘ 1. 不要なカスタムプロパティのパージ
PurgeCustomProperties(Wb)

‘ 2. 未使用スタイルやゴーストオブジェクトの強制削除
PurgeUnusedObjects(Wb)

‘ 3. 整合性チェック(データの破損を防ぐ)
If Not ValidateProjectIntegrity(Wb) Then
‘ 致命的な不整合がある場合は保存をキャンセル
Cancel = True
MessageBox.Show(“プロジェクトのデータ構造に異常が検出されたため、保存が中断されました。”, “Enterprise Security”, MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End If

System.Diagnostics.Debug.WriteLine(“=== 強制クリーンアップ処理が正常終了しました ===”)

Catch ex As Exception
‘ 例外発生時でもホストを落とさないための防御的プログラミング
System.Diagnostics.Debug.WriteLine(“クリーンアップ中にエラーが発生しました: ” & ex.Message)
‘ 業務継続性を優先する場合、エラーログを残しつつ保存自体はブロックしない設計にする
End Try
End Sub

Private Sub PurgeCustomProperties(ByVal targetObject As Object)
‘ 実務におけるメタデータ(ゴミデータ)の削除処理をここに実装
‘ 例: targetObject.CustomDocumentProperties から不要なキーを走査・削除
End Sub

Private Sub PurgeUnusedObjects(ByVal targetObject As Object)
‘ 肥大化の原因となる不要なオブジェクト参照を切断
End Sub

Private Function ValidateProjectIntegrity(ByVal targetObject As Object) As Boolean
‘ データの整合性検証ロジック
Return True
End Function

”’

”’ COMオブジェクトのメモリリークを防ぐための確実な解放メソッド
”’

Private Sub ReleaseComObject(ByVal obj As Object)
Try
If obj IsNot Nothing AndAlso Marshal.IsComObject(obj) Then
Marshal.ReleaseComObject(obj)
End If
Catch
‘ 握りつぶし
Finally
obj = Nothing
End Try
End Sub

End Class

現場で絶対に踏んではならない「地雷」と回避策

このレベルのツールを全社展開する場合、素人が書いたコードでは必ずインフラ部門から差し戻される。以下の3点を死守せよ。

1. COMオブジェクトの参照解放漏れ(メモリリーク)

VB.NETからOfficeのオブジェクトモデルを操作する際、`.`(ドット)でつないでプロパティやメソッドを連続して叩くと(例: `app.ActiveWorkbook.Sheets(1).Name`)、背後で暗黙的なCOMラッパーが生成され、ガベージコレクションの対象外となる。

  • 対策: オブジェクトの参照は必ずローカル変数に受けて、使い終わったら `Marshal.ReleaseComObject` を明示的に呼ぶこと。これを怠ると、Officeプロセスが裏でゾンビ化し、メモリを食い潰す。

2. サイレントインストールの権限問題

COMアドインは、レジストリ(`HKEY_CURRENT_USER` または `HKEY_LOCAL_MACHINE`)への登録が必須となる。ユーザー権限で動作する環境において、権限昇格なしで確実にアドインをロードさせるためには、User-Level (Per-User) 登録を採用したMSIパッケージ、あるいはClickOnce等のデプロイ手法を設計する必要がある。

3. 無限ループ・保存クラッシュの防止

`BeforeSave` イベント内でファイルを変更して保存(Save)を呼び出すと、無限再帰(Infinite Recursion)を引き起こし、アプリケーションが確実にクラッシュする。

  • 対策: クリーンアップ処理は「メモリ上のオブジェクトに対する非破壊的なプロパティ変更」にとどめ、イベント内で明示的な `Save` メソッドを呼び出さないこと。

リーダーからの総括

VBAのマクロだけで業務効率化を語る時代は終わった。真にスケールし、組織の資産を守るシステムを構築したいのであれば、アプリケーションの「外側」からガバナンスを効かせ るアーキテクチャを取り入れるべきだ。

COMアドインによる強制クリーンアップは、最初はハード高く感じるかもしれない。だが、一度導入すれば、ユーザーのスキル不足や不注意によるファイルの破損・肥大化を「完全自動で」根絶やしにできる。
コードを書いて終わりではない。運用に耐え、保守性を極限まで高めたシステムをデザインし抜け。それがプロの仕事だ。

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