diff --git a/patch-dc-controllers.yaml b/patch-dc-controllers.yaml index c222ba6..a9c99e4 100644 --- a/patch-dc-controllers.yaml +++ b/patch-dc-controllers.yaml @@ -1,118 +1,17 @@ --- -- name: Diagnose & start patch task via JEA-PatchOps - hosts: domain_controllers +- name: Patch Windows DCs using PowerShell via JEA + hosts: windows 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: Search for updates + win_shell: Get-WindowsUpdate + register: search_output - - 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: Install updates + win_shell: Install-WindowsUpdate -AcceptAll -AutoReboot + register: install_output - - 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: Reboot the system + win_shell: Restart-Computer -Force + when: install_output.stdout | search("RebootRequired") - - 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 }}"