以下の製品コラムでは、Job DirectorとWindows Update Agent(WUA) APIを使用して、Windows Serverの毎月の更新を自動化する活用例を紹介しました。
Windows Updateの更新を全自動化-Job Director R16活用例: おじさんのブログと連動
上記の製品コラムでは、Microsoft提供のサンプルスクリプト「WUA_SearchDownloadInstall.vbs」を少しだけ改良して、終了(EXIT)コードでインストールの成功失敗や再起動の要否をJob Directoryで判断させていましたが、このサンプルスクリプトを使用して更新プログラムのインストールを行うと、インストールは問題なく行われても、スクリプト全体が最終的にエラーコード「-1073741819(0xC0000005)」で異常終了する場合があることが確認されました。今回はこの問題に対する2つの回避策を紹介します。
WUA_SearchDownloadInstall.vbsのアクセス違反エラーとその影響
「-1073741819(0xC0000005)」は、Winodwsで発生することがあるアクセス違反(Access Violation)のエラーです。「WUA_SearchDownloadInstall.vbs」で更新プログラムがインストールされると、必ず再現するというわけではありませんが、このエラーが発生することがあることを確認しました(画面1)。このエラーが発生すると、イベントログや信頼性モニター(perfmon /rel)にアプリケーションエラーが記録されます(画面2)。ただし、このエラー発生したとしても、更新プログラムのインストールには影響はありません。「WUA_SearchDownloadInstall.vbs」を手動で実行する場合は正常に終了したように見えます。スクリプトの実行後にEXITコードを意図的に確認しない限り(cmd.exeのecho %ERRORLEVEL%やpowershell.exeの$LASTEXITCODE)、気が付かないでしょう。

画面1 「WUA_SearchDownloadInstall.vbs」で更新プログラムをインストールすると、EXITコード「-1073741819(0xC0000005)」で終了することがある(必ず発生するわけではない)

画面2 「WUA_SearchDownloadInstall.vbs」が発生させたアプリケーションエラー。発生障害が発生しているモジュール名はwuapi.dll_unloadedと記録されている
参考:
アクセス違反 C0000005 - 実行|インサイド ショー(Microsoft Learn)
しかし、Job Directorのジョブとしてスクリプトを実行する場合、EXITコードはジョブの実行結果の判断に影響します。Job Directorは「-1073741819(0xC0000005)」を、EXITコード「5」として拾い上げます。Windows Updateの自動化のためのジョブネットワークでは、EXITコード「5」をキャンセル(中止)のフローに分岐してしまうため、結果として更新プログラムのインストールが問題なく完了したとしても、再起動を行うジョブに進むことなく終了してしまいます(画面3)。

