【実務・中級編】【特定COMオブジェクトの生存チェック】CreateObject失敗時のレジストリ自動確認とCOMコンポーネント再登録バッチ – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

CreateObjectの死角を突く:レジストリ自動検証とSelf-Healing型COM再登録アーキテクチャ

現場の業務自動化において、長年運用されてきたVBScript(WSH)環境が突如として沈黙する原因の第一位は何か。それは、コードの論理バグではなく「依存COMコンポーネントの突然の喪失・不整合」である。

Windows Update、サードパーティ製ソフトウェアのサイレントアンインストール、あるいはセキュリティソフトによるレジストリエントリの隔離――これらによって、昨日まで動いていた `CreateObject(“Vendor.Component”)` は突如として `エラー 429: ActiveX コンポーネントはオブジェクトを作成できません。` を吐き出し、バッチ処理を緊急停止させる。

無責任なエンジニアは、スクリプトの先頭に `On Error Resume Next` を記述して問題を覆い隠すが、それは問題を先送りにしているに過ぎない。

真のシステムアーキテクトが取るべきアプローチは、「エラーを検知し、レジストリの状態を自律的に診断し、必要なDLL/OCXを管理者権限で再登録して正常系へ復帰する(Self-Healing: 自己修復)」 という堅牢な設計だ。

本稿では、VBScriptにおけるCOMオブジェクトの動的生存チェック、レジストリ構造の解読、UAC(ユーザーアカウント制御)を回避・昇格して `regsvr32` を実行するプロダクションレベルの自己修復コードを提示・解説する。

1. なぜ `CreateObject` は失敗するのか? アーキテクチャの解剖

単に「エラーが出たから再登録する」というコードを書く前に、Windowsが `CreateObject` 実行時にバックグラウンドで何を行っているかを正しく理解しなければならない。

COM呼び出しの内部メカニズム

[CreateObject(“MyApp.Engine”)]


1. HKEY_CLASSES_ROOT\MyApp.Engine 問い合わせ
│ ──> (CLSIDを取得: {XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX})

2. HKEY_CLASSES_ROOT\CLSID\{XXXXXXX-…}\InprocServer32 問い合わせ
│ ──> (DLLの物理パスを取得: C:\Program Files\Vendor\Engine.dll)

3. DLLのロードおよび DllGetClassObject の呼び出し

`CreateObject` が失敗するパターンは主に以下の3点に集約される。

1. ProgID未登録: レジストリに `ProgID`(例: `MyApp.Engine`)自体が存在しない。
2. CLSID/パスの破損: ProgIDは存在するが、紐づく `CLSID` や `InprocServer32` のファイルパスが存在しない、あるいは移動されている。
3. ビット数(Bitness)の不一致: 【最も根深い罠】 64bit版 `wscript.exe` から 32bit専用のDLL(またはその逆)を呼び出そうとしている。

本設計では、これらの状態を正確に判定し、物理ファイルが存在する場合にのみ `regsvr32` による自動再修復を試みる。

2. 堅牢な自己修復(Self-Healing)設計パターン

自動メンテナンス機構を構築する上で守るべき3つの鉄則がある。

1. グローバルな `On Error Resume Next` の排除
エラーハンドリングは `CreateObject` およびレジストリ読み込みの最小限のスコープに留め、直後に `On Error GoTo 0` で解除する。
2. 管理者権限の動的昇格(UAC Privilege Elevation)
`regsvr32` によるレジストリ書き込みは `HKEY_LOCAL_MACHINE` や `HKEY_CLASSES_ROOT` を対象とするため、標準ユーザー権限では拒否される。スクリプトが非昇格状態であれば、UAC昇格を伴って再登録コマンドを発行しなければならない。
3. 32bit / 64bit レジストリ・リフレクションの意識
`WScript.Shell` からレジストリを読み込む際、WSH実行プロセスのビット数によって参照先が `WOW6432Node` に自動リダイレクトされる挙動を把握する。

3. プロダクションコード実装

以下のコードは、本番環境にそのまま組み込み可能な自己修復型COMインスタンス生成モジュールである。

