2010/07/07

Windows & Hyper-V Scripting: Windows Update をスクリプトから ( ゲスト OS のスタートアップに仕込む)

Windows Update Agent (WUA) API は、リモートはお嫌いのようなので、初心(!?)に返って、昔ながらの手法でやってみようと思います。仮想マシンのコンピューターのスタートアップ スクリプトに WindowsUpdate.vbs (WindowsUpdate.vbs 内のシャットダウンや再起動コードは削除) を仕込んでおき、ローカルで実行させる方法です。


仮想マシンが起動するたびに、WindowsUpdate.vbs が毎回実行されるのは避けたいので、共有フォルダーに特定のファイルが存在する場合にのみ、Windows Update.vbs を実行するようにしました。多数の仮想マシンの更新を連続して、シリアルに行うというのが目的なので、Windows Update.vbs で更新後、再起動の必要性の有無に関係なく、仮想マシンをシャットダウンするようなバッチを組みました。また、更新状況を追跡するために、WindowsUpdate.vbs の標準出力を共有フォルダー上のログ ファイルにリダイレクトします。そのため、共有フォルダーには「Everyone:変更」または「コンピューター アカウント: 変更」の権限が必要です。
1 台の仮想マシンのスタートアップ スクリプトにバッチを仕込み、バッチが期待どうりに動作することを確認します。どうやらうまくいったようです。

バッチをもう少し工夫してみます。同じ日に 2 回実行されることがないようにしてみます。いまのままだと、共有フォルダーにファイルが存在する限り、シャットダウンしてしまうからです。

次の checkupdate.cmd が、ゲストのスタートアップ スクリプトに仕込むバッチ ファイルの例です。共有フォルダー上に WindowsUpdate_コンピューター名_YYMMDD.log というファイルが存在する場合は、その日に WindowsUpdate.vbs を実行済みということで、スルーするようにしました。

[checkupdate.cmd (ゲスト OS 側)]
@echo off
SET dt=%date:~-10%
IF EXIST \\SERVERNAME\SHARENAME\patchtuesday.txt GOTO UPDATESOON
GOTO END

:UPDATESOON
IF EXIST \\SERVERNAME\SHARENAME\WindowsUpdate-%COMPUTERNAME%-%dt:~0,4%%dt:~5,2%%dt:~8,2%.log GOTO END
cscript WindowsUpdate.vbs //NoLogo >> \\SERVERNAME\SHARENAME\WindowsUpdate-%COMPUTERNAME%-%dt:~0,4%%dt:~5,2%%dt:~8,2%.log
shutdown /s /t 0

:END

ホスト側のバッチ ファイルは次の updatevm.cmd のようにしました。最初に共有フォルダー上に WindowsUpdate.vbs をキックするためのファイルを作成し、すべての仮想マシンを処理したあとにファイルを削除して終了します。hvvmup2down.vbs は、オフラインの仮想マシンをシリアルに次々に更新していくために、仮想マシンを起動して、更新後、シャットダウンするまでを監視するスクリプトです。

[updatevm.cmd (ホスト OS 側)]
@echo off
echo PatchNow > \\SERVERNAME\SHARENAME\patchtuesday.txt
cscript hvvmup2down.vbs VirtualMachineName1
cscript hvvmup2down.vbs VirtualMachineName2
cscript hvvmup2down.vbs VirtualMachineName3
cscript hvvmup2down.vbs VirtualMachineName4
cscript hvvmup2down.vbs VirtualMachineName5
del \\SERVERNAME\SHARENAME\patchtuesday.txt

hvvmup2down.vbs の役割は、仮想マシンを起動して、シャットダウンするまでを監視することですが、仮想マシンが一時停止または保存状態のことも考慮しなければなりません。一時停止または保存状態の仮想マシンは、すでに起動が完了している可能性があるり、復帰時にスタートアップ スクリプトが実行されないかもしれないからです。そこで、仮想マシンが一時停止または保存状態の場合は、仮想マシンを開始後に、1 分待って、シャットダウンを開始し、シャットダウンしたらもう一度起動するようにスクリプトを組んでいます。

[hvvmup2down.vbs (ホスト OS 側)]
Option Explicit
Dim arg, targetVM, WMIService, VMs, InputKey,timeInterval, VMshut, VMshutRet

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

timeInterval = 1000 '1秒
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
 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(timeInterval)
 Loop
 WScript.Echo "..完了。"
Case 32768, 32769 '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(timeInterval)
 Loop
 WScript.Echo "..完了。"

 timeInterval = 60000 '1分
 WScript.sleep(timeInterval)

 WScript.StdOut.Write targetVM & " のシャットダウンを開始します "
 Set VMshut = WMIService.ExecQuery("SELECT * FROM Msvm_ShutdownComponent WHERE  SystemName='" & VMs.ItemIndex(0).Name & "'")
 VMshutRet = VMshut.ItemIndex(0).InitiateShutdown(True,"shutdown by script")
 if VMshutRet <> 0 then
  WScript.Echo "... 失敗しました。監視を終了します。"
  WScript.Quit
 end if
 timeInterval = 10000 '10秒
 Do while VMs.ItemIndex(0).EnabledState <> 3
  Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
  WScript.StdOut.Write(".")
  WScript.sleep(timeInterval)
 Loop
 WScript.StdOut.Write vbCrLF & 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(timeInterval)
 Loop
 WScript.Echo "..完了。"
Case 2
 WScript.Echo targetVM & " は既に動作しているため、監視対象外です。"
 WScript.Quit
Case Else
 WScript.Echo targetVM & " を開始できません。監視を終了します。"
 WScript.Quit
End Select

WScript.Echo "仮想マシンが停止するまで待機します..."

timeInterval = 600000 '5分
Do while timeInterval <> 0
 WScript.Sleep(timeInterval)
 Set VMs = WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & targetVM & "'")
 Select Case VMs.ItemIndex(0).EnabledState
 Case 3
  WScript.Echo targetVM & " は停止しました(シャットダウン)。"
  timeInterval = 0
 Case 32769
  WScript.Echo targetVM & " は停止しました(保存)。"
  timeInterval = 0
 Case 32773
  WScript.Echo targetVM & " は保存中です..."
  timeInterval = 5000
  Case 32774
 WScript.Echo targetVM & " は停止です..."
  timeInterval = 5000
 Case Else
  timeInterval = 60000
 End Select
Loop

これでようやく、我が家の Hyper-V テスト環境で仮想マシンをバッチ的に更新できるようになりました。

0 件のコメント: