ansible-collections / ansible.windows

Windows core collection for Ansible
https://galaxy.ansible.com/ansible/windows
GNU General Public License v3.0
249 stars 169 forks source link

win_update does not find updates reliably; but showing up via GUI #471

Closed alfonsrv closed 1 year ago

alfonsrv commented 1 year ago
SUMMARY

Available Windows Updates are not shown reliably, even after being allowed through e.g. WSUS. Ansible finds 0 updates; triggering searching via GUI finds new updates however. Re-running ansible afterwards causes it to disappear from the GUI again. Might be related to https://github.com/ansible-collections/ansible.windows/issues/87

Recorded the behavior for better clarity here: https://youtu.be/KMRL_xCQHek
Tried changing state and server_selection to different values.

ISSUE TYPE
COMPONENT NAME

win_update

ANSIBLE VERSION
root@~:/etc/ansible/play# ansible --version
ansible [core 2.14.1]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.10/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True
COLLECTION VERSION
# /root/.ansible/collections/ansible_collections
Collection      Version
--------------- -------
ansible.windows 1.13.0
CONFIGURATION
CONFIG_FILE() = /etc/ansible/ansible.cfg
OS / ENVIRONMENT
EXPECTED RESULTS

Find all updates

ACTUAL RESULTS

Does not find available Exchange update, even though it's available when using the GUI.


root@orchestrator:/etc/ansible/play# ansible-playbook exchange.yml -vvvv
ansible-playbook [core 2.14.1]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.10/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible-playbook
  python version = 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True
Using /etc/ansible/ansible.cfg as config file
setting up inventory plugins
host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Parsed /etc/ansible/hosts inventory source with ini plugin
redirecting (type: action) ansible.builtin.win_updates to ansible.windows.win_updates
Loading collection ansible.windows from /root/.ansible/collections/ansible_collections/ansible/windows
Loading callback plugin default of type stdout, v2.0 from /usr/local/lib/python3.10/dist-packages/ansible/plugins/callback/default.py
Skipping callback 'default', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.

PLAYBOOK: exchange.yml *******************************************************************************************************************************************************************************************************
Positional arguments: exchange.yml
verbosity: 4
connection: smart
timeout: 10
become_method: sudo
tags: ('all',)
inventory: ('/etc/ansible/hosts',)
forks: 5
1 plays in exchange.yml

PLAY [Install updates on core Windows servers] *******************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************
task path: /etc/ansible/play/exchange.yml:3
redirecting (type: modules) ansible.builtin.setup to ansible.windows.setup
Using module file /root/.ansible/collections/ansible_collections/ansible/windows/plugins/modules/setup.ps1
Pipelining is enabled.
<ex2.domain.tld> ESTABLISH WINRM CONNECTION FOR USER: administrator@domain.tld on PORT 5986 TO ex2.domain.tld
calling kinit with pexpect for principal administrator@domain.tld
EXEC (via pipeline wrapper)
ok: [ex2.domain.tld]
redirecting (type: action) ansible.builtin.win_updates to ansible.windows.win_updates

TASK [search and install updates] ********************************************************************************************************************************************************************************************
task path: /etc/ansible/play/exchange.yml:7
redirecting (type: modules) ansible.builtin.win_updates to ansible.windows.win_updates
redirecting (type: action) ansible.builtin.win_updates to ansible.windows.win_updates
redirecting (type: action) ansible.builtin.win_updates to ansible.windows.win_updates
<ex2.domain.tld> ESTABLISH WINRM CONNECTION FOR USER: administrator@domain.tld on PORT 5986 TO ex2.domain.tld
calling kinit with pexpect for principal administrator@domain.tld
EXEC (via pipeline wrapper)
<ex2.domain.tld> PUT "/root/.ansible/tmp/ansible-local-19477g9yktbyj/tmpl16w9vd_/poll.ps1" TO "C:\Users\administrator\AppData\Local\Temp\ansible-tmp-1674248261.0345626-19484-41692100241694\poll.ps1"
<ex2.domain.tld> PUT "/root/.ansible/tmp/ansible-local-19477g9yktbyj/tmpg5bk4m8y/cancel.ps1" TO "C:\Users\administrator\AppData\Local\Temp\ansible-tmp-1674248261.0345626-19484-41692100241694\cancel.ps1"
<ex2.domain.tld> Running win_updates - round 1
<ex2.domain.tld> Starting update task
Using module file /root/.ansible/collections/ansible_collections/ansible/windows/plugins/modules/win_updates.ps1
Pipelining is enabled.
EXEC (via pipeline wrapper)
<ex2.domain.tld> Starting polling for update results
EXEC (via pipeline wrapper)
<ex2.domain.tld> Received final progress result from update task
EXEC (via pipeline wrapper)
ok: [ex2.domain.tld] => {
    "changed": false,
    "failed_update_count": 0,
    "filtered_updates": {},
    "found_update_count": 0,
    "installed_update_count": 0,
    "invocation": {
        "module_args": {
            "accept_list": null,
            "category_names": [
                "*"
            ],
            "log_path": "c:\\windows\\logs\\ansible_update_log.txt",
            "reboot": true,
            "reboot_timeout": 600,
            "reject_list": null,
            "server_selection": "windows_update",
            "skip_optional": false,
            "state": "searched",
            "use_scheduled_task": false
        }
    },
    "reboot_required": false,
    "updates": {}
}

PLAY RECAP *******************************************************************************************************************************************************************************************************************
ex2.domain.tld             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
jborean93 commented 1 year ago

