2010/07/02

Windows Scripting: Windows Update をスクリプトから ( WindowsUpdate.vbs )

最近、久しぶりにスクリプトづいています。なぜかというと、やりたいことがあるからです。

今抱えている課題は、テストや評価のために Hyper-V ホスト上に作成した多数の仮想マシンの更新 (Windows Update) を自動化できないかということ。前回の投稿でゲスト OS の起動完了をホスト側からスクリプトで監視することも、この課題に関係しています。

仮想マシン A を開始 → Windows Update 実行 → ゲスト OS 再起動 → 仮想マシン A をシャットダウン → 仮想マシン B を開始 → Windows Update を実行...

という一連の作業 (現在は手作業) をスクリプトで自動化したいと考えています。うまくいったら、COMPUTERWORLD.JP のブログでまとめるつもり。(←大人の事情でもう存在しません) こちらのブログでは、試行錯誤の痕跡を残しておきます。

Windows Update の処理 (更新プログラムの検索、ダウンロード、インストール) をスクリプト化するには、Windows Update Agent (WUA) API という COM インターフェイスを使用します。サンプルコードは、インターネットを検索すればいろいろと出てくるでしょうが、そのすべてはおそらく MSDN のサイトにある WUA_SearchDownloadInstall.vbs がもとになっていると思います。

Windows Update Agent API

http://msdn.microsoft.com/en-us/library/aa387099(v=VS.85).aspx
Searching, Downloading, and Installing Updates (WUA_SearchDownloadInstall.vbs)

http://msdn.microsoft.com/en-us/library/aa387102.aspx

WUA_SearchDownloadInstall.vbs はコピペするだけでちゃんと動きますが、インストールするかどうかの Yes/no が含まれていたり、検索対象がすべてのソフトウェア (ドライバーは除く) だったりと、更新の自動化という目的のためには改良が必要です。

以下の windowsupdate.vbs は、私の目的のために改良したバージョンになります。検索対象として、Windows Update サイト上でインストールが推奨される更新プログラム (AutoSelectOnWebSites=1) を条件に追加しています。また、ユーザーの対話を必要とせず、ノンストップで検索、ダウンロード、インストールまで実行します。更新プログラムが再起動が必要な場合は、即座に再起動(またはシャットダウン)を開始します。ついでに、メッセージも日本語化してみました。

[windowsupdate.vbs]
Option Explicit
Dim updateSession, updateSearcher, update, searchResult, downloader, updatesToDownload, updatesToInstall, installer, installationResult, InputKey, i

If Right((LCase(WScript.FullName)),11) <> "cscript.exe" then
 WScript.Echo "このスクリプトはCSCRIPT.EXEを使用して実行して下さい。" & _
  vbCRLF & "例: cscript WindowsUpdate.vbs"
 WScript.Quit(0)
End if

WScript.Echo "------------------------------"
WScript.Echo "Windows Update"
WScript.Echo "------------------------------"
WScript.Echo "更新プログラムを確認しています..."
Set updateSession = CreateObject("Microsoft.Update.Session")
Set updateSearcher = updateSession.CreateupdateSearcher()
Set searchResult = _
updateSearcher.Search("IsInstalled=0 and Type='Software' and AutoSelectOnWebSites=1")
For i = 0 To searchResult.Updates.Count-1
 Set update = searchResult.Updates.Item(i)
 WScript.Echo i + 1 & vbTab & update.Title
Next

If searchResult.Updates.Count = 0 Then
 WScript.Echo "利用可能な更新プログラムはありません。Windowsは最新の状態です。"
 WScript.Quit(0)
Else
 WScript.Echo searchResult.Updates.Count & _
  " 個の更新プログラムを検出しました。ダウンロードを開始します。"
End If

WScript.StdOut.Write "ダウンロードの準備をしています..."
Set updatesToDownload = CreateObject("Microsoft.Update.UpdateColl")
For i = 0 to searchResult.Updates.Count-1
 Set update = searchResult.Updates.Item(i)
 WScript.StdOut.Write "."
 updatesToDownload.Add(update)
Next

WScript.Echo vbCRLF & "更新プログラムをダウンロードしています..."
Set downloader = updateSession.CreateUpdateDownloader()
downloader.Updates = updatesToDownload
downloader.Download()

WScript.Echo "以下の更新プログラムのダウンロードが完了しました。"
For i = 0 To searchResult.Updates.Count-1
 Set update = searchResult.Updates.Item(i)
 If update.IsDownloaded Then
  WScript.Echo i + 1 & vbTab & update.Title
 End If
Next

Set updatesToInstall = CreateObject("Microsoft.Update.UpdateColl")
WScript.StdOut.Write "インストールの準備をしています..."
For i = 0 To searchResult.Updates.Count-1
 set update = searchResult.Updates.Item(i)
 If update.IsDownloaded = true Then
  WScript.StdOut.Write "."
  updatesToInstall.Add(update)
 End If