画面3 スクリプトのアプリケーションエラーが意図せずキャンセル用のフローに進んでしまい、再起動のためのジョブが実行されない
回避策(その1): 更新の実行結果をファイルを介して分岐する
この問題を回避するために、さらに改良を加えた「WUA_SearchDownloadInstallv3.vbs」を作成しました。この問題の発生条件はよくわかりませんが、少なくとも更新プログラムの確認、ダウンロード、インストールには直接的に影響しない問題です。そのため、改良したスクリプトは更新プログラムのインストール結果をEXITコードで返す代わりに、そのコードをファイルに出力(%TEMP%¥wuresultcode.log)するように変更しました。
改良前の「WUA_SearchDownloadInstallv2.vbs(抜粋)」(カスタマイズ部分は青の行)
returnValue = 1
'Output results of install
WScript.Echo "Installation Result: " & _
InstallationResultToText(installationResult.ResultCode)
WScript.Echo "Reboot Required: " & _
installationResult.RebootRequired & vbCRLF
' --- customize ---
returnValue = installationResult.ResultCode
If (returnValue > 1) And (returnValue < 6) Then
If installationResult.RebootRequired Then
returnValue = returnValue + 100
End If
End If
' --- customize ---
WScript.Echo "Listing of updates installed " & _
"and individual installation results:"
さらに改良した「WUA_SearchDownloadInstallv3.vbs(抜粋)」(カスタマイズ部分は青の行、プレーンテキストで表示)
returnValue = 1
'Output results of install
WScript.Echo "Installation Result: " & _
InstallationResultToText(installationResult.ResultCode)
WScript.Echo "Reboot Required: " & _
installationResult.RebootRequired & vbCRLF
' --- customize ---
Dim myfso
Dim myshell
Dim myLog
Set myfso = CreateObject("Scripting.FileSystemObject")
Set myshell = CreateObject("WScript.Shell")
Set myLog = myfso.CreateTextFile(myshell.ExpandEnvironmentStrings("%TEMP%") & "\wuresultcode.log", True)
If (installationResult.ResultCode > 1) And (installationResult.ResultCode < 6) Then
myLog.WriteLine installationResult.ResultCode
Else
myLog.WriteLine installationResult.ResultCode + 100
End If
myLog.Close
' --- customize ---
WScript.Echo "Listing of updates installed " & _
"and individual installation results:"
スクリプトの改良に加えて、新しいジョブを最初のジョブの直後に追加し、0~105を正常終了として扱うようにパラメーターを設定して、次のコマンド行をジョブに記述します。このジョブは、コードを含むファイルを読み取って、そのコードをEXITコードとして終了します。
追加のジョブ(GetResultCode)のコマンド(プレーンテキストで表示)
@echo off
setlocal enabledelayedexpansion
set FILE=%TEMP%\wuresultcode.log
if not exist "%FILE%" (
exit /b 0
)
set /p LINE=<"%FILE%"
exit /b %LINE%
このように修正することで、更新用のスクリプトが「-1073741819(0xC0000005)」で異常終了するしないに影響されることなく、ジョブのフローが期待通りに動作するはずです。なお、最後の更新履歴の確認用スクリプトは、再起動直後に実行されると再起動前の状態が返ってくる場合があるので、再起動後しばらく待ってから(60秒後など)実行されるようにすることをお勧めします(画面4)。