‘==============================================================================
‘ モジュール名: SelfHealingCOM.vbs
‘ 概要 : COMオブジェクト生成の安全なカプセル化とレジストリ自動修復
‘ 著作者 : Lead Automation Architect
‘==============================================================================
Option Explicit

‘ — テスト実行用メインロジック —
Dim myObject
Dim progID, targetDllPath

‘ 例: 独自ビルドのCOMコンポーネントまたはサードパーティ製DLL
progID = “MyCustomComponent.Engine”
targetDllPath = “C:\Program Files\MyCompany\Components\MyCustomComponent.dll”

WScript.Echo “=== COMインスタンス取得プロセスを開始します ===”
Set myObject = SafeCreateObject(progID, targetDllPath)

If Not myObject Is Nothing Then
WScript.Echo “[SUCCESS] COMオブジェクトの生成に成功しました。”
‘ — ここに本番処理を記述 —

‘ メモリ解放の徹底(VBScriptライフサイクルの定石)
Set myObject = Nothing
Else
WScript.Echo “[FATAL] 修復プロセスを実行しましたが、COMオブジェクトの生成に失敗しました。”
WScript.Quit 1
End If

‘==============================================================================
‘ 関数名: SafeCreateObject
‘ 目的 : CreateObjectを実行し、失敗した場合はレジストリ診断および再登録を試みる
‘ 戻り値: オブジェクト(失敗時は Nothing)
‘==============================================================================
Function SafeCreateObject(ByVal strProgID, ByVal strDllPath)
Dim objInstance
Dim errNumber, errDescription

Set SafeCreateObject = Nothing

‘ 1. 段階的な生成試行 (Try CreateObject)
On Error Resume Next
Set objInstance = CreateObject(strProgID)
errNumber = Err.Number
errDescription = Err.Description
On Error GoTo 0

‘ 成功した場合はそのままオブジェクトを返却
If errNumber = 0 Then
Set SafeCreateObject = objInstance
Exit Function
End If

‘ 2. エラー発生時のログ出力と自己修復フェーズへの移行
WScript.Echo “[WARN] CreateObject 失敗: ” & strProgID & ” (Error: ” & errNumber & ” – ” & errDescription & “)”
WScript.Echo “[INFO] レジストリ状態の診断および自己修復シーケンスを開始します…”

‘ 3. レジストリおよび物理ファイルの確認
If DiagnosedAndNeedsRegister(strProgID, strDllPath) Then
‘ 4. regsvr32 の昇格実行による再登録
If RegisterComponentElevated(strDllPath) Then
WScript.Echo “[INFO] コンポーネントの再登録コマンドが発行されました。再試行します…”
WScript.Sleep 1000 ‘ レジストリ反映の微小ウェイト

‘ 5. リトライ
On Error Resume Next
Set objInstance = CreateObject(strProgID)
errNumber = Err.Number
On Error GoTo 0

If errNumber = 0 Then
WScript.Echo “[SUCCESS] 自己修復後に CreateObject が成功しました。”
Set SafeCreateObject = objInstance
Exit Function
Else
WScript.Echo “[ERROR] 再登録後も CreateObject が失敗しました。Bitnessの不一致などを確認してください。”
End If
Else
WScript.Echo “[ERROR] コンポーネントの管理者権限再登録に失敗しました。”
End If
Else
WScript.Echo “[ERROR] 指定されたDLLファイルが存在しないか、手動介入が必要です: ” & strDllPath
End If

End Function

‘==============================================================================
‘ 関数名: DiagnosedAndNeedsRegister
‘ 目的 : 物理DLLの存在とレジストリエントリの整合性を検査する
‘==============================================================================
Function DiagnosedAndNeedsRegister(ByVal strProgID, ByVal strDllPath)
Dim objFSO, objShell
Dim clsidValue, regPath

DiagnosedAndNeedsRegister = False

Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objShell = CreateObject(“WScript.Shell”)

‘ 物理ファイルの存在チェック(存在しなければ修復不可能)
If Not objFSO.FileExists(strDllPath) Then
WScript.Echo “[CRITICAL] DLL物理ファイルが見つかりません: ” & strDllPath
Set objFSO = Nothing
Set objShell = Nothing
Exit Function
End If