By specifying windows_update the module is going to do ServerSelection = ssWindowsUpdate which the docs indicate it's the Windows Update service. In your issue description you mentioned having a WSUS server which will be ignored if you do server_selection: windows_update. Have you tried not specifying server_selection or doing server_selection: managed_server?

Also thank you for the video to describe the issue, it makes it a lot easier to try and understand what is happening here.

alfonsrv commented 1 year ago

Initially I tried using server_selection: windows_update, however that did not seem to work reliably on all machines. So I admitted all updates on the WSUS server and set it back to the default server_selection: managed_server. After waiting a bit and executing the command multiple times, switching server_selection forward and backward, some function updates could not be installed. This included Windows Malicious Software Removal Tool, Updates for Microsoft SQL Server, Updates for Exchange Servers.

For all these instances the behavior was the same as shown in the video and had to be installed via GUI. Could be that it's Windows Server 2012 R2 and Feature Updates aren't implemented properly in the API or sth.

I also ran this code I found you outlined somewhere, and it didn't list the updates in question either. Didn't cross check with other tools like this, but might next time – likely uses the same API tho https://github.com/EliaSaSe/windows-update-remote-service

$ErrorActionPreference = "Stop"

$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$search_result = $searcher.Search("IsInstalled = 0")

foreach ($update in $search_result.Updates) {
    $categories = @()
    foreach ($category in $update.Categories) {
        $categories += "$($category.Name) - $($category.CategoryID)"
    }

    $kbs = @()
    foreach ($kb in $update.KBArticleIDs) {
        $kbs += $kb
    }

    [PSCustomObject]@{
        Categories = $categories
        Description = $update.Description
        Hidden = $update.IsHidden
        Id = $update.Identity.UpdateID
        KBs = $kbs
        Mandatory = $update.IsMandatory
        Present = $update.IsPresent
        Title = $update.Title
    }
}
jborean93 commented 1 year ago

Thanks for sharing the info, unfortunately I think I'm just going to have to try and set up a similar environment to yourself and test it out. I don't know why the API is not returning the info, the script you shared is pretty much what win_updates does locally.

alfonsrv commented 1 year ago

I noticed it behaves similarly for other "Feature Updates" such as MSSQL. Might make it easier to build an environment around that.

jborean93 commented 1 year ago

I spent a bit of time last week and today and unfortunately I cannot replicate this problem. I installed SQL Server Express 2016 and set up a WSUS environment. The Server 2012 R2 host was configured with the following registry policies to point to the WSUS server:

- name: configure WSUS registry settings
  win_regedit:
    path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate{{ item.path }}
    name: '{{ item.name }}'
    data: '{{ item.data }}'
    type: '{{ item.type | default("string") }}'
    state: present
  loop:
  - path: ''
    name: WUServer
    data: http://{{ wsus_server }}:8530
  - path: ''
    name: WUStatusServer
    data: http://{{ wsus_server }}:8530
  - path: ''
    name: DoNotConnectToWindowsUpdateInternetLocations
    data: 1
    type: dword
  - path: \AU
    name: UseWUServer
    data: 1
    type: dword

SQL Server Express 2016 SP2 was updated and after running Ansible it found the following updates:

ok: [APP] => changed=false
  failed_update_count: 0
  filtered_updates:
    9489d8c1-9a07-471f-8d76-62a841c3a22e:
      categories:
      - Microsoft SQL Server 2016
      - Service Packs
      downloaded: false
      filtered_reason: category_names
      filtered_reasons:
      - category_names
      id: 9489d8c1-9a07-471f-8d76-62a841c3a22e
      installed: false
      kb:
      - '5003279'
      title: SQL Server 2016 Service Pack 3 (KB5003279)
  found_update_count: 2
  installed_update_count: 0
  reboot_required: false
  rebooted: false
  updates:
    2681c9db-3adb-4069-830b-e998c31bc37b:
      categories:
      - Microsoft SQL Server 2016
      - Security Updates
      downloaded: false
      id: 2681c9db-3adb-4069-830b-e998c31bc37b
      installed: false
      kb:
      - '5014351'
      title: Security Update for SQL Server 2016 Service Pack 2 CU (KB5014351)
    485fee55-15be-4183-844f-67f717c2c794:
      categories:
      - Microsoft SQL Server 2016
      - Security Updates
      downloaded: false
      id: 485fee55-15be-4183-844f-67f717c2c794
      installed: false
      kb:
      - '5014365'
      title: Security Update for SQL Server 2016 Service Pack 2 GDR (KB5014365)

I even cleared out the local update cache to try it again and it was always able to find the latest updates. I tried all sorts of permutations but no matter what I tried it never failed to find the SQL server updates from the WSUS server.

I can confirm that searching through the COM API (whether through Ansible or manually in PowerShell) will reset the GUI back to the default "Search for updates" screen but that just seems to be a byproduct of the API. I cannot find any documentation on that so I am just assuming that's the behaviour. It makes some sense because if another process has updated the update cache by potentially installing updates or by hiding updates; the GUI would want to check again to make sure it has the latest list available.

Unfortunately there's not much else I can do here, from what I can see the module just calls the Windows Update API and I can get it working in my test environment. If this is still a problem for you my only recommendation is to contact Microsoft support to see why their API is not getting the updates you expect with that PowerShell example you tried in https://github.com/ansible-collections/ansible.windows/issues/471#issuecomment-1401088767. That's essentially what the module is doing and if Microsoft's own API isn't returning the results expected then it's most likely some sort of misconfiguration or problem in their client.