Edited at

PowerShell で Windows Update を実行する

More than 1 year has passed since last update.


概要

Windows サーバのセキュリティパッチ適用は手間のかかる作業です。サーバの台数にも依りますが、システム停止日に全てのサーバをアップデートするよう計画するためには、僅かな手間も省略したいところです。

そこで手間を省略するために Windows Update を PowerShell から実行する方法を検討しました(とりあえず PowerShell の流れ)。


方針

Windows Update Agent API を使います。これは COM として呼び出せるので New-Object -Com Microsoft.Update.Session することができます。後はMSDNのサンプルを書き換えるだけです。


手順

Windows サーバにログインして PowerShell を管理者権限で実行します。続いて、以下のスクリプトを全て貼り付けるとサーバの再起動まで自動で進行します。サーバ管理者は「更新プログラムの確認」やダウンロードを待つことなく、次のサーバの作業に移ることができます。

# 1. ログの記録を開始する

$log = ".\$($env:ComputerName)_$(date -f yyyyMMdd).txt"
Start-Transcript $log
$Env:ComputerName
$Env:UserName
date

# 2. Windows Update を確認するセッションを開始
$updateSession = New-Object -com Microsoft.Update.Session

# 3. Windows Update の検索
$searcher = $updateSession.CreateUpdateSearcher()
$searchResult = $searcher.search("IsInstalled=0 and Type='software'")

# 4. Windows Update の結果確認
$searchResult.Updates | % { $_.title -replace ".*(KB\d+).*", "`$1`t$&" }

# 5. 手作業が不要な項目だけを抽出する(通常は検索結果すべて)
$updatesToDownload = New-Object -com Microsoft.Update.UpdateColl
$searchResult.Updates | ? { -not $_.InstallationBehavior.CanRequestUserInput } | ? { $_.EulaAccepted } | % { [void]$updatesToDownload.add($_) }

# 6. ダウンロードする
$downloader = $updateSession.CreateUpdateDownloader()
$downloader.Updates = $updatesToDownload
$downloader.Download()

# 7. ダウンロードが完了したものだけ抽出(通常は全てダウンロードされる)
$updatesToInstall = New-Object -com Microsoft.Update.UpdateColl
$searchResult.Updates | ? { $_.IsDownloaded } | % { [void]$updatesToInstall.add($_) }

# 8. インストールする
$installer = $updateSession.CreateUpdateInstaller()
$installer.Updates = $updatesToInstall
$installationResult = $installer.Install()

# 9. インストールの結果を確認する(ResultCode 2 なら成功、3以上なら一部または全て失敗)
$installationResult

# 10. 再起動する(自動でStop-Transcriptされる)
date
restart-computer

再起動後のサーバにログインし、以下のコードを PowerShell に貼り付けて更新プログラムが無いことを確認しておきましょう。

# 11. 続きからログを記録する

$log = Get-Item .\$($env:ComputerName)_$(date -f yyyyMMdd).txt
Start-Transcript -append $log

# 12. Windows Update を確認するセッションを開始
$updateSession = New-Object -com Microsoft.Update.Session

# 13. Windows Update の検索
$searcher = $updateSession.CreateUpdateSearcher()
$searchResult = $searcher.search("IsInstalled=0 and Type='software'")

# 14. 検索結果の確認(結果が表示されたら 5 からリトライするか、手動で windows update を実行すること)
$searchResult.Updates | % { $_.title -replace '^(.*)\((KB\d+).*$', "`$2`t`$1" }

# 15. ロギングの停止
Stop-Transcript

最後にログを回収してください。

# 16. ログの回収

Set-Clipboard -Path $log

# 17. ログの削除
rm $log


心のこり

本当は Invoke-Command でやりたかったのですが、$downloader = $updateSession.CreateUpdateDownloader() でアクセス拒否エラーが発生するので諦めました。


追記:アクセス拒否エラーの回避策

以下のURLで公開されているモジュールを参考にしたところ、タスクスケジューラで実行するよう実装されていました。

https://gallery.technet.microsoft.com/scriptcenter/2d191bcd-3308-4edd-9de2-88dff796b0bc