@ -17,31 +17,27 @@ $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "insta
$blacklist = Get-AnsibleParam -obj $params -name " blacklist " -type " list "
$whitelist = Get-AnsibleParam -obj $params -name " whitelist " -type " list "
Function Get-CategoryGuid($category_name ) {
$guid = switch -exact ( $category_name ) {
" Application " { " 5C9376AB-8CE6-464A-B136-22113DD69801 " }
" Connectors " { " 434DE588-ED14-48F5-8EED-A15E09A991F6 " }
" CriticalUpdates " { " E6CF1350-C01B-414D-A61F-263D14D133B4 " }
" DefinitionUpdates " { " E0789628-CE08-4437-BE74-2495B842F43B " }
" DeveloperKits " { " E140075D-8433-45C3-AD87-E72345B36078 " }
" FeaturePacks " { " B54E7D24-7ADD-428F-8B75-90A396FA584F " }
" Guidance " { " 9511D615-35B2-47BB-927F-F73D8E9260BB " }
" SecurityUpdates " { " 0FA1201D-4330-4FA8-8AE9-B877473B6441 " }
" ServicePacks " { " 68C5B0A3-D1A6-4553-AE49-01D3A7827828 " }
" Tools " { " B4832BD8-E735-4761-8DAF-37F882276DAB " }
" UpdateRollups " { " 28BC880E-0592-4CBF-8F95-C79B17911D5F " }
" Updates " { " CD5FFD1E-E932-4E3A-BF74-18BF0B1BBD83 " }
default { Fail-Json -message " Unknown category_name $category_name , must be one of (Application,Connectors,CriticalUpdates,DefinitionUpdates,DeveloperKits,FeaturePacks,Guidance,SecurityUpdates,ServicePacks,Tools,UpdateRollups,Updates) " }
# For backwards compatibility
Function Get-CategoryMapping ( $category_name ) {
switch -exact ( $category_name ) {
" CriticalUpdates " { return " Critical Updates " }
" DefinitionUpdates " { return " Definition Updates " }
" DeveloperKits " { return " Developer Kits " }
" FeaturePacks " { return " Feature Packs " }
" SecurityUpdates " { return " Security Updates " }
" ServicePacks " { return " Service Packs " }
" UpdateRollups " { return " Update Rollups " }
default { return $category_name }
}
return $guid
}
$category_guids = $category_names | ForEach-Object { Get-CategoryGuid -category_name $_ }
$category_names = $category_names | ForEach-Object { Get-CategoryMapping -category_name $_ }
$common_functions = {
Function Write-DebugLog($msg ) {
$date_str = Get-Date -Format u
$msg = " $date_str $msg "
Write-Debug -Message $msg
if ( $log_path -ne $null -and ( -not $check_mode ) ) {
Add-Content -Path $log_path -Value $msg
@ -59,7 +55,7 @@ $update_script_block = {
Function Start-Updates {
Param (
$category_ guid s,
$category_ name s,
$log_path ,
$state ,
$blacklist ,
@ -71,7 +67,7 @@ $update_script_block = {
updates = @ { }
filter ed_updates = @ { }
}
Write-DebugLog -msg " Creating Windows Update session... "
try {
$session = New-Object -ComObject Microsoft . Update . Session
@ -80,7 +76,7 @@ $update_script_block = {
$result . msg = " Failed to create Microsoft.Update.Session COM object: $( $_ . Exception . Message ) "
return $result
}
Write-DebugLog -msg " Create Windows Update searcher... "
try {
$searcher = $session . CreateUpdateSearcher ( )
@ -89,24 +85,17 @@ $update_script_block = {
$result . msg = " Failed to create Windows Update search from session: $( $_ . Exception . Message ) "
return $result
}
# OR is only allowed at the top-level, so we have to repeat base criteria inside
# FUTURE: change this to client-side filtered?
$criteria_base = " IsInstalled = 0 "
$criteria_list = $category_guids | ForEach-Object { " ( $criteria_base AND CategoryIds contains ' $_ ') " }
$criteria = [ string ] :: Join ( " OR " , $criteria_list )
Write-DebugLog -msg " Search criteria: $criteria "
Write-DebugLog -msg " Searching for updates to install in category Ids $category_guids ... "
Write-DebugLog -msg " Searching for updates to install "
try {
$search_result = $searcher . Search ( $criteria )
$search_result = $searcher . Search ( " IsInstalled = 0 " )
} catch {
$result . failed = $true
$result . msg = " Failed to search for updates with criteria '$criteria ' : $( $_ . Exception . Message ) "
$result . msg = " Failed to search for updates: $( $_ . Exception . Message ) "
return $result
}
Write-DebugLog -msg " Found $( $search_result . Updates . Count ) updates "
Write-DebugLog -msg " Creating update collection... "
try {
$updates_to_install = New-Object -ComObject Microsoft . Update . UpdateColl
@ -115,7 +104,7 @@ $update_script_block = {
$result . msg = " Failed to create update collection object: $( $_ . Exception . Message ) "
return $result
}
foreach ( $update in $search_result . Updates ) {
$update_info = @ {
title = $update . Title
@ -123,10 +112,10 @@ $update_script_block = {
kb = $update . KBArticleIDs
id = $update . Identity . UpdateId
installed = $false
categories = ( $update . Categories | ForEach-Object { $_ . Name } )
}
# validate update again blacklist/whitelist
$skipped = $false
# validate update again blacklist/whitelist/post_category_names/hidden
$whitelist_match = $false
foreach ( $whitelist_entry in $whitelist ) {
if ( $update_info . title -imatch $whitelist_entry ) {
@ -142,33 +131,52 @@ $update_script_block = {
}
if ( $whitelist . Length -gt 0 -and -not $whitelist_match ) {
Write-DebugLog -msg " Skipping update $( $update_info . id ) - $( $update_info . title ) as it was not found in the whitelist "
$skipped = $true
$update_info . filtered_reason = " whitelist "
$result . filtered_updates [ $update_info . id ] = $update_info
continue
}
foreach ( $kb in $update_info . kb ) {
if ( " KB $kb " -imatch $blacklist_entry ) {
$kb_match = $true
$blacklist_match = $false
foreach ( $blacklist_entry in $blacklist ) {
if ( $update_info . title -imatch $blacklist_entry ) {
$blacklist_match = $true
break
}
foreach ( $blacklist_entry in $blacklist ) {
$kb_match = $false
foreach ( $kb in $update_info . kb ) {
if ( " KB $kb " -imatch $blacklist_entry ) {
$kb_match = $true
}
}
if ( $kb_match -or $update_info . title -imatch $blacklist_entry ) {
Write-DebugLog -msg " Skipping update $( $update_info . id ) - $( $update_info . title ) as it was found in the blacklist "
$skipped = $true
foreach ( $kb in $update_info . kb ) {
if ( " KB $kb " -imatch $blacklist_entry ) {
$blacklist_match = $true
break
}
}
}
if ( $skipped ) {
if ( $blacklist_match ) {
Write-DebugLog -msg " Skipping update $( $update_info . id ) - $( $update_info . title ) as it was found in the blacklist "
$update_info . filtered_reason = " blacklist "
$result . filtered_updates [ $update_info . id ] = $update_info
continue
}
if ( $update . IsHidden ) {
Write-DebugLog -msg " Skipping update $( $update_info . title ) as it was hidden "
$update_info . filtered_reason = " skip_hidden "
$result . filtered_updates [ $update_info . id ] = $update_info
continue
}
$category_match = $false
foreach ( $match_cat in $category_names ) {
if ( $update_info . categories -ieq $match_cat ) {
$category_match = $true
break
}
}
if ( $category_names . Length -gt 0 -and -not $category_match ) {
Write-DebugLog -msg " Skipping update $( $update_info . id ) - $( $update_info . title ) as it was not found in the category names filter "
$update_info . filtered_reason = " category_names "
$result . filtered_updates [ $update_info . id ] = $update_info
continue
}
if ( -not $update . EulaAccepted ) {
Write-DebugLog -msg " Accepting EULA for $( $update_info . id ) "
try {
@ -179,36 +187,31 @@ $update_script_block = {
return $result
}
}
if ( $update . IsHidden ) {
Write-DebugLog -msg " Skipping hidden update $( $update_info . title ) "
continue
}
Write-DebugLog -msg " Adding update $( $update_info . id ) - $( $update_info . title ) "
$updates_to_install . Add ( $update ) > $null
$result . updates [ $update_info . id ] = $update_info
}
Write-DebugLog -msg " Calculating pre-install reboot requirement... "
# calculate this early for check mode, and to see if we should allow updates to continue
$result . reboot_required = ( New-Object -ComObject Microsoft . Update . SystemInfo ) . RebootRequired
$result . found_update_count = $updates_to_install . Count
$result . installed_update_count = 0
# Early exit of check mode/state=searched as it cannot do more after this
if ( $check_mode -or $state -eq " searched " ) {
Write-DebugLog -msg " Check mode: exiting... "
Write-DebugLog -msg " Return value: `r `n $( ConvertTo-Json -InputObject $result -Depth 99 ) "
if ( $updates_to_install . Count -gt 0 -and ( $state -ne " searched " ) ) {
$result . changed = $true
}
return $result
}
if ( $updates_to_install . Count -gt 0 ) {
if ( $result . reboot_required ) {
Write-DebugLog -msg " FATAL: A reboot is required before more updates can be installed "
@ -221,7 +224,7 @@ $update_script_block = {
# no updates to install exit here
return $result
}
Write-DebugLog -msg " Downloading updates... "
$update_index = 1
foreach ( $update in $updates_to_install ) {
@ -231,7 +234,7 @@ $update_script_block = {
$update_index + +
continue
}
Write-DebugLog -msg " Creating downloader object... "
try {
$dl = $session . CreateUpdateDownloader ( )
@ -240,7 +243,7 @@ $update_script_block = {
$result . msg = " Failed to create downloader object: $( $_ . Exception . Message ) "
return $result
}
Write-DebugLog -msg " Creating download collection... "
try {
$dl . Updates = New-Object -ComObject Microsoft . Update . UpdateColl
@ -249,10 +252,10 @@ $update_script_block = {
$result . msg = " Failed to create download collection object: $( $_ . Exception . Message ) "
return $result
}
Write-DebugLog -msg " Adding update $update_number $( $update . Identity . UpdateId ) "
$dl . Updates . Add ( $update ) > $null
Write-DebugLog -msg " Downloading $update_number $( $update . Identity . UpdateId ) "
try {
$download_result = $dl . Download ( )
@ -261,7 +264,7 @@ $update_script_block = {
$result . msg = " Failed to download update $update_number $( $update . Identity . UpdateId ) - $( $update . Title ) : $( $_ . Exception . Message ) "
return $result
}
Write-DebugLog -msg " Download result code for $update_number $( $update . Identity . UpdateId ) = $( $download_result . ResultCode ) "
# FUTURE: configurable download retry
if ( $download_result . ResultCode -ne 2 ) { # OperationResultCode orcSucceeded
@ -269,14 +272,14 @@ $update_script_block = {
$result . msg = " Failed to download update $update_number $( $update . Identity . UpdateId ) - $( $update . Title ) : Download Result $( $download_result . ResultCode ) "
return $result
}
$result . changed = $true
$update_index + +
}
Write-DebugLog -msg " Installing updates... "
# install as a batch so the reboot manager will suppress intermediate reboots
Write-DebugLog -msg " Creating installer object... "
try {
$installer = $session . CreateUpdateInstaller ( )
@ -285,7 +288,7 @@ $update_script_block = {
$result . msg = " Failed to create Update Installer object: $( $_ . Exception . Message ) "
return $result
}
Write-DebugLog -msg " Creating install collection... "
try {
$installer . Updates = New-Object -ComObject Microsoft . Update . UpdateColl
@ -294,12 +297,12 @@ $update_script_block = {
$result . msg = " Failed to create Update Collection object: $( $_ . Exception . Message ) "
return $result
}
foreach ( $update in $updates_to_install ) {
Write-DebugLog -msg " Adding update $( $update . Identity . UpdateID ) "
$installer . Updates . Add ( $update ) > $null
}
# FUTURE: use BeginInstall w/ progress reporting so we can at least log intermediate install results
try {
$install_result = $installer . Install ( )
@ -308,10 +311,10 @@ $update_script_block = {
$result . msg = " Failed to install update from Update Collection: $( $_ . Exception . Message ) "
return $result
}
$update_success_count = 0
$update_fail_count = 0
# WU result API requires us to index in to get the install results
$update_index = 0
foreach ( $update in $updates_to_install ) {
@ -325,9 +328,9 @@ $update_script_block = {
}
$update_resultcode = $update_result . ResultCode
$update_hresult = $update_result . HResult
$update_index + +
$update_dict = $result . updates [ $update . Identity . UpdateID ]
if ( $update_resultcode -eq 2 ) { # OperationResultCode orcSucceeded
$update_success_count + +
@ -341,18 +344,18 @@ $update_script_block = {
Write-DebugLog -msg " Update $update_number $( $update . Identity . UpdateID ) failed, resultcode: $update_resultcode , hresult: $update_hresult "
}
}
Write-DebugLog -msg " Performing post-install reboot requirement check... "
$result . reboot_required = ( New-Object -ComObject Microsoft . Update . SystemInfo ) . RebootRequired
$result . installed_update_count = $update_success_count
$result . failed_update_count = $update_fail_count
if ( $update_fail_count -gt 0 ) {
$result . failed = $true
$result . msg = " Failed to install one or more updates "
return $result
}
Write-DebugLog -msg " Return value: `r `n $( ConvertTo-Json -InputObject $result -Depth 99 ) "
return $result
@ -389,7 +392,7 @@ Function Start-Natively($common_functions, $script) {
# add the update script block and required parameters
$ps_pipeline . AddStatement ( ) . AddScript ( $script ) > $null
$ps_pipeline . AddParameter ( " arguments " , @ {
category_ guids = $category_guid s
category_ names = $category_name s
log_path = $log_path
state = $state
blacklist = $blacklist
@ -450,7 +453,7 @@ Function Start-AsScheduledTask($common_functions, $script) {
Name = $job_name
ArgumentList = @ (
@ {
category_ guids = $category_guid s
category_ names = $category_name s
log_path = $log_path
state = $state
blacklist = $blacklist
@ -499,7 +502,7 @@ Function Start-AsScheduledTask($common_functions, $script) {
Write-DebugLog -msg " Waiting for job output to populate... "
Start-Sleep -Milliseconds 500
}
# NB: fallthru on both timeout and success
$ret = @ {
ErrorOutput = $job . Error
@ -553,3 +556,4 @@ if ($wua_available) {
}
Exit-Json -obj $result