2010/07/01

Hyper-V Scripting: “実行中”の仮想マシン=ゲスト OS が“実行中”ではない

ここ最近、Hyper-V の仮想マシンのゲスト OS が立ち上がっているかどうかをどうやって調べればよいか悩んでいます。仮想マシンを起動すると、状態が“実行中”になります。この仮想マシンの状態は前回の投稿で紹介したスクリプトのように、Hyper-V WMI Provider (http://msdn.microsoft.com/en-us/library/cc136992(VS.85).aspxhttps://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/virtual/windows-virtualization-portal) の Msvm_ComputerSystem クラスにある EnabledState プロパティで取得できます。しかし、EnabledState プロパティでは、仮想マシンがオンであることがわかっても、ゲスト OS の状態まではわかりません。たとえば、OS なしの仮想マシンや起動障害のある仮想マシンが途中で止まっていたとしても、 EnabledState プロパティは実行中 (2) を示します。

KVP のデータを取得する WSH サンプル スクリプト

1 つの解決策として、Hyper-V の統合サービスのデータ交換 (Key Value Pair (KVP) Exchange) サービスを利用した方法を考えてみました。データ交換サービスは、仮想マシン側の統合コンポーネントとの連携機能であり、ホスト側に仮想マシン側の OS のバージョンや IP アドレス情報、統合コンポーネントのバージョン情報などを提供するものです。以下のサイトが参考になると思います (参考にさせていただきました)。

フィールドSEあがりの安納です:【Hyper-V】統合サービスの「データ交換」ってなに? 
http://blogs.technet.com/b/junichia/archive/2009/06/12/3253986.aspx
フィールドSEあがりの安納です:【Hyper-V】統合サービスの「データ交換」ってなに? その2
http://blogs.technet.com/b/junichia/archive/2009/06/12/3253869.aspx
Virtual PC Guy's Blog: Scripts to Inventory Virtual Machines [Hyper-V]
http://blogs.msdn.com/b/virtual_pc_guy/archive/2010/04/14/scripts-to-inventory-virtual-machines-hyper-v.aspx
Hyper-V Script: Looking at KVP GuestIntrinsicExchangeItems 
http://blogs.msdn.com/b/virtual_pc_guy/archive/2008/11/18/hyper-v-script-looking-at-kvp-guestintrinsicexchangeitems.aspx
(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
実行例:

KVP のデータが取得できれば、ゲスト OS が起動しているはず

上のスクリプトを応用して、仮想マシンの開始から、ゲスト 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 & "'")

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.Quit
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
実行例:

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

無限ループ対策の方法の 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

その他の方法
ゲスト OS の起動完了を確認する方法は、他にもいろいろと考えられます。例えば...
  • ゲスト OS のスタートアップ スクリプトを利用して、特定の場所にファイルを作成し、そのファイルのタイムスタンプをホスト側から監視する。
  • イベント ログのサブスクリプション機能 (Windows Vista 以降、Windows Server 2008 以降で利用可能) とタスクを組み合わせて、ゲスト OS の特定イベントを監視する。
  • システム監視ツールの機能を利用する。
今回の KVP を利用する方法が良い点は、仮想マシンのネットワーク接続を使用しないというところです。たとえば、ネットワーク アダプターの割り当ての無い仮想マシンや、閉じたプライベート ネットワークに接続された仮想マシンからでも KVP データはホスト経由で取得できます。

1 件のコメント:

  1. Windows Server 2012 R2以降のHyper-Vの場合は、root\virtualizationの部分をroot\virtualization\v2に変更する必要があります。

    返信削除

注: コメントを投稿できるのは、このブログのメンバーだけです。