2010/10/12

VHD オフライン パッチ スクリプト: patch2vhd.vbs

自作スクリプト patch2vhd.vbs は、Windows 7 および Windows Server 2008 R2 の標準コマンド (DISKPART および DISM) を利用して、仮想マシン (Windows 7 または Windows Server 2008 R2 ゲストのみ対応) の VHD に更新プログラムをオフラインでインストールスクリプトです。使い方など詳しくは、COMPUTERWORLD BLOG の本日の投稿をご覧ください。

なお、このスクリプトは一般向きではありません。DISKPART と DISM によるオフラインパッチを手作業で (マニュアルで) できるという人が、それを自動化したい場合に限り参考にしてください。スクリプトを眺めるだけでも、いろいろと勉強になるところがあるかもしれません (サブフォルダーのための再帰とか)。

[patch2vhd.vbs]

Option Explicit
Const HKEY_LOCAL_MACHINE = &H80000002
Dim arg, InputKey, targetVHD
Dim objShell, objExec, strCmd
Dim objFSO, objTextFile, strText, outMsgs, outMsg, invalidVHD, VHDDiskID, VHDDriveLetter
Dim objRegistry, strKeyPath, strValueName, strValue, winVer
Dim objFolder, objFile, objSubFolder, strExt

' VHD ファイルの指定
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 "VHD のパスを入力して下さい: "
  InputKey = Trim(WScript.StdIn.ReadLine)
  If InputKey <> "" then
  targetVHD = InputKey
  Else
    WScript.Echo "VHD のパスが入力されませんでした。中止します。"
    WScript.Quit
  End If
else
  targetVHD = arg(0)
end if

