【マルチバリューレジストリ編集】WMI StdRegProv を使用した REG_MULTI_SZ(複数文字列値)の安全なパースと動的更新
レジストリ操作において、最もエンジニアを絶望させるデータ型は何か。それは間違いなく `REG_MULTI_SZ`(複数文字列値)である。
通常の `REG_SZ` や `REG_DWORD` であれば、WScript.Shell の `RegRead` と `RegWrite` で一撃だ。しかし、`REG_MULTI_SZ` を `RegRead` で読み出すと、VBScriptのランタイムは突如として「Variant型のエレメント配列(VT_ARRAY | VT_VARIANT)」という魔物を召喚する。これを無理やり文字列として扱おうものなら型不一致エラーの泥沼にハマり、さらに要素の追加や既存値の安全な部分置換を行おうとすれば、コードは途端にスパゲッティ化する。
WSH(Windows Script Host)およびVBScriptの寿命が尽きかけている現在もなお、レガシーなクライアント環境やドメイン環境の自動化において、この `REG_MULTI_SZ` の正確な制御はシニアエンジニアにとって避けて通れない必須科目だ。
今回は、WMI(Windows Management Instrumentation)の `StdRegProv` を駆使し、メモリリークを完全に排除しながら `REG_MULTI_SZ` を安全にパース、動的更新するための極限の知見を公開する。
—
1. なぜ WScript.Shell ではなく WMI `StdRegProv` なのか
レジストリ操作の入り口として `WScript.Shell` の `RegRead` / `RegWrite` を使うのは、インフラ自動化の初期段階における悪癖だ。
`WScript.Shell` には以下の決定的な致命傷がある。
1. `REG_MULTI_SZ` の書き込み精度の欠如: 標準のシェルオブジェクト経由では、マルチスレッド環境や特殊な改行コード混入時にデータ構造が破損することがある。
2. リモート接続の不在: `WScript.Shell` はローカルマシン専用である。ネットワーク越しの多数の端末を統御するエンタープライズ要件には微塵も耐ええない。
これに対し、WMIの `StdRegProv`(標準レジストリプロバイダ)は、`root\default` ネームスペースに常駐するシステムクラスであり、レジストリの各データ型を厳密なCOMバリアント配列として直接制御できる。ローカルはもちろん、DCOMを介したリモートマシンのレジストリ操作をも単一のインターフェースで完結させられる唯一無二の存在だ。
—
2. メモリ最適化とオブジェクトライフサイクルの鉄則
VBScriptのガベージコレクションは極めて怠惰である。特にWMIオブジェクト(`SWbemLocator` や `SWbemServices`)をループ内で生成・破棄するようなコードを書けば、瞬く間にVBScriptの背後でCOMの参照カウンタが溢れ返り、メモリリークの温床となる。
シニアエンジニアたるもの、「取得したオブジェクトは必ず明示的に `Nothing` を代入して解放する」「変数のスコープを最小限に絞る」 という鉄則を死守しなければならない。
—
3. 実装コード:安全なパースと動的更新の完全版
以下のスクリプトは、指定されたレジストリキーから `REG_MULTI_SZ` の配列を取得し、重複を排除しながら指定の文字列を「追加」、あるいは「削除」して書き戻すプロダクション品質のコードである。
Option Explicit
‘ ==============================================================================
‘ スクリプト名: RegMultiSZ_Manager.vbs
‘ 概要: WMI StdRegProv を使用した REG_MULTI_SZ の安全なパースと動的更新
‘ 依存関係: Administrator権限 (リモート操作時は適切な権限とDCOM設定が必要)
‘ ==============================================================================
Const HKEY_LOCAL_MACHINE = &H80000002
Const REG_PATH = “SOFTWARE\Policies\Microsoft\Windows\System”
Const REG_VALUE_NAME = “MyMultiStringValue”
‘ テスト実行
Call ModifyMultiSZ(HKEY_LOCAL_MACHINE, REG_PATH, REG_VALUE_NAME, “TargetValue_ToAdd”, “ADD”)
Call ModifyMultiSZ(HKEY_LOCAL_MACHINE, REG_PATH, REG_VALUE_NAME, “TargetValue_ToRemove”, “REMOVE”)
Sub ModifyMultiSZ(strHKey, strKeyPath, strValueName, strTargetValue, strAction)
Dim objLocator, objServices, oReg
Dim lRC, arrCurrentValues, arrNewValues
Dim i, j, bFound, bModified
Dim colList
On Error Resume Next
‘ 1. WMI接続の確立 (Locatorパターンによる安全な初期化)
Set objLocator = CreateObject(“WbemScripting.SWbemLocator”)
Set objServices = objLocator.ConnectServer(“.”, “root\default”)
If Err.Number <> 0 Then
WScript.Echo “[-] WMI接続に失敗しました: ” & Err.Description
Exit Sub
End If
‘ セキュリティと認証レベルの調整
objServices.Security_.ImpersonationLevel = 3 ‘ Impersonate
objServices.Security_.AuthenticationLevel = 4 ‘ Packet
‘ StdRegProv インスタンスの取得
Set oReg = objServices.Get(“StdRegProv”)
If Err.Number <> 0 Then
WScript.Echo “[-] StdRegProv の取得に失敗しました: ” & Err.Description
Set objLocator = Nothing
Set objServices = Nothing
Exit Sub
End If
On Error GoTo 0
‘ 2. 既存の REG_MULTI_SZ データの取得
‘ GetMultiStringValue メソッドは戻り値として Variant の配列を返す
lRC = oReg.GetMultiStringValue(strHKey, strKeyPath, strValueName, arrCurrentValues)
If lRC <> 0 Then
‘ レジストリキーまたは値が存在しない場合は空の配列として初期化
arrCurrentValues = Array()
End If
‘ 3. 動的パースと配列の再構築
bFound = False
bModified = False
If strAction = “ADD” Then
‘ 既存配列の中にターゲットが存在するか走査
If IsArray(arrCurrentValues) Then
For i = 0 To UBound(arrCurrentValues)
If UCase(arrCurrentValues(i)) = UCase(strTargetValue) Then
bFound = True
Exit For
End If
Next
End If
‘ 存在しない場合のみ動的拡張
If Not bFound Then
Dim nSize
If IsArray(arrCurrentValues) Then
nSize = UBound(arrCurrentValues) + 1
ReDim Preserve arrCurrentValues(nSize)
arrCurrentValues(nSize) = strTargetValue
Else
ReDim arrCurrentValues(0)
arrCurrentValues(0) = strTargetValue
End If
arrNewValues = arrCurrentValues
bModified = True
Else
arrNewValues = arrCurrentValues
End If
ElseIf strAction = “REMOVE” Then
‘ 削除処理:該当要素を除外した新しい動的配列を構築
If IsArray(arrCurrentValues) Then
Dim tempArray()
Dim count : count = 0
For i = 0 To UBound(arrCurrentValues)
If UCase(arrCurrentValues(i)) <> UCase(strTargetValue) Then
ReDim Preserve tempArray(count)
tempArray(count) = arrCurrentValues(i)
count = count + 1
Else
bModified = True ‘ 削除対象が見つかった
End If
Next
If bModified Then
If count > 0 Then
arrNewValues = tempArray
Else
arrNewValues = Array() ‘ すべて削除された場合
End If
Else
arrNewValues = arrCurrentValues
End If
Else
arrNewValues = Array()
End If
End If
‘ 4. 変更がある場合のみレジストリへ書き戻し
If bModified Or (lRC <> 0 And strAction = “ADD”) Then
lRC = oReg.SetMultiStringValue(strHKey, strKeyPath, strValueName, arrNewValues)
If lRC = 0 Then
WScript.Echo “[+] レジストリの更新に成功しました (” & strAction & “): ” & strTargetValue
Else
WScript.Echo “[-] レジストリの書き込みに失敗しました. Error Code: ” & lRC
End If
Else
WScript.Echo “[i] 変更の必要はありませんでした (値の状態に変化なし).”
End If
‘ 5. メモリの明示的解放 (ライフサイクル管理の徹底)
Set oReg = Nothing
Set objServices = Nothing
Set objLocator = Nothing
End Sub
—
4. チーフアーキテクトの洞察:コードの急所
この実装において、単なる「動くコード」を超えたプロフェッショナルな設計思想がいくつか隠されている。
配列境界(`UBound`)と空のステートのハンドリング
WMIの `GetMultiStringValue` は、対象の値が存在しない場合やデータが空の場合、戻り値として `Null` やエラーコード(非ゼロ)を返す。この時、`IsArray()` 関数による厳密な型チェックを行わずに `UBound` を叩くと、VBScriptは容赦なく実行時エラー(エラー 13: 型が一致しません)を吐き出す。上記のコードでは、未初期化状態を完全に網羅するセーフガードを構築している。
大文字小文字の正規化(`UCase`)
レジストリの複数文字列値において、文字列の大文字・小文字の違い(例: `Value1` と `value1`)による重複登録は、グループポリシーやアプリケーションの挙動を破壊する一般的な原因である。パースおよび比較のフェーズで `UCase()` による正規化を挟むことで、データの整合性を強固に担保している。
デッドオブジェクトの確実な抹消
末尾の `Set oReg = Nothing` 等の記述は、単なる作法ではない。VBScriptのCOMラッパーが保持するバックグラウンドのインターフェースポインタを即座に解放させ、ロングランでのスクリプト実行時におけるメモリリークを確実に防止するための防壁である。
—
結び
VBScriptはレガシーな言語と揶揄されることが多い。しかし、Windowsの根幹を成すAPI群(WMIやCOM)の挙動を熟知していれば、モダンな言語と同等、あるいはそれ以上に軽量かつ強力なシステム自動化の武器となり得る。
`REG_MULTI_SZ` という難敵に直面したとき、本記事の知見があなたのインフラ管理の現場で確かな防壁となることを期待する。