‘ レジストリのProgIDエントリを確認
regPath = “HKEY_CLASSES_ROOT\” & strProgID & “\CLSID\”

On Error Resume Next
clsidValue = objShell.RegRead(regPath)
If Err.Number <> 0 Then
WScript.Echo “[DIAG] レジストリキーが見つかりません: ” & regPath & ” (未登録状態)”
DiagnosedAndNeedsRegister = True
Else
WScript.Echo “[DIAG] ProgIDはレジストリに存在します (CLSID: ” & clsidValue & “)。登録が破損している可能性があります。”
DiagnosedAndNeedsRegister = True
End If
On Error GoTo 0

Set objFSO = Nothing
Set objShell = Nothing
End Function

‘==============================================================================
‘ 関数名: RegisterComponentElevated
‘ 目的 : Shell.Application を使用して UAC 昇格プロンプト経由で regsvr32 を無音実行する
‘==============================================================================
Function RegisterComponentElevated(ByVal strDllPath)
Dim objShellApp
Dim args

On Error Resume Next
Set objShellApp = CreateObject(“Shell.Application”)

‘ /s: サイレントモード(ダイアログ非表示)
args = “/s “”” & strDllPath & “”””

‘ verb “runas” により、必要に応じてUACプロンプトを昇格呼び出し
objShellApp.ShellExecute “regsvr32.exe”, args, “”, “runas”, 1

If Err.Number = 0 Then
‘ プロセス起動の安定化のため待機(同期実行が不可のため)
WScript.Sleep 2000
RegisterComponentElevated = True
Else
WScript.Echo “[ERROR] ShellExecute (runas) の呼び出しに失敗: ” & Err.Description
RegisterComponentElevated = False
End If

On Error GoTo 0
Set objShellApp = Nothing
End Function

4. アーキテクトが解説する実装の肝と落とし穴

現場の運用でバグを出さないため、このコードに盛り込まれた技術的意図を解説する。

1. `Shell.Application` による “runas” 昇格メカニズム

`WScript.Shell` の `.Run` メソッドでは、スクリプトが非管理者権限で動いている場合に UAC(ユーザーアカウント制御)をバイパスして管理者の `regsvr32` を起動できない。
コード内で使用している `Shell.Application` オブジェクトの `ShellExecute` メソッドにパラメータ `”runas”` を渡すことで、Windows標準の昇格ダイアログを発火させ、安全かつ確実にレジストリへ書き込みを行う。

2. 非同期プロセスのウェイト制御

`ShellExecute` は非同期でプロセスを起動して即座に制御を戻す。そのため、`regsvr32` が完了する前に2回目の `CreateObject` が走ると、再びエラー429で失敗する。これを防ぐため、直後に `WScript.Sleep 2000` を挟み込み、レジストリキャッシュの同期時間を確定させている。

3. 32bit / 64bit 不一致(Bitness Trap)への考慮

64bit Windows上で32bitのDLLを `regsvr32` に登録する場合、64bitの `regsvr32.exe`(`System32`内)ではなく、32bit用の `regsvr32.exe`(`SysWOW64`内)で登録する必要があるケースが存在する。

もし上記スクリプトを実行しても修復に失敗する場合、呼び出し元の `wscript.exe` / `cscript.exe` 自体がミスマッチを起こしている。その場合はバッチファイルの段階で以下のように明示的に32bit環境へ切り替えて実行する設計が必要だ。

:: 32bit環境でVBScriptを強制実行するラッパーバッチ例
@echo off
%SystemRoot%\SysWOW64\cscript.exe //Nologo “C:\Scripts\SelfHealingCOM.vbs”

5. まとめ:自己修復設計がもたらす運用コストの最小化

自動化ツールは「正常に動くこと」以上に「異常時にシステムを止めず、自己回復できること」が評価の境目となる。

今回提示した「`CreateObject` 捕獲 → レジストリ診断 → UAC昇格再登録 → 自動リトライ」のパターンを標準共通モジュールとして組み込んでおけば、コンポーネント未登録に起因する深夜帯の障害呼び出しを激減させることができる。

「動けばいい」スクリプトから卒業し、インフラの変動に耐えうる頑丈なオートメーション・アーキテクチャを構築していただきたい。

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