diff --git a/lib/ansible/modules/windows/win_scheduled_task.ps1 b/lib/ansible/modules/windows/win_scheduled_task.ps1 index 06cfb3c38b..d3606a5258 100644 --- a/lib/ansible/modules/windows/win_scheduled_task.ps1 +++ b/lib/ansible/modules/windows/win_scheduled_task.ps1 @@ -87,8 +87,10 @@ $executable = Get-AnsibleParam -obj $params -name "executable" -type "str" -alia $frequency = Get-AnsibleParam -obj $params -name "frequency" -type "str" -validateset "once","daily","weekly" -failifempty $present $time = Get-AnsibleParam -obj $params -name "time" -type "str" -failifempty $present -# TODO: We should default to the current user -$user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $present +$user = Get-AnsibleParam -obj $params -name "user" -default "$env:USERDOMAIN\$env:USERNAME" -type "str" +$password = Get-AnsibleParam -obj $params -name "password" -type "str" +$runlevel = Get-AnsibleParam -obj $params -name "runlevel" -default "limited" -type "str" -validateset "limited", "highest" +$store_password = Get-AnsibleParam -obj $params -name "store_password" -default $true -type "bool" $weekly = $frequency -eq "weekly" $days_of_week = Get-AnsibleParam -obj $params -name "days_of_week" -type "str" -failifempty $weekly @@ -165,7 +167,23 @@ try { Exit-Json $result } - $principal = New-ScheduledTaskPrincipal -UserId "$user" -LogonType ServiceAccount + # Handle RunAs/RunLevel options for the task + + if ($store_password) { + # Specify direct credential and run-level values to add to Register-ScheduledTask + $registerRunOptionParams = @{ + User = $user + RunLevel = $runlevel + } + if ($password) { + $registerRunOptionParams.Password = $password + } + } + else { + # Create a ScheduledTaskPrincipal for the task to run under + $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType S4U -RunLevel $runlevel -Id Author + $registerRunOptionParams = @{Principal = $principal} + } if ($enabled){ $settings = New-ScheduledTaskSettingsSet @@ -186,7 +204,7 @@ try { $pathResults = Invoke-TaskPathCheck -Path $path if (-not $check_mode) { - Register-ScheduledTask -Action $action -Trigger $trigger -TaskName $name -Description $description -TaskPath $path -Settings $settings -Principal $principal + Register-ScheduledTask -Action $action -Trigger $trigger -TaskName $name -Description $description -TaskPath $path -Settings $settings @registerRunOptionParams } $result.changed = $true @@ -200,7 +218,15 @@ try { # Check task path prior to registering $pathResults = Invoke-TaskPathCheck -Path $path - if ($task.Description -eq $description -and $task.TaskName -eq $name -and $task.TaskPath -eq $path -and $task.Actions.Execute -eq $executable -and $taskState -eq $enabled -and $task.Principal.UserId -eq $user) { + if ((!$store_password -and $task.Principal.LogonType -in @("S4U", "ServiceAccount")) -or ($store_password -and $task.Principal.LogonType -notin @("S4U", "Password") -and !$password)) { + $passwordStoreConsistent = $true + } + else { + $passwordStoreConsistent = $false + } + + if ($task.Description -eq $description -and $task.TaskName -eq $name -and $task.TaskPath -eq $path -and $task.Actions.Execute -eq $executable -and + $taskState -eq $enabled -and $task.Principal.UserId -eq $user -and $task.Principal.RunLevel -eq $runlevel -and $passwordStoreConsistent) { # No change in the task $result.msg = "No change in task $name" } @@ -209,7 +235,7 @@ try { if (-not $check_mode) { $oldPathResults = Invoke-TaskPathCheck -Path $task.TaskPath -Remove - Register-ScheduledTask -Action $action -Trigger $trigger -TaskName $name -Description $description -TaskPath $path -Settings $settings -Principal $principal + Register-ScheduledTask -Action $action -Trigger $trigger -TaskName $name -Description $description -TaskPath $path -Settings $settings @registerRunOptionParams } $result.changed = $true $result.msg = "Updated task $name" diff --git a/lib/ansible/modules/windows/win_scheduled_task.py b/lib/ansible/modules/windows/win_scheduled_task.py index 82c9038bda..70e4608b68 100644 --- a/lib/ansible/modules/windows/win_scheduled_task.py +++ b/lib/ansible/modules/windows/win_scheduled_task.py @@ -57,7 +57,27 @@ options: - absent user: description: - - User to run scheduled task as + - User to run the scheduled task as; defaults to the current user + default: DOMAIN\user + password: + description: + - Password for the user account to run the scheduled task as. This is required for running a task without the user being + logged in, excluding Windows built-in service accounts. This should be used for specifying credentials during initial + task creation, and changing stored user credentials, as setting this value will cause the task to be recreated. + version_added: "2.4" + runlevel: + description: + - The level of user rights used to run the task + default: limited + choices: + - limited + - highest + version_added: "2.4" + store_password: + description: + - Store the password for the user running the task. If C(false), the task will only have access to local resources. + default: true + version_added: "2.4" executable: description: - Command the scheduled task should execute @@ -99,4 +119,45 @@ EXAMPLES = r''' state: present enabled: yes user: SYSTEM + +- name: Create a task to run a PowerShell script as NETWORK SERVICE at the highest user rights level + win_scheduled_task: + name: TaskName2 + description: Run a PowerShell script + executable: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe + arguments: -ExecutionPolicy Unrestricted -NonInteractive -File C:\TestDir\Test.ps1 + time: 6pm + frequency: once + state: present + enabled: yes + user: NETWORK SERVICE + runlevel: highest + +- name: Change the above task to run under a domain user account, storing credentials for the task + win_scheduled_task: + name: TaskName2 + description: Run a PowerShell script + executable: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe + arguments: -ExecutionPolicy Unrestricted -NonInteractive -File C:\TestDir\Test.ps1 + time: 6pm + frequency: once + state: present + enabled: yes + user: DOMAIN\user + password: passwordGoesHere + runlevel: highest + +- name: Change the above task again, choosing not to store the password for the account + win_scheduled_task: + name: TaskName2 + description: Run a PowerShell script + executable: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe + arguments: -ExecutionPolicy Unrestricted -NonInteractive -File C:\TestDir\Test.ps1 + time: 6pm + frequency: once + state: present + enabled: yes + user: DOMAIN\user + runlevel: highest + store_password: no ''' diff --git a/test/integration/targets/win_scheduled_task/tasks/tests.yml b/test/integration/targets/win_scheduled_task/tasks/tests.yml index 15e1cfd1ba..db62136b10 100644 --- a/test/integration/targets/win_scheduled_task/tasks/tests.yml +++ b/test/integration/targets/win_scheduled_task/tasks/tests.yml @@ -211,3 +211,124 @@ that: - remove_scheduled_task_new_path_1.msg == 'Task does not exist' when: in_check_mode + + +# Test scheduled task RunAs and RunLevel options + +- name: Remove potentially leftover run options task 1 + win_scheduled_task: &wstr1_absent + name: Ansible Test Run Options 1 + state: absent + + +- name: Add scheduled task run options 1 + win_scheduled_task: &wstr1_present + name: Ansible Test Run Options 1 + description: A test of run options functionality + executable: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe + arguments: -ExecutionPolicy Unrestricted -NonInteractive -File C:\TestDir\Test.ps1 + time: "6pm" + frequency: once + state: present + enabled: yes + user: SYSTEM + register: add_scheduled_task_run_options_1 + +- name: Test add_scheduled_task_run_options_1 + assert: + that: + - add_scheduled_task_run_options_1.changed == true + - add_scheduled_task_run_options_1.exists == false + + +- name: Execute run options tests for normal mode only (expects scheduled task) + when: not in_check_mode + block: + + - name: Change scheduled task run options user + win_scheduled_task: + <<: *wstr1_present + user: NETWORK SERVICE + register: change_scheduled_task_run_options_user + + - name: Test change_scheduled_task_run_options_user + assert: + that: + - change_scheduled_task_run_options_user.changed == true + - change_scheduled_task_run_options_user.exists == true + + + - name: Change scheduled task run options user (again) + win_scheduled_task: + <<: *wstr1_present + user: NETWORK SERVICE + register: change_scheduled_task_run_options_user_again + + - name: Test change_scheduled_task_run_options_user_again + assert: + that: + - change_scheduled_task_run_options_user_again.changed == false + - change_scheduled_task_run_options_user_again.exists == true + + + - name: Change scheduled task run options run level + win_scheduled_task: + <<: *wstr1_present + user: NETWORK SERVICE + runlevel: highest + register: change_scheduled_task_run_options_runlevel + + - name: Test change_scheduled_task_run_options_runlevel + assert: + that: + - change_scheduled_task_run_options_runlevel.changed == true + - change_scheduled_task_run_options_runlevel.exists == true + + + - name: Change scheduled task run options run level (again) + win_scheduled_task: + <<: *wstr1_present + user: NETWORK SERVICE + runlevel: highest + register: change_scheduled_task_run_options_runlevel_again + + - name: Test change_scheduled_task_run_options_runlevel_again + assert: + that: + - change_scheduled_task_run_options_runlevel_again.changed == false + - change_scheduled_task_run_options_runlevel_again.exists == true + + + # Should ignore change as account being tested is a built-in service account + - name: Change scheduled task run options store password + win_scheduled_task: + <<: *wstr1_present + user: NETWORK SERVICE + runlevel: highest + store_password: no + register: change_scheduled_task_run_options_store_password + + - name: Test change_scheduled_task_run_options_store_password + assert: + that: + - change_scheduled_task_run_options_store_password.changed == false + - change_scheduled_task_run_options_store_password.exists == true + + +- name: Remove scheduled task run options 1 + win_scheduled_task: *wstr1_absent + register: remove_scheduled_task_run_options_1 + +- name: Test remove_scheduled_task_run_options_1 (normal mode) + assert: + that: + - remove_scheduled_task_run_options_1.changed == true + - remove_scheduled_task_run_options_1.exists == true + when: not in_check_mode + +- name: Test remove_scheduled_task_run_options_1 (check-mode) + assert: + that: + - remove_scheduled_task_run_options_1.changed == false + - remove_scheduled_task_run_options_1.exists == false + when: in_check_mode