--- - name: Run DC patch task via JEA-PatchOps hosts: domain_controllers gather_facts: no vars: task_path: '\' # or e.g. '\\Microsoft\\Windows\\WindowsUpdate\\' task_name: 'Patching-windows-task' poll_delay: 60 # seconds start_retries: 30 # up to 30 minutes to confirm it started finish_retries: 3 # up to 6 hours to finish winrm_port: 5986 tasks: - name: Verify task exists and capture pre-start info ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' Import-Module ScheduledTasks $tp='{{ task_path }}'; $tn='{{ task_name }}' $t = Get-ScheduledTask -TaskPath $tp -TaskName $tn $i = Get-ScheduledTaskInfo -TaskPath $tp -TaskName $tn [PSCustomObject]@{ Exists = $true State = $i.State LastRunTime = $i.LastRunTime LastTaskResult= $i.LastTaskResult Enabled = $t.Settings.Enabled } | ConvertTo-Json -Compress register: pre_info - name: Fail if task not found ansible.builtin.fail: msg: "Task '{{ task_path }}{{ task_name }}' not found." when: pre_info.stdout | default('') == '' - name: Ensure task is enabled ansible.windows.win_powershell: script: | Import-Module ScheduledTasks $tp='{{ task_path }}'; $tn='{{ task_name }}' $t = Get-ScheduledTask -TaskPath $tp -TaskName $tn if (-not $t.Settings.Enabled) { Enable-ScheduledTask -TaskPath $tp -TaskName $tn | Out-Null } register: enable_task failed_when: false - name: Start the SYSTEM patch task (schtasks) ansible.windows.win_command: > schtasks /Run /TN "{{ task_path }}{{ task_name }}" register: start_task failed_when: false changed_when: > (start_task.rc | default(1)) == 0 or ('SUCCESS' in (start_task.stdout | default(''))) # Confirm the task actually started (LastRunTime advanced) - name: Wait until LastRunTime changes (task actually started) ansible.windows.win_powershell: script: | Import-Module ScheduledTasks $tp='{{ task_path }}'; $tn='{{ task_name }}' (Get-ScheduledTaskInfo -TaskPath $tp -TaskName $tn).LastRunTime.ToString('o') register: started_info retries: "{{ start_retries }}" delay: "{{ poll_delay }}" until: > (started_info.stdout | default('') | length > 0) and (started_info.stdout != (pre_info.stdout | from_json).LastRunTime) # Long poll until the task finishes successfully - name: Poll until task is Ready/Disabled with LastTaskResult 0 block: - name: Get current task state/result ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' Import-Module ScheduledTasks $tp='{{ task_path }}'; $tn='{{ task_name }}' $i = Get-ScheduledTaskInfo -TaskPath $tp -TaskName $tn [PSCustomObject]@{ State = $i.State LastTaskResult = $i.LastTaskResult LastRunTime = $i.LastRunTime } | ConvertTo-Json -Compress register: task_info failed_when: false retries: "{{ finish_retries }}" delay: "{{ poll_delay }}" until: > (task_info.stdout | default('') | length > 0) and ((task_info.stdout | from_json).State in ['Ready','Disabled']) and (((task_info.stdout | from_json).LastTaskResult | int) == 0) rescue: # If the node rebooted and PSRP dropped, wait for WinRM then check once more - name: Wait for WinRM after possible reboot ansible.windows.win_wait_for: port: "{{ winrm_port }}" timeout: 1800 - name: Final check after reconnect ansible.windows.win_powershell: script: | Import-Module ScheduledTasks $tp='{{ task_path }}'; $tn='{{ task_name }}' $i = Get-ScheduledTaskInfo -TaskPath $tp -TaskName $tn [PSCustomObject]@{ State=$i.State; LastTaskResult=$i.LastTaskResult; LastRunTime=$i.LastRunTime } | ConvertTo-Json -Compress register: task_info failed_when: false - name: Show final task state/result ansible.builtin.debug: msg: - "Final State: {{ (task_info.stdout | default('{}') | from_json).State | default('n/a') }}" - "LastTaskResult: {{ (task_info.stdout | default('{}') | from_json).LastTaskResult | default('n/a') }}" - "LastRunTime: {{ (task_info.stdout | default('{}') | from_json).LastRunTime | default('n/a') }}" - name: Fail if task ended but did not return 0 ansible.builtin.fail: msg: > Task finished in state {{ (task_info.stdout | from_json).State }} with LastTaskResult {{ (task_info.stdout | from_json).LastTaskResult }} (non-zero). when: > (task_info.stdout | default('') | length > 0) and ((task_info.stdout | from_json).State in ['Ready','Disabled']) and (((task_info.stdout | from_json).LastTaskResult | int) != 0) - name: Reboot if ready (belt & suspenders) ansible.windows.win_reboot: reboot_timeout: 5400 when: (task_info.stdout | from_json).State == 'Ready'