From 6a1a25c85c98dbe4a7fe3fb0f451eee10a543e01 Mon Sep 17 00:00:00 2001 From: "mhorak@totalservice.cz" Date: Tue, 19 Aug 2025 16:54:21 +0200 Subject: [PATCH] task --- patch-dc-controllers.yaml | 117 +++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 19 deletions(-) diff --git a/patch-dc-controllers.yaml b/patch-dc-controllers.yaml index 586668f..a638c61 100644 --- a/patch-dc-controllers.yaml +++ b/patch-dc-controllers.yaml @@ -4,21 +4,44 @@ gather_facts: no vars: - task_path: '\\' # e.g. '\\Microsoft\\Windows\\WindowsUpdate\\' + task_path: '\' # or e.g. '\\Microsoft\\Windows\\WindowsUpdate\\' task_name: 'Patching-windows-task' - poll_delay: 60 - poll_retries: 3 # up to 6h + 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: Ensure the task is enabled (in case it was disabled) + - 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 ($t.Settings.Enabled -ne $true) { Enable-ScheduledTask -TaskPath $tp -TaskName $tn } - changed_when: "'Enable-ScheduledTask' in (result.stdout | default(''))" - register: result + 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) @@ -30,24 +53,80 @@ (start_task.rc | default(1)) == 0 or ('SUCCESS' in (start_task.stdout | default(''))) - - name: Poll until task is Ready/Disabled with success + # Confirm the task actually started (LastRunTime advanced) + - name: Wait until LastRunTime changes (task actually started) 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 } | ConvertTo-Json -Compress - register: task_info - failed_when: false - retries: "{{ poll_retries }}" + (Get-ScheduledTaskInfo -TaskPath $tp -TaskName $tn).LastRunTime.ToString('o') + register: started_info + retries: "{{ start_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) + (started_info.stdout | default('') | length > 0) and + (started_info.stdout != (pre_info.stdout | from_json).LastRunTime) - - name: Reboot if needed (belt & suspenders) + # 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' \ No newline at end of file + when: (task_info.stdout | from_json).State == 'Ready'