KVP のデータを取得する WSH サンプル スクリプト
1 つの解決策として、Hyper-V の統合サービスのデータ交換 (Key Value Pair (KVP) Exchange) サービスを利用した方法を考えてみました。データ交換サービスは、仮想マシン側の統合コンポーネントとの連携機能であり、ホスト側に仮想マシン側の OS のバージョンや IP アドレス情報、統合コンポーネントのバージョン情報などを提供するものです。以下のサイトが参考になると思います (参考にさせていただきました)。
フィールドSEあがりの安納です:【Hyper-V】統合サービスの「データ交換」ってなに?
フィールドSEあがりの安納です:【Hyper-V】統合サービスの「データ交換」ってなに? その2
Virtual PC Guy's Blog: Scripts to Inventory Virtual Machines [Hyper-V]
Hyper-V Script: Looking at KVP GuestIntrinsicExchangeItems
(2019/3/25 KVP 取得スクリプト kvptest.ps1/.vbs のサンプルはこちらにアーカイブ)
データ交換サービスでやり取りされるゲスト OS の情報は、ホスト側から WMI 経由で取得することができます (仮想マシンのネットワーク接続の有無とは関係なく取得できます)。データが XML 形式であるため、サンプル コードは XML を扱いやすい PowerShell のものがほとんどですが、Windows Script Host (VBScript) で書いてみました。hvvminfo.vbs は、データ交換サービスが提供するすべての情報を出力します。
[hvvminfo.vbs]
Cscript hvvminfo.vbs "仮想マシン名" でゲスト OS の情報を KVP から取得します。
Option Explicit
Dim arg, targetVM, WMIService, VMs, InputKey
Dim KVP, xml, guestData, xpath, node, guestDataStr
If Right((LCase(WScript.FullName)),11) <> "cscript.exe" then
WScript.Echo "このスクリプトはCSCRIPT.EXEを使用して実行して下さい。"
WScript.Quit
End if
Set arg = WScript.Arguments
if arg.Count = 0 then
WScript.StdOut.Write "仮想マシン名を入力して下さい: "
InputKey = Trim(WScript.StdIn.ReadLine)
If InputKey <> "" then
targetVM = InputKey
Else
WScript.Echo "仮想マシン名が入力されませんでした。中止します。"
WScript.Quit
End If
else
targetVM = arg(0)
end if
Set WMIService = GetObject("winmgmts:\\.\root\virtualization")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
if VMs.Count < 1 then
WScript.Echo targetVM & " が見つかりません。"
WScript.Quit
end if
Select Case VMs.ItemIndex(0).EnabledState
Case 2 '実行中
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
Set xml = CreateObject("Microsoft.XMLDOM")
xml.async = false
guestDataStr = ""
for each guestData in KVP.GuestIntrinsicExchangeItems
xml.loadXML(guestData)
xpath = "/INSTANCE/PROPERTY[@NAME='Name']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
guestDataStr = guestDataStr & node.Text & ": "
xpath = "/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
if node is nothing then
guestDataStr = guestDataStr & "---" & vbCrLf
else
guestDataStr = guestDataStr & node.Text & vbCrLf
end if
Set node = nothing
next
WScript.Echo WScript.Echo "仮想マシン名 " & targetVM & " のゲスト OS の情報"
WScript.Echo "------------------------------------------------------------------------------"
if guestDataStr <> "" then
WScript.Echo guestDataStr
WScript.Echo
else
WScript.Echo targetVM & " は完全に起動していないか、ゲスト コンポーネントが動作していません。"
end if
Case Else
WScript.Echo targetVM & " は実行中でないため、ゲスト OS の情報を取得できません。"
End Select
Dim arg, targetVM, WMIService, VMs, InputKey
Dim KVP, xml, guestData, xpath, node, guestDataStr
If Right((LCase(WScript.FullName)),11) <> "cscript.exe" then
WScript.Echo "このスクリプトはCSCRIPT.EXEを使用して実行して下さい。"
WScript.Quit
End if
Set arg = WScript.Arguments
if arg.Count = 0 then
WScript.StdOut.Write "仮想マシン名を入力して下さい: "
InputKey = Trim(WScript.StdIn.ReadLine)
If InputKey <> "" then
targetVM = InputKey
Else
WScript.Echo "仮想マシン名が入力されませんでした。中止します。"
WScript.Quit
End If
else
targetVM = arg(0)
end if
Set WMIService = GetObject("winmgmts:\\.\root\virtualization")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
if VMs.Count < 1 then
WScript.Echo targetVM & " が見つかりません。"
WScript.Quit
end if
Select Case VMs.ItemIndex(0).EnabledState
Case 2 '実行中
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
Set xml = CreateObject("Microsoft.XMLDOM")
xml.async = false
guestDataStr = ""
for each guestData in KVP.GuestIntrinsicExchangeItems
xml.loadXML(guestData)
xpath = "/INSTANCE/PROPERTY[@NAME='Name']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
guestDataStr = guestDataStr & node.Text & ": "
xpath = "/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
if node is nothing then
guestDataStr = guestDataStr & "---" & vbCrLf
else
guestDataStr = guestDataStr & node.Text & vbCrLf
end if
Set node = nothing
next
WScript.Echo WScript.Echo "仮想マシン名 " & targetVM & " のゲスト OS の情報"
WScript.Echo "------------------------------------------------------------------------------"
if guestDataStr <> "" then
WScript.Echo guestDataStr
WScript.Echo
else
WScript.Echo targetVM & " は完全に起動していないか、ゲスト コンポーネントが動作していません。"
end if
Case Else
WScript.Echo targetVM & " は実行中でないため、ゲスト OS の情報を取得できません。"
End Select
実行例:
上のスクリプトを応用して、仮想マシンの開始から、ゲスト OS の起動完了までを監視しようとしているのが、次の hvvmstart2up.vbs です。統合コンポーネントがインストールされた、Hyper-V サポート対象のゲスト OS がインストールされている仮想マシンで、データ交換サービスが有効になっていることを前提としたものです。
[hvvmstart2up.vbs]
Cscript hvvmstart2up.vbs "仮想マシン名" でゲスト OS の起動完了までを監視します。
Option Explicit
Dim arg, targetVM, WMIService, VMs, InputKey
Dim KVP, xml, guestData, xpath, node, isUp
If Right((LCase(WScript.FullName)),11) <> "cscript.exe" then
WScript.Echo "このスクリプトはCSCRIPT.EXEを使用して実行して下さい。"
WScript.Quit
End if
Set arg = WScript.Arguments
if arg.Count = 0 then
WScript.StdOut.Write "開始する仮想マシン名を入力して下さい: "
InputKey = Trim(WScript.StdIn.ReadLine)
If InputKey <> "" then
targetVM = InputKey
Else
WScript.Echo "仮想マシン名が入力されませんでした。中止します。"
WScript.Quit
End If
else
targetVM = arg(0)
end if
Set WMIService = GetObject("winmgmts:\\.\root\virtualization")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
End Select
isUp = False
Do while isUp <> True
WScript.StdOut.Write(".")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
Set xml = CreateObject("Microsoft.XMLDOM")
xml.async = false
for each guestData in KVP.GuestIntrinsicExchangeItems
isUp = True
xml.loadXML(guestData)
xpath = "/INSTANCE/PROPERTY[@NAME='Name']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
if node.Text = "OSName" then
xpath = "/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
WScript.Echo ".. ゲスト OS " & node.Text & " の起動が完了しました。"
end if
Set node = nothing
next
WScript.sleep 100
Loop
Dim arg, targetVM, WMIService, VMs, InputKey
Dim KVP, xml, guestData, xpath, node, isUp
If Right((LCase(WScript.FullName)),11) <> "cscript.exe" then
WScript.Echo "このスクリプトはCSCRIPT.EXEを使用して実行して下さい。"
WScript.Quit
End if
Set arg = WScript.Arguments
if arg.Count = 0 then
WScript.StdOut.Write "開始する仮想マシン名を入力して下さい: "
InputKey = Trim(WScript.StdIn.ReadLine)
If InputKey <> "" then
targetVM = InputKey
Else
WScript.Echo "仮想マシン名が入力されませんでした。中止します。"
WScript.Quit
End If
else
targetVM = arg(0)
end if
Set WMIService = GetObject("winmgmts:\\.\root\virtualization")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Select Case VMs.ItemIndex(0).EnabledState
Case 3, 32768, 32769
WScript.StdOut.Write targetVM & " を開始します"
VMs.ItemIndex(0).RequestStateChange(2)
Do while VMs.ItemIndex(0).EnabledState > 2
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
WScript.StdOut.Write(".")
WScript.sleep 100
Loop
'WScript.Echo "完了"
Case 2
WScript.StdOut.Write targetVM & " は実行中です。"
Case Else
WScript.Echo targetVM & " を開始できません。"
WScript.QuitEnd Select
isUp = False
Do while isUp <> True
WScript.StdOut.Write(".")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
Set xml = CreateObject("Microsoft.XMLDOM")
xml.async = false
for each guestData in KVP.GuestIntrinsicExchangeItems
isUp = True
xml.loadXML(guestData)
xpath = "/INSTANCE/PROPERTY[@NAME='Name']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
if node.Text = "OSName" then
xpath = "/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child:text()"
set node = xml.selectSingleNode(xpath)
WScript.Echo ".. ゲスト OS " & node.Text & " の起動が完了しました。"
end if
Set node = nothing
next
WScript.sleep 100
Loop
実行例:
KVP の振る舞いをいろいろと試してみたところ、仮想マシンを起動した直後から Msvm_KvpExchangeComponent クラスに対するクエリー(WQL) は、KVP オブジェクトを返すようです。ゲスト OS 側でデータ交換サービスが利用可能かどうかには関係なくです。仮想マシンが起動していない場合、つまり Msvm_ComputerSystem クラスにある EnabledState が 2 以外の場合は、「SWbemObjectSet: 無効なパラメーターです」のエラーを返します。
仮想マシンの統合サービスのプロパティで「データ交換」が有効になっている場合、KVP オブジェクトの GuestIntrinsicExchangeItems は何も返さないか、ゲスト OS の情報を XML データとして返します。「データ交換」が無効になっている場合は、GuestIntrinsicExchangeItems は「Microsoft VBScript 実行時エラー: オブジェクトがコレクションではありません」というエラーを返します。そして、ゲスト OS の情報が返ってくるのは、ゲスト OS の統合サービスの 1 つである 「Hyper-V Data Exchange Service」が開始し、動作している間になります。
このスクリプトは、統合サービス コンポーネントが存在しないか、正常に動作しない場合、無限ループになるという問題があります。起動が途中でストップしている場合も、このスクリプトは無限ループになります。統合サービスにデータ交換サービスが含まれない、Linux ゲストも無限ループになります。まだまだ課題は多いですが、何とか使えそうです。
よりシンプルに、タイムアウトもプラス
hvvmstart2up.vbs は、XML データから OS の情報 (OSName) を取得するために、コードがちょっと複雑になっています。起動したことだけが分かればよいので、Do Loop の XML データの加工部分はすべてカットしてシンプルにできます。
[hvvmstart2up.vbs(シンプル版)]
isUp = False
Do while isUp <> True
WScript.StdOut.Write(".")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
for each guestData in KVP.GuestIntrinsicExchangeItems
isUp = True
next
WScript.sleep 100
Loop
Do while isUp <> True
WScript.StdOut.Write(".")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
for each guestData in KVP.GuestIntrinsicExchangeItems
isUp = True
next
WScript.sleep 100
Loop
無限ループ対策の方法の 1 つは、タイムアウトによる終了です。次の例は、5 分 (300 秒) たったら、スクリプトを終了します。
[hvvmstart2up.vbs(タイムアウト付)]
Dim StartTime
Const TIMEOUT = 300 '秒
StartTime = Timer()
isUp = False
Do while isUp <> True
if TIMEOUT < (Timer - StartTime) then
WScript.Echo "ゲスト OS の起動を確認する前にタイムアウト(" & TIMEOUT & " 秒)しました。監視を終了します。"
WScript.Quit
end if
WScript.StdOut.Write(".")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
for each guestData in KVP.GuestIntrinsicExchangeItems
isUp = True
next
WScript.sleep 100
Loop
Const TIMEOUT = 300 '秒
StartTime = Timer()
isUp = False
Do while isUp <> True
if TIMEOUT < (Timer - StartTime) then
WScript.Echo "ゲスト OS の起動を確認する前にタイムアウト(" & TIMEOUT & " 秒)しました。監視を終了します。"
WScript.Quit
end if
WScript.StdOut.Write(".")
Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
Set KVP = (VMs.ItemIndex(0).Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0)
for each guestData in KVP.GuestIntrinsicExchangeItems
isUp = True
next
WScript.sleep 100
Loop
その他の方法
ゲスト OS の起動完了を確認する方法は、他にもいろいろと考えられます。例えば...
- ゲスト OS のスタートアップ スクリプトを利用して、特定の場所にファイルを作成し、そのファイルのタイムスタンプをホスト側から監視する。
- イベント ログのサブスクリプション機能 (Windows Vista 以降、Windows Server 2008 以降で利用可能) とタスクを組み合わせて、ゲスト OS の特定イベントを監視する。
- システム監視ツールの機能を利用する。
Windows Server 2012 R2以降のHyper-Vの場合は、root\virtualizationの部分をroot\virtualization\v2に変更する必要があります。
返信削除