今抱えている課題は、テストや評価のために Hyper-V ホスト上に作成した多数の仮想マシンの更新 (Windows Update) を自動化できないかということ。前回の投稿でゲスト OS の起動完了をホスト側からスクリプトで監視することも、この課題に関係しています。
仮想マシン A を開始 → Windows Update 実行 → ゲスト OS 再起動 → 仮想マシン A をシャットダウン → 仮想マシン B を開始 → Windows Update を実行...
という一連の作業 (現在は手作業) をスクリプトで自動化したいと考えています。
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'")
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
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'")
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
(2020/11/11 修正:updateSearcher.Search("IsInstalled=0 and Type='Software' and AutoSelectOnWebSites=1") → updateSearcher.Search("IsInstalled=0 and Type='Software'") に再変更。理由は Windows 10/Server 2016+ の重要な更新が AutoSelectOnWebSites=True(1) とは限らないから)
このスクリプトは、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
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 を作ってみます。
大変貴重な情報をありがとうございます。
返信削除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を実行するのは危険なのでしょうか?
ごめんなさい。そこまでは承知していません。ご自身で検証、評価してみてください。
返信削除有益な情報を掲載いただきありがとうございます。
返信削除こちらWindows Server 2016上で実行したところ、ナゼか途中で落ちているようです。
具体的には、検出で5件、ダウンロードで5件出て、そのあとのインストール完了表示では4件出たところでVBSのほうからは抜けてしまい、そのあとの処理は実行していませんでした。
とりあえず完了表示のループの問題?かと思いその部分(「詳細:」のブロック)をばっさりカットしたら問題なく動作しましたので、とりいそぎお知らせします。