Implemented Trigger Repetition (#32938)

* Implemented Trigger Repetition

* Refactorings and integration tests for Trigger Repetition

* Suggestions of first review

* Changes of second review
This commit is contained in:
David Vilar Benet 2018-01-11 23:08:50 +01:00 committed by Jordan Borean
parent bd989b3088
commit 57ff84251e
4 changed files with 360 additions and 41 deletions

View file

@ -198,6 +198,15 @@ Function Compare-Properties($property_name, $parent_property, $map, $enum_map=$n
return ,$changes
}
Function Set-PropertyForComObject($com_object, $name, $arg, $value) {
$com_name = Convert-SnakeToPascalCase -snake $arg
try {
$com_object.$com_name = $value
} catch {
Fail-Json -obj $result -message "failed to set $name property '$com_name' to '$value': $($_.Exception.Message)"
}
}
Function Compare-PropertyList {
Param(
$collection, # the collection COM object to manipulate, this must contains the Create method
@ -266,11 +275,21 @@ Function Compare-PropertyList {
# we have more properties than before,just add to the new
# properties list
$diff_list = [System.Collections.ArrayList]@()
foreach ($property_arg in $total_args) {
if ($new_property.ContainsKey($property_arg)) {
$com_name = Convert-SnakeToPascalCase -snake $property_arg
$property_value = $new_property.$property_arg
[void]$diff_list.Add("+$com_name=$property_value")
if ($property_value -is [Hashtable]) {
foreach ($sub_property_arg in $property_value.Keys) {
$sub_com_name = Convert-SnakeToPascalCase -snake $sub_property_arg
$sub_property_value = $property_value.$sub_property_arg
[void]$diff_list.Add("+$com_name.$sub_com_name=$sub_property_value")
}
} else {
[void]$diff_list.Add("+$com_name=$property_value")
}
}
}
@ -285,7 +304,16 @@ Function Compare-PropertyList {
if ($new_property.ContainsKey($property_arg)) {
$com_name = Convert-SnakeToPascalCase -snake $property_arg
$property_value = $new_property.$property_arg
[void]$diff_list.Add("+$com_name=$property_value")
if ($property_value -is [Hashtable]) {
foreach ($sub_property_arg in $property_value.Keys) {
$sub_com_name = Convert-SnakeToPascalCase -snake $sub_property_arg
$sub_property_value = $property_value.$sub_property_arg
[void]$diff_list.Add("+$com_name.$sub_com_name=$sub_property_value")
}
} else {
[void]$diff_list.Add("+$com_name=$property_value")
}
}
}
} else {
@ -295,14 +323,31 @@ Function Compare-PropertyList {
[void]$diff_list.Add("+Type=$type")
foreach ($property_arg in $total_args) {
$com_name = Convert-SnakeToPascalCase -snake $property_arg
$property_value = $new_property.$property_arg
$existing_value = $existing_property.$com_name
$new_value = $new_property.$property_arg
if ($existing_value -ne $null) {
[void]$diff_list.Add("-$com_name=$existing_value")
}
if ($new_value -ne $null) {
[void]$diff_list.Add("+$com_name=$new_value")
if ($property_value -is [Hashtable]) {
foreach ($sub_property_arg in $property_value.Keys) {
$sub_property_value = $property_value.$sub_property_arg
$sub_com_name = Convert-SnakeToPascalCase -snake $sub_property_arg
$sub_existing_value = $existing_property.$com_name.$sub_com_name
if ($sub_property_value -ne $null) {
[void]$diff_list.Add("+$com_name.$sub_com_name=$sub_property_value")
}
if ($sub_existing_value -ne $null) {
[void]$diff_list.Add("-$com_name.$sub_com_name=$sub_existing_value")
}
}
} else {
if ($property_value -ne $null) {
[void]$diff_list.Add("+$com_name=$property_value")
}
if ($existing_value -ne $null) {
[void]$diff_list.Add("-$com_name=$existing_value")
}
}
}
}
@ -313,15 +358,27 @@ Function Compare-PropertyList {
$diff_list = [System.Collections.ArrayList]@()
foreach ($property_arg in $total_args) {
$new_value = $new_property.$property_arg
$com_name = Convert-SnakeToPascalCase -snake $property_arg
$property_value = $new_property.$property_arg
$existing_value = $existing_property.$com_name
if ($property_value -is [Hashtable]) {
foreach ($sub_property_arg in $property_value.Keys) {
$sub_property_value = $property_value.$sub_property_arg
if ($sub_property_value -ne $null) {
$sub_com_name = Convert-SnakeToPascalCase -snake $sub_property_arg
$sub_existing_value = $existing_property.$com_name.$sub_com_name
if ($new_value -ne $null) {
if ($new_value -cne $existing_value) {
[void]$diff_list.Add("-$com_name=$existing_value")
[void]$diff_list.Add("+$com_name=$new_value")
if ($sub_property_value -cne $sub_existing_value) {
[void]$diff_list.Add("-$com_name.$sub_com_name=$sub_existing_value")
[void]$diff_list.Add("+$com_name.$sub_com_name=$sub_property_value")
}
}
}
} elseif ($property_value -ne $null -and $property_value -cne $existing_value) {
[void]$diff_list.Add("-$com_name=$existing_value")
[void]$diff_list.Add("+$com_name=$property_value")
}
}
@ -334,13 +391,18 @@ Function Compare-PropertyList {
$new_object = $collection.Create($type)
foreach ($property_arg in $total_args) {
$new_value = $new_property.$property_arg
if ($new_value -ne $null) {
if ($new_value -is [Hashtable]) {
$com_name = Convert-SnakeToPascalCase -snake $property_arg
try {
$new_object.$com_name = $new_value
} catch {
Fail-Json -obj $result -message "failed to set $property_name property '$com_name' to '$new_value': $($_.Exception.Message)"
$new_object_property = $new_object.$com_name
foreach ($key in $new_value.Keys) {
$value = $new_value.$key
if ($value -ne $null) {
Set-PropertyForComObject -com_object $new_object_property -name $property_name -arg $key -value $value
}
}
} elseif ($new_value -ne $null) {
Set-PropertyForComObject -com_object $new_object -name $property_name -arg $property_arg -value $new_value
}
}
}
@ -543,52 +605,51 @@ Function Compare-Triggers($task_definition) {
$task_triggers.Clear()
}
# TODO: solve repetition, takes in a COM object
$map = @{
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_BOOT = @{
mandatory = @()
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary')
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_DAILY = @{
mandatory = @('start_boundary')
optional = @('days_interval', 'enabled', 'end_boundary', 'execution_time_limit', 'random_delay')
optional = @('days_interval', 'enabled', 'end_boundary', 'execution_time_limit', 'random_delay', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_EVENT = @{
mandatory = @('subscription')
# TODO: ValueQueries is a COM object
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary')
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_IDLE = @{
mandatory = @()
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'start_boundary')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'start_boundary', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_LOGON = @{
mandatory = @()
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary', 'user_id')
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary', 'user_id', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_MONTHLYDOW = @{
mandatory = @('start_boundary')
optional = @('days_of_week', 'enabled', 'end_boundary', 'execution_time_limit', 'months_of_year', 'random_delay', 'run_on_last_week_of_month', 'weeks_of_month')
optional = @('days_of_week', 'enabled', 'end_boundary', 'execution_time_limit', 'months_of_year', 'random_delay', 'run_on_last_week_of_month', 'weeks_of_month', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_MONTHLY = @{
mandatory = @('days_of_month', 'start_boundary')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'months_of_year', 'random_delay', 'run_on_last_day_of_month', 'start_boundary')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'months_of_year', 'random_delay', 'run_on_last_day_of_month', 'start_boundary', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_REGISTRATION = @{
mandatory = @()
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary')
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'start_boundary', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_TIME = @{
mandatory = @('start_boundary')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'random_delay')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'random_delay', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_WEEKLY = @{
mandatory = @('days_of_week', 'start_boundary')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'random_delay', 'weeks_interval')
optional = @('enabled', 'end_boundary', 'execution_time_limit', 'random_delay', 'weeks_interval', 'repetition')
}
[TASK_TRIGGER_TYPE2]::TASK_TRIGGER_SESSION_STATE_CHANGE = @{
mandatory = @('days_of_week', 'start_boundary')
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'state_change', 'user_id')
optional = @('delay', 'enabled', 'end_boundary', 'execution_time_limit', 'state_change', 'user_id', 'repetition')
}
}
$changes = Compare-PropertyList -collection $task_triggers -property_name "trigger" -new $triggers -existing $existing_triggers -map $map -enum TASK_TRIGGER_TYPE2
@ -614,6 +675,17 @@ Function Test-TaskExists($task_folder, $name) {
return $task
}
Function Test-XmlDurationFormat($key, $value) {
# validate value is in the Duration Data Type format
# PnYnMnDTnHnMnS
try {
$time_span = [System.Xml.XmlConvert]::ToTimeSpan($value)
return $time_span
} catch [System.FormatException] {
Fail-Json -obj $result -message "trigger option '$key' must be in the XML duration format but was '$value'"
}
}
######################################
### VALIDATION/BUILDING OF OPTIONS ###
######################################
@ -774,15 +846,27 @@ for ($i = 0; $i -lt $triggers.Count; $i++) {
$time_properties = @('execution_time_limit', 'delay', 'random_delay')
foreach ($property_name in $time_properties) {
# validate the duration is in the Duration Data Type format
# PnYnMnDTnHnMnS
if ($trigger.ContainsKey($property_name)) {
$time_span = $trigger.$property_name
try {
[void][System.Xml.XmlConvert]::ToTimeSpan($time_span)
} catch [System.FormatException] {
Fail-Json -obj $result -message "trigger option '$property_name' must be in the XML duration format but was '$time_span'"
}
Test-XmlDurationFormat -key $property_name -value $time_span
}
}
if ($trigger.ContainsKey("repetition")) {
$trigger.repetition = ConvertTo-HashtableFromPsCustomObject -object $trigger.repetition
$interval_timespan = $null
if ($trigger.repetition.ContainsKey("interval") -and $trigger.repetition.interval -ne $null) {
$interval_timespan = Test-XmlDurationFormat -key "interval" -value $trigger.repetition.interval
}
$duration_timespan = $null
if ($trigger.repetition.ContainsKey("duration") -and $trigger.repetition.duration -ne $null) {
$duration_timespan = Test-XmlDurationFormat -key "duration" -value $trigger.repetition.duration
}
if ($interval_timespan -ne $null -and $duration_timespan -ne $null -and $interval_timespan -gt $duration_timespan) {
Fail-Json -obj $result -message "trigger repetition option 'interval' value '$($trigger.repetition.interval)' must be less than or equal to 'duration' value '$($trigger.repetition.duration)'"
}
}

View file

@ -193,6 +193,14 @@ options:
- The interval of weeks to run on, e.g. C(1) means every week while
C(2) means every other week.
- Optional when C(type=weekly).
repetition:
description:
- Allows you to define the repetition action of the trigger that defines how often the task is run and how long the repetition pattern is repeated
after the task is started.
- It takes in the following keys, C(duration), C(interval), C(stop_at_duration_end)
- C(duration) is how long the pattern is repeated and is written in the ISO 8601 Duration format C(P[n]Y[n]M[n]DT[n]H[n]M[n]S).
- C(interval) is the amount of time between earch restart of the task and is written in the ISO 8601 Duration format C(P[n]Y[n]M[n]DT[n]H[n]M[n]S).
- C(stop_at_duration_end) is a boolean value that indicates if a running instance of the task is stopped at the end of the repetition pattern.
version_added: '2.5'
days_of_week:
description:
@ -488,6 +496,20 @@ EXAMPLES = r'''
win_scheduled_task:
name: TaskToDisable
enabled: no
- name: create a task that will be repeated every minute for five minutes
win_scheduled_task:
name: RepeatedTask
description: open command prompt
actions:
- path: cmd.exe
arguments: /c hostname
triggers:
- type: registration
repetition:
- interval: PT1M
duration: PT5M
stop_at_duration_end: yes
'''
RETURN = r'''

View file

@ -121,3 +121,37 @@
months_of_year: fakemonth
register: fail_trigger_invalid_month_of_year
failed_when: fail_trigger_invalid_month_of_year.msg != "invalid month name 'fakemonth', please specify full month name"
- name: fail trigger repetition with duration in incorrect format
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
triggers:
- type: boot
repetition:
- duration: fake
register: fail_trigger_repetition_invalid_duration
failed_when: fail_trigger_repetition_invalid_duration.msg != "trigger option 'duration' must be in the XML duration format but was 'fake'"
- name: fail trigger repetition with interval in incorrect format
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
triggers:
- type: boot
repetition:
- interval: fake
register: fail_trigger_repetition_invalid_interval
failed_when: fail_trigger_repetition_invalid_interval.msg != "trigger option 'interval' must be in the XML duration format but was 'fake'"
- name: fail trigger repetition option interval greater than duration
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
triggers:
- type: boot
repetition:
- interval: PT5M
duration: PT1M
register: fail_trigger_repetition_interval_greater_than_duration
failed_when: fail_trigger_repetition_interval_greater_than_duration.msg != "trigger repetition option 'interval' value 'PT5M' must be less than or equal to 'duration' value 'PT1M'"

View file

@ -288,6 +288,184 @@
that:
- trigger_monthlydow_again is not changed
- name: create trigger repetition (check mode)
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
actions:
- path: cmd.exe
triggers:
- type: registration
repetition:
- interval: PT1M
duration: PT5M
stop_at_duration_end: yes
register: create_trigger_repetition_check
check_mode: yes
- name: get result of create trigger repetition (check mode)
win_scheduled_task_stat:
path: \
name: '{{test_scheduled_task_name}}'
register: create_trigger_repetition_result_check
- name: assert results of create trigger repetition (check mode)
assert:
that:
- create_trigger_repetition_check is changed
- create_trigger_repetition_result_check.task_exists == True
- create_trigger_repetition_result_check.triggers|count == 1
- create_trigger_repetition_result_check.triggers[0].type == "TASK_TRIGGER_MONTHLYDOW"
- create_trigger_repetition_result_check.triggers[0].enabled == True
- create_trigger_repetition_result_check.triggers[0].start_boundary == "2000-01-01T00:00:01"
- create_trigger_repetition_result_check.triggers[0].end_boundary == None
- create_trigger_repetition_result_check.triggers[0].weeks_of_month == "1,2"
- create_trigger_repetition_result_check.triggers[0].days_of_week == "monday,wednesday"
- create_trigger_repetition_result_check.triggers[0].repetition.interval == None
- create_trigger_repetition_result_check.triggers[0].repetition.duration == None
- create_trigger_repetition_result_check.triggers[0].repetition.stop_at_duration_end == False
- name: create trigger repetition
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
actions:
- path: cmd.exe
triggers:
- type: registration
repetition:
- interval: PT1M
duration: PT5M
stop_at_duration_end: yes
register: create_trigger_repetition
- name: get result of create trigger repetition
win_scheduled_task_stat:
path: \
name: '{{test_scheduled_task_name}}'
register: create_trigger_repetition_result
- name: assert results of create trigger repetition
assert:
that:
- create_trigger_repetition is changed
- create_trigger_repetition_result.task_exists == True
- create_trigger_repetition_result.triggers|count == 1
- create_trigger_repetition_result.triggers[0].type == "TASK_TRIGGER_REGISTRATION"
- create_trigger_repetition_result.triggers[0].enabled == True
- create_trigger_repetition_result.triggers[0].start_boundary == None
- create_trigger_repetition_result.triggers[0].end_boundary == None
- create_trigger_repetition_result.triggers[0].repetition.interval == "PT1M"
- create_trigger_repetition_result.triggers[0].repetition.duration == "PT5M"
- create_trigger_repetition_result.triggers[0].repetition.stop_at_duration_end == True
- name: create trigger repetition (idempotent)
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
actions:
- path: cmd.exe
triggers:
- type: registration
repetition:
- interval: PT1M
duration: PT5M
stop_at_duration_end: yes
register: create_trigger_repetition_again
- name: assert results of create trigger repetition (idempotent)
assert:
that:
- create_trigger_repetition_again is not changed
- name: change trigger repetition (check mode)
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
actions:
- path: cmd.exe
triggers:
- type: registration
repetition:
- interval: PT10M
duration: PT20M
stop_at_duration_end: no
register: change_trigger_repetition_check
check_mode: yes
- name: get result of change trigger repetition (check mode)
win_scheduled_task_stat:
path: \
name: '{{test_scheduled_task_name}}'
register: change_trigger_repetition_result_check
- name: assert results of change trigger repetition (check mode)
assert:
that:
- change_trigger_repetition_check is changed
- change_trigger_repetition_result_check.task_exists == True
- change_trigger_repetition_result_check.triggers|count == 1
- change_trigger_repetition_result_check.triggers[0].type == "TASK_TRIGGER_REGISTRATION"
- change_trigger_repetition_result_check.triggers[0].enabled == True
- change_trigger_repetition_result_check.triggers[0].start_boundary == None
- change_trigger_repetition_result_check.triggers[0].end_boundary == None
- change_trigger_repetition_result_check.triggers[0].repetition.interval == "PT1M"
- change_trigger_repetition_result_check.triggers[0].repetition.duration == "PT5M"
- change_trigger_repetition_result_check.triggers[0].repetition.stop_at_duration_end == True
- name: change trigger repetition
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
actions:
- path: cmd.exe
triggers:
- type: registration
repetition:
- interval: PT10M
duration: PT20M
stop_at_duration_end: no
register: change_trigger_repetition
- name: get result of change trigger repetition
win_scheduled_task_stat:
path: \
name: '{{test_scheduled_task_name}}'
register: change_trigger_repetition_result
- name: assert results of change trigger repetition
assert:
that:
- change_trigger_repetition is changed
- change_trigger_repetition_result.task_exists == True
- change_trigger_repetition_result.triggers|count == 1
- change_trigger_repetition_result.triggers[0].type == "TASK_TRIGGER_REGISTRATION"
- change_trigger_repetition_result.triggers[0].enabled == True
- change_trigger_repetition_result.triggers[0].start_boundary == None
- change_trigger_repetition_result.triggers[0].end_boundary == None
- change_trigger_repetition_result.triggers[0].repetition.interval == "PT10M"
- change_trigger_repetition_result.triggers[0].repetition.duration == "PT20M"
- change_trigger_repetition_result.triggers[0].repetition.stop_at_duration_end == False
- name: change trigger repetition (idempotent)
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
state: present
actions:
- path: cmd.exe
triggers:
- type: registration
repetition:
- interval: PT10M
duration: PT20M
stop_at_duration_end: no
register: change_trigger_repetition_again
- name: assert results of change trigger repetition (idempotent)
assert:
that:
- change_trigger_repetition_again is not changed
- name: create task with multiple triggers (check mode)
win_scheduled_task:
name: '{{test_scheduled_task_name}}'
@ -321,12 +499,13 @@
- create_multiple_triggers_check is changed
- create_multiple_triggers_result_check.task_exists == True
- create_multiple_triggers_result_check.triggers|count == 1
- create_multiple_triggers_result_check.triggers[0].type == "TASK_TRIGGER_MONTHLYDOW"
- create_multiple_triggers_result_check.triggers[0].type == "TASK_TRIGGER_REGISTRATION"
- create_multiple_triggers_result_check.triggers[0].enabled == True
- create_multiple_triggers_result_check.triggers[0].start_boundary == "2000-01-01T00:00:01"
- create_multiple_triggers_result_check.triggers[0].start_boundary == None
- create_multiple_triggers_result_check.triggers[0].end_boundary == None
- create_multiple_triggers_result_check.triggers[0].weeks_of_month == "1,2"
- create_multiple_triggers_result_check.triggers[0].days_of_week == "monday,wednesday"
- create_multiple_triggers_result_check.triggers[0].repetition.interval == "PT10M"
- create_multiple_triggers_result_check.triggers[0].repetition.duration == "PT20M"
- create_multiple_triggers_result_check.triggers[0].repetition.stop_at_duration_end == False
- name: create task with multiple triggers
win_scheduled_task: