【実務・中級編】【マルチバリューレジストリ編集】WMI StdRegProv を使用した REG_MULTI_SZ(複数文字列値)の安全なパースと動的更新 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【マルチバリューレジストリ編集】WMI StdRegProv を使用した REG_MULTI_SZ(複数文字列値)の安全なパースと動的更新

プロフェッショナルな現場において、VBScriptは「レガシーな言語」と侮られがちだ。しかし、インフラストラクチャの初期セットアップや、サードパーティ製ライブラリの持ち込みが許されない閉域環境のWindowsキッティングにおいて、WSH(Windows Script Host)とWMI(Windows Management Instrumentation)のコンビネーションは、今なお最強の無人化・自動化プラットフォームである。

特に、業務自動化ツールを開発する中でエンジニアの前に幾度となく立ち塞がるのが、レジストリの `REG_MULTI_SZ`(複数文字列値) だ。

標準の `WScript.Shell` の `RegRead` や `RegWrite` は、`REG_MULTI_SZ` を扱う際に単なる文字列(改行区切り)として返したり、配列への強制変換で予期せぬ型ミスマッチを引き起こしたりと、極めて扱いづらい。これを素朴な文字列操作だけでパースしようとすると、特殊文字の混入や配列の境界エラーで容易にスクリプトは爆散する。

今回は、WMIの `StdRegProv` クラスを駆使し、`REG_MULTI_SZ` を安全にパース、そして既存の整合性を1ミリも崩すことなく動的に要素の追加・削除・更新を行う、プロダクションクオリティの堅牢な設計手法を伝授しよう。

なぜ `WScript.Shell` では不十分なのか?

多くの初心者は、レジストリ操作の際に以下のようなコードを書く。

‘ 【アンチパターン】WScript.Shellによる直感的なレジストリ読み書き
Set ws = CreateObject(“WScript.Shell”)
val = ws.RegRead(“HKLM\SOFTWARE\MyCompany\App\Paths”)
‘ ここで返されるvalの型や構造は、環境やデータの持ち方によって揺らぎがあり非常に不安定

`REG_MULTI_SZ` は、内部的には「ヌル文字(`Chr(0)`)区切りの連続バイト列」として格納されている。これを `WScript.Shell` 経由で取得すると、改行コードに置換されることがあるが、配列としての厳密な境界保証がないため、要素の完全な動的制御(重複排除、特定キーワードの厳密な削除など)を行おうとすると、パース処理が途端にスパゲッティ化する。

また、リモートマシンのレジストリ操作や、実行権限(32bit/64bitリダイレクション)の制御を考慮した場合、`WScript.Shell` のアーキテクチャでは限界が早い段階で訪れる。

解決策:WMI `StdRegProv` の採用

WMIの `StdRegProv` (`root\default:StdRegProv`) は、レジストリをネイティブな型(配列=VBScriptのVariant配列)として安全に操作するための公式インターフェースだ。これを使用すれば、`REG_MULTI_SZ` を「文字列の配列(Array)」として完全にコントロールできる。

堅牢なマルチバリュー・レジストリマネージャーの設計

実務の現場で耐えうるスクリプトには、以下の要素が不可欠である。

1. エラーハンドリングの徹底: WMIメソッドの戻り値(Error Code)の厳密な評価。
2. 安全な配列の不変性(Immutability)の維持: 存在しないキーや値に対するフォールバック処理。
3. 重複排除(Deduplication)とクレンジング: 空要素の排除。

それでは、そのままエンタープライズ環境のデプロイメントに投入できるプロダクションコードを提示しよう。

プロダクション・コード:`RegMultiSzManager.vbs`

‘ ==============================================================================
‘ Script Name: RegMultiSzManager.vbs
‘ Description: WMI StdRegProv を用いた REG_MULTI_SZ の安全なパースと動的更新
‘ Author: Chief Systems Architect
‘ ==============================================================================
Option Explicit

Const HKEY_LOCAL_MACHINE = &H80000002
Const REG_SZ = 1
Const REG_MULTI_SZ = 7

‘ メイン処理の実行例
Sub Main()
Dim targetKey, targetValue, itemToAdd, itemToRemove
targetKey = “SOFTWARE\MyCompany\AppConfig”
targetValue = “AllowedServers”

itemToAdd = “srv-log-02.internal”
itemToRemove = “srv-old-01.internal”

WScript.Echo “=== 1. 要素の追加処理 ===”
If AddMultiSzItem(HKEY_LOCAL_MACHINE, targetKey, targetValue, itemToAdd) Then
WScript.Echo “成功: [” & itemToAdd & “] を追加しました。”
Else
WScript.Echo “失敗または既に存在します。”
End If

WScript.Echo vbCrLf & “=== 2. 現在のレジストリ値一覧 ===”
Dim currentValues
currentValues = GetMultiSzValues(HKEY_LOCAL_MACHINE, targetKey, targetValue)
If IsArray(currentValues) Then
Dim i
For i = LBound(currentValues) To UBound(currentValues)
WScript.Echo ” [” & i & “] ” & currentValues(i)
Next
Else
WScript.Echo “値が存在しないか、空です。”
End If

WScript.Echo vbCrLf & “=== 3. 要素の削除処理 ===”
If RemoveMultiSzItem(HKEY_LOCAL_MACHINE, targetKey, targetValue, itemToRemove) Then
WScript.Echo “成功: [” & itemToRemove & “] を削除しました。”
Else
WScript.Echo “失敗または対象が存在しませんでした。”
End If
End Sub

‘ ——————————————————————————
‘ REG_MULTI_SZ の値配列を取得する
‘ ——————————————————————————
Function GetMultiSzValues(hHive, sKeyPath, sValueName)
Dim oReg, oLocator, oServices, iRet
Set oLocator = CreateObject(“WbemScripting.SWbemLocator”)
Set oServices = oLocator.ConnectServer(“.”, “root\default”)
Set oReg = oServices.Get(“StdRegProv”)

Dim arrValues
iRet = oReg.GetMultiStringValue(hHive, sKeyPath, sValueName, arrValues)

If iRet = 0 And Not IsNull(arrValues) Then
GetMultiSzValues = arrValues
Else
GetMultiSzValues = Array() ‘ 空の配列を返して後続のエラーを防ぐ
End If
End Function

‘ ——————————————————————————
‘ REG_MULTI_SZ に要素を追加する(重複排除つき)
‘ ——————————————————————————
Function AddMultiSzItem(hHive, sKeyPath, sValueName, sItemToAdd)
Dim arrCurrent, arrNew, iCount, i, bExists
arrCurrent = GetMultiSzValues(hHive, sKeyPath, sValueName)

bExists = False
iCount = 0

‘ 既存要素の走査と重複チェック
If IsArray(arrCurrent) Then
For i = LBound(arrCurrent) To UBound(arrCurrent)
If LCase(arrCurrent(i)) = LCase(sItemToAdd) Then
bExists = True
Exit For
End If
Next
End If

If bExists Then
AddMultiSzItem = False ‘ 既に存在する場合は何もしない
Exit Function
End If

‘ 新しい配列のサイズを再構築
If IsArray(arrCurrent) Then
iCount = UBound(arrCurrent) – LBound(arrCurrent) + 1
ReDim arrNew(iCount)
For i = 0 To iCount – 1
arrNew(i) = arrCurrent(i)
Next
arrNew(iCount) = sItemToAdd
Else
ReDim arrNew(0)
arrNew(0) = sItemToAdd
End If

AddMultiSzItem = SetMultiSzValues(hHive, sKeyPath, sValueName, arrNew)
End Function

‘ ——————————————————————————
‘ REG_MULTI_SZ から特定要素を削除する
‘ ——————————————————————————
Function RemoveMultiSzItem(hHive, sKeyPath, sValueName, sItemToRemove)
Dim arrCurrent, arrNew(), iCount, i, bFound
arrCurrent = GetMultiSzValues(hHive, sKeyPath, sValueName)

If Not IsArray(arrCurrent) Then
RemoveMultiSzItem = False
Exit Function
End If

bFound = False
iCount = 0

‘ 一時的に動的配列を構築(マッチしないものを退避)
For i = LBound(arrCurrent) To UBound(arrCurrent)
If LCase(arrCurrent(i)) <> LCase(sItemToRemove) Then
ReDim Preserve arrNew(iCount)
arrNew(iCount) = arrCurrent(i)
iCount = iCount + 1
Else
bFound = True
End If
Next

If Not bFound Then
RemoveMultiSzItem = False
Exit Function
End If

‘ 要素がすべて消えた場合のハンドリング(空配列の書き込みまたはキー自体のクリーンアップ)
If iCount = 0 Then
ReDim arrNew(0)
arrNew(0) = “” ‘ 空の要素を設定
End If

RemoveMultiSzItem = SetMultiSzValues(hHive, sKeyPath, sValueName, arrNew)
End Function

‘ ——————————————————————————
‘ レジストリアップデートのコア関数
‘ ——————————————————————————
Function SetMultiSzValues(hHive, sKeyPath, sValueName, arrValues)
Dim oReg, oLocator, oServices, iRet
Set oLocator = CreateObject(“WbemScripting.SWbemLocator”)
Set oServices = oLocator.ConnectServer(“.”, “root\default”)
Set oReg = oServices.Get(“StdRegProv”)

‘ キーパスが存在するか確認し、なければ作成するロジックをここに挟むとさらに堅牢になる
iRet = oReg.SetMultiStringValue(hHive, sKeyPath, sValueName, arrValues)

If iRet = 0 Then
SetMultiSzValues = True
Else
SetMultiSzValues = False
End If
End Function

‘ 実行トリガー
Main()

コードの深掘りとアーキテクチャの解説

このコードが「なぜプロフェッショナル仕様なのか」、その実装上の重要なポイントを解説する。

1. `GetMultiStringValue` と `IsNull(arrValues)` の罠

WMI経由で値を取得するとき、指定したレジストリ値が存在しない場合、VBScript側で `arrValues` は空ではなく `Null` として返されることがある。これに対して直接 `UBound` やループを回すと、ランタイムエラー(「プロシージャの呼び出しまたは引数が不正です: ‘UBound’」)が発生する。
上記のコードでは、ラッパー関数 `GetMultiSzValues` の中で `IsNull` チェックを行い、安全に空の配列(`Array()`)を返すフォールバックを実装している。これにより、後続の配列操作ロジックが完全に保護される。

2. 大文字小文字を区別しない(Case-Insensitive)完全性

ネットワークのFQDNやファイルパスなどの設定値において、大文字と小文字の差異(例: `Server-01` と `server-01`)で重複エントリが生まれるのは自動化ツールのバグの温床だ。
本スクリプトでは、比較の際に `LCase()` 関数を挟むことで、厳密な一意性を担保している。

3. トランザクション思考と例外処理

レジストリの書き込みメソッド `SetMultiStringValue` は、成功時に `0` を返す。この戻り値を無視して「書き込んだはず」と思い込むのはアマチュアの所業である。必ず戻り値のステータスコードを評価し、エラーログや例外処理(必要に応じて `Err.Raise` やイベントログへの書き込み)へ繋げられる設計にしておくべきだ。

運用・ファイル連携における注意点

  • 実行権限(権限昇格): `HKEY_LOCAL_MACHINE (HKLM)` 配下を操作するため、当然ながらこのスクリプトは管理者特権(Administrator)で実行される必要がある。クライアント端末への自動デプロイ時は、Task Scheduler等で「最上位の特権で実行する」設定を忘れないこと。
  • 外部設定ファイル(CSV / JSON)との連携: 今回はハードコードされた文字列を追加・削除したが、実際の業務では「CSVファイルに定義された複数のサーバー群を、このレジストリの一覧にマージ(同期)したい」という要件が多い。その場合は、CSVを読み込むループの外側で `AddMultiSzItem` を呼び出す形に拡張すれば、堅牢な同期エンジンへと昇華できる。

まとめ

VBScriptは古びた言語かもしれないが、Windows OSの根幹をダイレクトに叩けるWMIとのコンビネーションは、今なお現役のキラー・テクノロジーだ。特に `REG_MULTI_SZ` のような扱いづらいデータ構造であっても、オブジェクトのライフサイクルと型安全性を意識した設計を行えば、バグの入り込む余地のない極めてロバストな自動化ツールを構築できる。

現場のエンジニア諸君には、場当たり的な文字列置換で時間を溶かすのではなく、こうした「型と構造を支配したコード」で、真の業務効率化とシステム信頼性を勝ち取ってほしい。

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