--- - name: Diagnose & start patch task via JEA-PatchOps hosts: domain_controllers gather_facts: no vars: task_path: '\\' # root task_name: 'Patching-windows-task' # your task poll_delay: 30 finish_retries: 3 # up to 2h tasks: - name: Show effective identity (who JEA runs as) ansible.windows.win_powershell: script: "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name" register: whoami - name: Read task details (state/enabled/last run) 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]@{ Enabled = $t.Settings.Enabled State = $i.State LastRunTime = $i.LastRunTime LastTaskResult= $i.LastTaskResult Actions = ($t.Actions | Select-Object -ExpandProperty Execute -ErrorAction SilentlyContinue) -join ';' RunAs = $t.Principal.UserId } | ConvertTo-Json -Compress register: pre - name: Compute on-disk task file path (C:\Windows\System32\Tasks\...) ansible.windows.win_powershell: script: | $tp='{{ task_path }}'.Trim() if (-not $tp.EndsWith('\')) { $tp += '\' } $rel = $tp.Trim('\') -replace '\\','\' if ([string]::IsNullOrEmpty($rel)) { $rel = '' } $p = Join-Path 'C:\Windows\System32\Tasks' (Join-Path $rel '{{ task_name }}') $p register: taskfile - name: Show ACL on the task file ansible.windows.win_powershell: script: | $p='{{ taskfile.stdout | trim }}' if (Test-Path $p) { (Get-Acl $p).Access | Select IdentityReference,FileSystemRights,AccessControlType | Out-String } else { "MISSING: $p" } register: aclinfo - name: Ensure JEA RunAs group can Start (RX,WD) the task file (adjust your group!) ansible.windows.win_command: > icacls "{{ taskfile.stdout | trim }}" /grant "TS-LABS25\SEC-PatchOperators:(RX,WD)" register: aclgrant changed_when: "'processed' in (aclgrant.stdout | default(''))" failed_when: false - name: Enable task if disabled 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: enabled failed_when: false - name: Start via schtasks.exe (most reliable under JEA) ansible.windows.win_command: > schtasks /Run /TN "{{ task_path }}{{ task_name }}" register: start_out failed_when: false changed_when: > (start_out.rc | default(1)) == 0 or ('SUCCESS' in (start_out.stdout | default(''))) - name: Confirm it actually started (LastRunTime changed) 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 retries: 20 delay: 15 until: > (started.stdout | default('') | length > 0) and (started.stdout != ((pre.stdout | default('{}') | from_json).LastRunTime)) - name: Poll to finish (Ready/Disabled) and success (0 or 3010) 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: info failed_when: false retries: "{{ finish_retries }}" delay: "{{ poll_delay }}" until: > (info.stdout | default('') | length > 0) and ((info.stdout | from_json).State in ['Ready','Disabled']) and (((info.stdout | from_json).LastTaskResult | int) in [0,3010]) - name: Debug summary ansible.builtin.debug: msg: - "RunAs (JEA): {{ whoami.stdout | default('n/a') }}" - "Task file: {{ taskfile.stdout | trim }}" - "ACL before grant (snippet): {{ aclinfo.stdout | default('n/a') | regex_replace('\\s+$','') }}" - "Start output rc/stdout: {{ start_out.rc | default('n/a') }} / {{ start_out.stdout | default('') | trim }}" - "Final State/Result: {{ (info.stdout | from_json).State }} / {{ (info.stdout | from_json).LastTaskResult }}"