win_updates: added blacklist and whitelist filtering of updates (#34907)

* win_updates: added blacklist and whitelist filtering of updates

* fixed sanity issue with docs

* fix docs typos
This commit is contained in:
Jordan Borean 2018-01-19 10:29:46 +10:00 committed by Matt Davis
parent beb0fd9b8b
commit e5c6708d39
3 changed files with 115 additions and 21 deletions

View file

@ -22,11 +22,13 @@ $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "b
$category_names = Get-AnsibleParam -obj $params -name "category_names" -type "list" -default @("CriticalUpdates", "SecurityUpdates", "UpdateRollups") $category_names = Get-AnsibleParam -obj $params -name "category_names" -type "list" -default @("CriticalUpdates", "SecurityUpdates", "UpdateRollups")
$log_path = Get-AnsibleParam -obj $params -name "log_path" -type "path" $log_path = Get-AnsibleParam -obj $params -name "log_path" -type "path"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "installed" -validateset "installed", "searched" $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "installed" -validateset "installed", "searched"
# TODO: blacklist and whitelist $blacklist = Get-AnsibleParam -obj $params -name "blacklist" -type "list"
$whitelist = Get-AnsibleParam -obj $params -name "whitelist" -type "list"
$result = @{ $result = @{
changed = $false changed = $false
updates = @{} updates = @{}
filtered_updates = @{}
} }
Function Write-DebugLog($msg) { Function Write-DebugLog($msg) {
@ -106,32 +108,67 @@ try {
Fail-Json -obj $result -message "Failed to create update collection object: $($_.Exception.Message)" Fail-Json -obj $result -message "Failed to create update collection object: $($_.Exception.Message)"
} }
# FUTURE: add further filtering options (whitelist/blacklist)
foreach ($update in $search_result.Updates) { foreach ($update in $search_result.Updates) {
if (-not $update.EulaAccepted) { $update_info = @{
Write-DebugLog -msg "Accepting EULA for $($update.Identity.UpdateID)"
try {
$update.AcceptEula()
} catch {
Fail-Json -obj $result -message "Failed to accept EULA for update $($update.Identity.UpdateID) - $($update.Title)"
}
}
if ($update.IsHidden) {
Write-DebugLog -msg "Skipping hidden update $($update.Title)"
continue
}
Write-DebugLog -msg "Adding update $($update.Identity.UpdateID) - $($update.Title)"
$updates_to_install.Add($update) > $null
$result.updates[$update.Identity.UpdateId] = @{
title = $update.Title title = $update.Title
# TODO: pluck the first KB out (since most have just one)? # TODO: pluck the first KB out (since most have just one)?
kb = $update.KBArticleIDs kb = $update.KBArticleIDs
id = $update.Identity.UpdateId id = $update.Identity.UpdateId
installed = $false installed = $false
} }
# validate update again blacklist/whitelist
$skipped = $false
foreach ($whitelist_entry in $whitelist) {
$kb_match = $false
foreach ($kb in $update_info.kb) {
if ("KB$kb" -imatch $whitelist_entry) {
$kb_match = $true
}
}
if (-not ($kb_match -or $update_info.title -imatch $whitelist_entry)) {
Write-DebugLog -msg "Skipping update $($update_info.id) - $($update_info.title) as it was not found in the whitelist"
$skipped = $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
break
}
}
if ($skipped) {
$result.filtered_updates[$update_info.id] = $update_info
continue
}
if (-not $update.EulaAccepted) {
Write-DebugLog -msg "Accepting EULA for $($update_info.id)"
try {
$update.AcceptEula()
} catch {
Fail-Json -obj $result -message "Failed to accept EULA for update $($update_info.id) - $($update_info.title)"
}
}
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..." Write-DebugLog -msg "Calculating pre-install reboot requirement..."
@ -279,3 +316,4 @@ if ($update_fail_count -gt 0) {
Write-DebugLog -msg "Return value:`r`n$(ConvertTo-Json -InputObject $result -Depth 99)" Write-DebugLog -msg "Return value:`r`n$(ConvertTo-Json -InputObject $result -Depth 99)"
Exit-Json $result Exit-Json $result

View file

@ -21,6 +21,16 @@ short_description: Download and install Windows updates
description: description:
- Searches, downloads, and installs Windows updates synchronously by automating the Windows Update client - Searches, downloads, and installs Windows updates synchronously by automating the Windows Update client
options: options:
blacklist:
description:
- A list of update titles or KB numbers that can be used to specify
which updates are to be excluded from installation.
- If an available update does match one of the entries, then it is
skipped and not installed.
- Each entry can either be the KB article or Update title as a regex
according to the PowerShell regex rules.
required: false
version_added: '2.5'
category_names: category_names:
description: description:
- A scalar or list of categories to install updates from - A scalar or list of categories to install updates from
@ -69,6 +79,19 @@ options:
description: description:
- If set, C(win_updates) will append update progress to the specified file. The directory must already exist. - If set, C(win_updates) will append update progress to the specified file. The directory must already exist.
required: false required: false
whitelist:
description:
- A list of update titles or KB numbers that can be used to specify
which updates are to be searched or installed.
- If an available update does not match one of the entries, then it
is skipped and not installed.
- Each entry can either be the KB article or Update title as a regex
according to the PowerShell regex rules.
- The whitelist is only validated on updates that were found based on
I(category_names). It will not force the module to install an update
if it was not in the category specified.
required: false
version_added: '2.5'
author: "Matt Davis (@nitzmahone)" author: "Matt Davis (@nitzmahone)"
notes: notes:
- C(win_updates) must be run by a user with membership in the local Administrators group. - C(win_updates) must be run by a user with membership in the local Administrators group.
@ -78,6 +101,8 @@ notes:
C(reboot) can be used to reboot the host if required in the one task. C(reboot) can be used to reboot the host if required in the one task.
- C(win_updates) can take a significant amount of time to complete (hours, in some cases). - C(win_updates) can take a significant amount of time to complete (hours, in some cases).
Performance depends on many factors, including OS version, number of updates, system load, and update server load. Performance depends on many factors, including OS version, number of updates, system load, and update server load.
- More information about PowerShell and how it handles RegEx strings can be
found at U(https://technet.microsoft.com/en-us/library/2007.11.powershell.aspx).
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@ -104,7 +129,24 @@ EXAMPLES = r'''
- SecurityUpdates - SecurityUpdates
reboot: yes reboot: yes
# Note async on works on Windows Server 2012 or newer - become must be explicitly set on the task for this to work - name: Install only particular updates based on the KB numbers
win_updates:
category_name:
- SecurityUpdates
whitelist:
- KB4056892
- KB4073117
- name: Exlude updates based on the update title
win_updates:
category_name:
- SecurityUpdates
- CriticalUpdates
blacklist:
- Windows Malicious Software Removal Tool for Windows
- \d{4}-\d{2} Cumulative Update for Windows Server 2016
# Note async works on Windows Server 2012 or newer - become must be explicitly set on the task for this to work
- name: Search for Windows updates asynchronously - name: Search for Windows updates asynchronously
win_updates: win_updates:
category_names: category_names:
@ -175,6 +217,15 @@ updates:
type: boolean type: boolean
sample: 2147942402 sample: 2147942402
filtered_updates:
description: List of updates that were found but were filtered based on
I(blacklist) or I(whitelist). The return value is in the same form as
I(updates).
returned: success
type: complex
sample: see the updates return value
contains: {}
found_update_count: found_update_count:
description: The number of updates found needing to be applied description: The number of updates found needing to be applied
returned: success returned: success

View file

@ -183,6 +183,7 @@ class ActionModule(ActionBase):
changed = result['changed'] changed = result['changed']
updates = result.get('updates', dict()) updates = result.get('updates', dict())
filtered_updates = result.get('filtered_updates', dict())
found_update_count = result.get('found_update_count', 0) found_update_count = result.get('found_update_count', 0)
installed_update_count = result.get('installed_update_count', 0) installed_update_count = result.get('installed_update_count', 0)
@ -231,7 +232,10 @@ class ActionModule(ActionBase):
result = self._run_win_updates(new_module_args, task_vars) result = self._run_win_updates(new_module_args, task_vars)
result_updates = result.get('updates', dict()) result_updates = result.get('updates', dict())
result_filtered_updates = result.get('filtered_updates', dict())
updates = self._merge_dict(updates, result_updates) updates = self._merge_dict(updates, result_updates)
filtered_updates = self._merge_dict(filtered_updates,
result_filtered_updates)
found_update_count += result.get('found_update_count', 0) found_update_count += result.get('found_update_count', 0)
installed_update_count += result.get('installed_update_count', 0) installed_update_count += result.get('installed_update_count', 0)
if result['changed']: if result['changed']:
@ -242,6 +246,7 @@ class ActionModule(ActionBase):
if self._task.async_val == 0: if self._task.async_val == 0:
result['changed'] = changed result['changed'] = changed
result['updates'] = updates result['updates'] = updates
result['filtered_updates'] = filtered_updates
result['found_update_count'] = found_update_count result['found_update_count'] = found_update_count
result['installed_update_count'] = installed_update_count result['installed_update_count'] = installed_update_count