画面4 WUA APIスクリプトの「-1073741819(0xC0000005)」エラーを回避するジョブネットワーク
回避策(その2): PowerShellスクリプトで代替する
「-1073741819(0xC0000005)」エラーは、Microsoft提供のオリジナルの「「WUA_SearchDownloadInstall.vbs」でも発生することがある問題です。「WUA_SearchDownloadInstall.vbs」のダウンロードページには次のように記述されており、このスクリプトの問題の原因を特定し、解決するのは難しそうです。
“このスクリプトは、Windows Update エージェント API の使用法を示し、開発者がこれらの API を使って問題を解決する方法の例を提供することを意図しています。 このスクリプトは運用環境コードとして意図されてません。”(Microsoft Learn)
また、ご存じのように、VBScriptは非推奨であり、今後、使用するべきではありません。
ITニュース. VBScript廃止までのタイムライン発表、ただし廃止時期は未定|かつて山市良とよばれたおじさんのブログ
メモ. VBScript廃止に備えるのはまだ早い? 使用状況を把握する2つの方法|かつて山市良とよばれたおじさんのブログ
そこで、「WUA_SearchDownloadInstall.vbs」より機能は少ないですが(各種パラメーターには対応していませんが)、PowerShellで記述したスクリプト「WUA_SearchDownloadInstall.ps1」を作成しました。このPowerShellスクリプトは、「WUA_SearchDownloadInstall.vbs」の「/Automate」オプションによる更新プログラムの確認、ダウンロード、インストールと同等の処理をPowerShellで記述したもので、その実行結果を「WUA_SearchDownloadInstallv2.vbs」と同じEXITコードとして返します(画面5)
[WUA_SearchDownloadInstall.ps1](プレーンテキストで表示)
function InstallationResultToText($result) {
switch ($result) {
2 { "Succeeded" }
3 { "Succeeded with errors" }
4 { "Failed" }
5 { "Cancelled" }
default { "Unexpected ($result)" }
}
}
Write-Host "--- Running Windows Update ---"
Write-Host "Searching for updates..."
Write-Host ""
$updateSession = new-object -com "Microsoft.Update.Session"
$updateSearcher = $updateSession.CreateupdateSearcher()
$criteria = "IsInstalled=0 and Type='Software' and IsHidden=0"
$searchResult = $updateSearcher.Search($criteria)
Write-Host "List of applicable items on the machine:"
if ($searchResult.Updates.Count -eq 0) {
Write-Host "There are no applicable updates."
exit 0
} else {
$downloadReq = $False
$i = 0
foreach ($update in $searchResult.Updates){
$i++
if ( $update.IsDownloaded ) {
Write-Host $i">" $update.Title "(downloaded)"
} else {
$downloadReq = $true
Write-Host $i">" $update.Title "(not downloaded)"
}
}
}
if ( $downloadReq ) {
Write-Host ""
Write-Host "Creating collection of updates to download..."
$updatesToDownload = new-object -com "Microsoft.Update.UpdateColl"
foreach ($update in $searchResult.Updates){
$updatesToDownload.Add($update) | out-null
}
Write-Host "Downloading updates..."
$downloader = $updateSession.CreateUpdateDownloader()
$downloader.Updates = $updatesToDownload
$downloader.Download()
Write-Host "List of downloaded updates:"
$i = 0
foreach ($update in $searchResult.Updates){
$i++
if ( $update.IsDownloaded ) {
Write-Host $i">" $update.Title "(downloaded)"
} else {
Write-Host $i">" $update.Title "(not downloaded)"
}
}
} else {
Write-Host "All updates are already downloaded."
}
$updatesToInstall = new-object -com "Microsoft.Update.UpdateColl"
Write-Host ""
Write-Host "Creating collection of downloaded updates to install..."
foreach ($update in $searchResult.Updates){
if ( $update.IsDownloaded ) {
$updatesToInstall.Add($update) | out-null
}
}
$returnValue = 0
if ( $updatesToInstall.Count -eq 0 ) {
Write-Host "Not ready for installation."
} else {
Write-Host "Installing" $updatesToInstall.Count "updates..."
$installer = $updateSession.CreateUpdateInstaller()
$installer.Updates = $updatesToInstall
$installationResult = $installer.Install()
if ( $installationResult.ResultCode -eq 2 ) {
Write-Host "All updates installed successfully."
} else {
Write-Host "Some updates could not installed."
}
$returnValue = $installationResult.ResultCode
if ( $installationResult.RebootRequired ) {
Write-Host "One or more updates are requiring reboot."
$returnValue = $returnValue + 100
} else {
Write-Host "Finished. Reboot are not required."
}
Write-Host ""
Write-Host "Listing of updates installed and indivisual installation results:"
$i = 0
foreach ($update in $updatesToInstall){
$result = $installationResult.GetUpdateResult($i)
$i++
Write-Host $i">" $update.Title ":" $(InstallationResultToText $result.ResultCode) ", HRESULT:" $($result.HResult)
}
}
Exit $returnValue

画面5 「WUA_SearchDownloadInstallv2.vbs」のPowerShell版「WUA_SearchDownloadInstall.ps1」
問題のジョブをこのPowerShellスクリプトを実行するように置き換えることで、問題を回避できることを確認しました(画面6)。ジョブではコマンドを次のように記述することで、PowerShellスクリプトが返すEXITコードを、ジョブのEXITコードとして取得させることができます。なお、C:¥toolsはスクリプトの配置先パスに置き換えてください。
powershell.exe -Command "& { C:¥tools¥wua_searchdownloadinstall.ps1 }; exit $LASTEXITCODE" |

画面6 「WUA_SearchDownloadInstall.ps1」に置き換えることで問題を回避し、ジョブフローが期待通りに動くように