Next

WScript.Echo vbCRLF & "更新プログラムをインストールしています..."
Set installer = updateSession.CreateUpdateInstaller()
installer.Updates = updatesToInstall
Set installationResult = installer.Install()

if installationResult.ResultCode = 2 then
 WScript.Echo "インストールは正常に完了しました。"
Else
 WScript.Echo "一部の更新プログラムをインストールできませんでした。"
End If
WScript.Echo "詳細:"
For i = 0 to updatesToInstall.Count - 1
 WScript.StdOut.Write i + 1 & vbTab & _
 updatesToInstall.Item(i).Title
 If installationResult.GetUpdateResult(i).ResultCode = 2 then
  WScript.Echo ":成功"
 Else
  WScript.Echo ":失敗"
 End If
Next
WScript.StdOut.Write "再起動の必要性: "
if installationResult.RebootRequired then
 WScript.Echo "必要"
 WScript.Echo "!重要な更新プログラムのインストールを完了するためコンピュータを再起動します。"
Else
 WScript.Echo "不要"
End if

'WScript.StdOut.Write vbCRLF & "続行するには何かキーを押してください:"
'InputKey = WScript.StdIn.Readline
if installationResult.RebootRequired then
 ' 再起動またはシャットダウンのためのコード(はじまり)
 Dim objWMIService, colOperatingSystems, ObjOperatingSystem
 Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate,(Shutdown)}!\\.\root\cimv2")

 Set colOperatingSystems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem")
 For Each objOperatingSystem in colOperatingSystems
  ObjOperatingSystem.Reboot() '再起動する場合
  'ObjOperatingSystem.Win32Shutdown(1)  'シャットダウンする場合
 Next
 ' 再起動またはシャットダウンのためのコード(おわり)

 WScript.Quit(-1)
Else
 WScript.Quit(0)
End If

このスクリプトは、WSUS (Windows Server Update Services) に対応した OS であればたぶん問題なく動作します。ただし、管理者権限するのを忘れずに。

再起動やシャットダウンのためのコードは、WindowsUpdate.vbs 内に含めないほうが汎用性が高まるかもしれません。このスクリプトは、再起動が必要な場合はエラー コード -1 (WScript.Quit (-1))を、不要な場合はエラー コード 0 (WScript.Quit (0)) を返して終了するようにしてあるので、次のようなバッチファイルでスクリプト外から制御することもできます(WindowsUpdate.vbs から再起動のためのコードは削除すること)。

[windowsupdate.cmd]
@echo off
cscript WindowsUpdate.vbs
if %errorlevel% == -1 goto REBOOTNOW
goto END
:REBOOTNOW
echo 今すぐコンピューターを再起動します。
shutdown /r /t 0
:END

WUA API を利用したスクリプトの難点は、ローカル コンピューターにしか対応していないことです。CreateObject("Microsoft.Update.Session", "コンピューター名または IP アドレス") を指定することでリモート コンピューターの WUA セッションに接続することはできます。検索の可能です。しかし、ダウンロード以降はエラーになります。右のスクリーンショットは、その方法でやってみたときのものです。

以下のサイトを見る限り、ダウンロードやインストールはリモートからは実行できないようです。

Using WUA From a Remote Computer

http://msdn.microsoft.com/en-us/library/aa387288.aspx

PowerShell なら何とかなるかもしれません。PowerShell 2.0 では PowerShell Remoting が使えます。というわけで、次回は、WindowsUpdate.vbs を、PowerShell スクリプト 化して WindowsUpdate.ps1 を作ってみます。

4 件のコメント:

匿名 さんのコメント...

大変貴重な情報をありがとうございます。
wup.vbsというファイル名でbatから実行した
結果・・・以下の画面になりました。
何が問題なのでしょうか?
OSは、Win7pro SP1 32bitです。
アドバイスいただけますか?
------------------------
Windows Update
------------------------
更新プログラムを確認しています。
wup.vbs(16,1) (null): 0x80072EE6

山市 良 さんのコメント...

WSUS 0x80072EE6 で検索してみるとよいかと。あと、通常の Windows Update を実行して問題ないかどうか確認したらいかがでしょうか。

匿名 さんのコメント...

コメントありがとうございます。別のPCで確認した所正常に動作できました。
 ところでこのBATをタスクスケジューラに登録して、「ユーザーがログオンしているかどうかにかかわらず実行する」「最上位の特権で実行する」を実行すれば、PC電源ON後、ログオン画面のままでWindowsUpdateを実行は可能でしょうか?実際に何度かテストしましたが、WindowsUpdateが実行できているようですが、一度ブルーバックのエラーになってしまいました。ログオン前にタスクスケジューラ―でWindowsUpdateを実行するのは危険なのでしょうか?

山市 良 さんのコメント...

ごめんなさい。そこまでは承知していません。ご自身で検証、評価してみてください。