' DISKPART 用スクリプトの生成
WScript.Echo "VHD 接続の準備をしています..."
Set objShell = CreateObject("WScript.Shell")
strCmd = "echo select vdisk file = """ & targetVHD & """ > AttachVHDScript.txt & echo attach vdisk >> AttachVHDScript.txt & echo detail vdisk >> AttachVHDScript.txt"
Set objExec = objShell.Exec("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop
strCmd = "echo select vdisk file = """ & targetVHD & """ > DetachVHDScript.txt & echo detach vdisk >> DetachVHDScript.txt"
Set objExec = objShell.Exec("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop

' VHD の接続
WScript.Echo "VHD を接続しています..."
strCmd = "DISKPART /s AttachVHDScript.txt > VHDDiskID.txt"
Set objExec = objShell.Exec ("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop

' ディスク番号の取得
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile ("VHDDiskID.txt", 1)
strText = objTextFile.ReadAll
objTextFile.Close
invalidVHD = true
outMsgs = Split(strText, vbCrLf)
For each outMsg in outMsgs
  If Instr(outMsg,"DiskPart により、仮想ディスク ファイルがアタッチされました。") > 0 then
    invalidVHD = false
    WScript.StdOut.Write "VHD を接続しました。ファイル名: " & targetVHD
    Exit For
  End If
Next
If invalidVHD then
  WScript.Echo "VHD を接続できませんでした。中止します。"
  WScript.Quit
End if
invalidVHD = true
For each outMsg in outMsgs
  If Instr(outMsg,"関連付けられたディスク番号:") > 0 then
    invalidVHD = false
    VHDDiskID = Replace(outMsg,"関連付けられたディスク番号: ","")
    WScript.Echo " ディスク番号: " & VHDDiskID
    Exit For
  End If
Next
If invalidVHD then
  WScript.Echo " ディスク番号: 不明"
  WScript.Echo "ディスク番号を取得できなかったので中止します。VHD は切断されていません。「ディスクの管理」を使用して切断してください。"
  WScript.Quit
End if

' ボリューム情報の取得
WScript.Echo "VHD のボリューム情報を取得しています..."
strCmd = "echo select disk " & VHDDiskID & " > DetailDiskScript.txt & echo detail disk >> DetailDiskScript.txt"
Set objExec = objShell.Exec("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop

' ローカルドライブにマウントされるまでしばらく待つ
WScript.Sleep 10000

strCmd = "DISKPART /s DetailDiskScript.txt > DiskDetails.txt"
Set objExec = objShell.Exec ("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop

Set objTextFile = objFSO.OpenTextFile ("DiskDetails.txt", 1)
strText = objTextFile.ReadAll
objTextFile.Close
invalidVHD = true

outMsgs = Split(strText, vbCrLf)
For each outMsg in outMsgs
  If (Left(outMsg,8) = "  Volume") and (Mid(outMsg,15,3) <> "Ltr") then
    VHDDriveLetter = Mid(outMsg,15,1)
    WScript.StdOut.Write VHDDriveLetter & " ドライブを調べています。"
    If objFSO.FolderExists(VHDDriveLetter & ":\Windows") then
      WScript.Echo VHDDriveLetter & ":\Windows を確認しました。"
      invalidVHD = false
      Exit for
    Else
      WScript.Echo VHDDriveLetter & ":\Windows は存在しません。"
    End If
  End If
Next
If invalidVHD then
  WScript.Echo "Windows のインストールが見つかりません。中止します。VHD は切断されていません。「ディスクの管理」を使用して切断してください。"
  WScript.Quit
End if

' OS 情報を取得しています。
' Windows 7 or Windows Server 2008 R2
WScript.Echo "VHD イメージの Windows バージョンを取得しています..."
' ハイブのロード
strCmd = "REG LOAD HKLM\myvhd " & VHDDriveLetter & ":\Windows\System32\config\SOFTWARE"
Set objExec = objShell.Exec ("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop
' レジストリの読み取り
Set objRegistry = GetObject("winmgmts:\\.\root\default:StdRegProv")
strKeyPath = "myvhd\Microsoft\Windows NT\CurrentVersion"
strValueName = "CurrentVersion"
objRegistry.GetStringValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName, strValue
invalidVHD = true
If Not isNull(strValue) then
  If CSng(strValue) > 6 then     ' 6.1 以降
    invalidVHD = false
  End If
  winVer = strValue
  WScript.Echo "Windows のバージョン: " & winVer
Else
  WScript.Echo "Windows のバージョン: 不明"
End If
strValueName = "ProductName"
objRegistry.GetStringValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName, strValue
If Not isNull(strValue) then
  WScript.Echo "Windows の製品名: " & strValue
Else
  WScript.Echo "Windows の製品名: 不明"
End If

' ハイブのアンロード
strCmd = "REG UNLOAD HKLM\myvhd"
Set objExec = objShell.Exec ("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop
If invalidVHD then
  WScript.Echo "このバージョンの Windows は、オフライン パッチに対応していません。中止します。VHD は切断されていません。「ディスクの管理」を使用して切断してください。"
  WScript.Quit
End if

' DISM によるオフライン パッチ
WScript.Echo "更新プログラムをインストールします..."
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(".\Updates")
InjectUpdates(objFolder)

Sub InjectUpdates(objFolder)
For Each objFile In objFolder.Files
   strExt = objFSO.GetExtensionName(objFile.Path)
   If (strExt = "msu") or (strExt = "cab") then
     WScript.Echo "更新プログラム: " & objFile.Name
     strCmd = "DISM /Image:" & VHDDriveLetter & ":\ /Add-Package /PackagePath:""" & objFile.Path & """ >> InjectUpdatesOutput.txt"
     Set objExec = objShell.Exec ("%comspec% /c " & strCmd)
     Do While objExec.Status = 0    ' DISM コマンドの終了を待つ
       WScript.Sleep 1000
     Loop
   End If
Next
For Each objSubFolder In objFolder.SubFolders
   InjectUpdates(objSubFolder)
Next

End Sub

' VHD の切断
WScript.Echo "VHD を切断します。"
WScript.Sleep 5000
strCmd = "DISKPART /s DetachVHDScript.txt > DetachVHDOutput.txt"
Set objExec = objShell.Exec ("%comspec% /c " & strCmd)
Do While objExec.Status = 0
  WScript.Sleep 1000
Loop
WScript.Echo "終了しました。"

注意: スクリプトの実行が途中で中止またはエラーで終了した場合、VHD のマウントは解除されません。「ディスクの管理」スナップインを使用して、VHD を手動で切断してください。また、ローカルのレジストリの「HKEY_LOCAL_MACHINE\myvhd」にオフラインのレジストリ ハイブがロードされたままになる場合があります。これもレジストリ エディター (Regedit.exe) を使用して、手動でアンロードしてください。

注目: 「DISKPART 用スクリプトの生成」~「 OS 情報の取得」の直前までの処理は、VMST 3.0 の  VHDMountDetails.bat を参考にしています。VHDMountDetails.batでは、VHD 内のボリューム (パーティション) のうち、後ろの方のボリュームが Windows のドライブ文字として選択されるようになっています。そのため、VMST 3.0 の場合、Windows のインストール ボリュームより後ろの位置に、別のボリュームが存在する場合にパッチできないという問題が出るかもしれません。そこで、patch2vhd.vbs では、最初に Windows パスが見つかったボリュームを、Windows のドライブ文字として選択するようにしています。

0 件のコメント: