From e8aebaff3e0619f8399fb8831997a5dce2ef4d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Wed, 25 Jul 2018 21:32:59 +0000 Subject: [PATCH 01/30] ScheduledTask: Add ExectuteAsGMSA parameter to support running scheduled tasks with Group Managed Service Accounts --- .../MSFT_ScheduledTask.psm1 | 54 +++++++++++++++++-- .../MSFT_ScheduledTask.schema.mof | 1 + .../en-US/MSFT_ScheduledTask.strings.psd1 | 1 + ...llTaskOnceAsGroupManagedServiceAccount.ps1 | 37 +++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 4210a65e..f0b84017 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -73,7 +73,12 @@ $script:localizedData = Get-LocalizedData ` .PARAMETER ExecuteAsCredential The credential this task should execute as. If not specified defaults to running - as the local system account. + as the local system account. Cannot be used in combination with ExecuteAsGMSA. + Not used in Get-TargetResource. + + .PARAMETER ExecuteAsGMSA + The gMSA (Group Managed Service Account) this task should execute as. Cannot be + used in combination with ExecuteAsCredential. Not used in Get-TargetResource. .PARAMETER DaysInterval @@ -267,6 +272,10 @@ function Get-TargetResource [System.Management.Automation.PSCredential] $ExecuteAsCredential, + [Parameter()] + [System.String] + $ExecuteAsGMSA, + [Parameter()] [System.UInt32] $DaysInterval = 1, @@ -503,6 +512,7 @@ function Get-TargetResource ScheduleType = $returnScheduleType RepeatInterval = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Interval ExecuteAsCredential = $task.Principal.UserId + ExecuteAsGMSA = $task.Principal.UserId -replace '^.+\\', $null Enable = $settings.Enabled DaysInterval = $trigger.DaysInterval RandomDelay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.RandomDelay @@ -579,7 +589,11 @@ function Get-TargetResource .PARAMETER ExecuteAsCredential The credential this task should execute as. If not specified defaults to running - as the local system account. + as the local system account. Cannot be used in combination with ExecuteAsGMSA. + + .PARAMETER ExecuteAsGMSA + The gMSA (Group Managed Service Account) this task should execute as. Cannot be + used in combination with ExecuteAsCredential. .PARAMETER DaysInterval Specifies the interval between the days in the schedule. An interval of 1 produces @@ -755,6 +769,10 @@ function Set-TargetResource [System.Management.Automation.PSCredential] $ExecuteAsCredential, + [Parameter()] + [System.String] + $ExecuteAsGMSA, + [Parameter()] [System.UInt32] $DaysInterval = 1, @@ -951,6 +969,13 @@ function Set-TargetResource -ArgumentName EventSubscription } + if ($ExecuteAsCredential -and $ExecuteAsGMSA) + { + New-InvalidArgumentException ` + -Message ($script:localizedData.gMSAandCredentialError) ` + -ArgumentName ExecuteAsGMSA + } + # Configure the action $actionParameters = @{ Execute = $ActionExecutable @@ -1193,7 +1218,12 @@ function Set-TargetResource # Prepare the register arguments $registerArguments = @{} - if ($PSBoundParameters.ContainsKey('ExecuteAsCredential')) + if ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) + { + $registerArguments.Add('User', $ExecuteAsGMSA) + $LogonType = 'Password' + } + elseif ($PSBoundParameters.ContainsKey('ExecuteAsCredential')) { $username = $ExecuteAsCredential.UserName $registerArguments.Add('User', $username) @@ -1343,7 +1373,11 @@ function Set-TargetResource .PARAMETER ExecuteAsCredential The credential this task should execute as. If not specified defaults to running - as the local system account. + as the local system account. Cannot be used in combination with ExecuteAsGMSA. + + .PARAMETER ExecuteAsGMSA + The gMSA (Group Managed Service Account) this task should execute as. Cannot be + used in combination with ExecuteAsCredential. .PARAMETER DaysInterval Specifies the interval between the days in the schedule. An interval of 1 produces @@ -1729,6 +1763,18 @@ function Test-TargetResource $PSBoundParameters['ExecuteAsCredential'] = $username } + if ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) + { + <# + There is a difference in W2012R2 and W2016 behaviour, + W2012R2 returns the gMSA including the DOMAIN prefix, + W2016 returns this without. So to be sure strip off the + domain part in Get & Test. + #> + + $PSBoundParameters['ExecuteAsGMSA'] = $PSBoundParameters.ExecuteAsGMSA -replace '^.+\\', $null + } + $desiredValues = $PSBoundParameters $desiredValues.TaskPath = $TaskPath diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof index 4d40f88a..c1c84bd4 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof @@ -13,6 +13,7 @@ class MSFT_ScheduledTask : OMI_BaseResource [Write, Description("Present if the task should exist, Absent if it should be removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; [Write, Description("True if the task should be enabled, false if it should be disabled")] boolean Enable; [Write, Description("The credential this task should execute as. If not specified defaults to running as the local system account"), EmbeddedInstance("MSFT_Credential")] string ExecuteAsCredential; + [Write, Description("The gMSA (Group Managed Service Account) this task should execute as. Cannot be used in combination with ExecuteAsCredential.")] string ExecuteAsGMSA; [Write, Description("Specifies the interval between the days in the schedule. An interval of 1 produces a daily schedule. An interval of 2 produces an every-other day schedule.")] Uint32 DaysInterval; [Write, Description("Specifies a random amount of time to delay the start time of the trigger. The delay time is a random time between the time the task triggers and the time that you specify in this setting.")] String RandomDelay; [Write, Description("Specifies how long the repetition pattern repeats after the task starts. May be set to `Indefinitely` to specify an indefinite duration.")] String RepetitionDuration; diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 index d811266f..fa4cc27c 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 @@ -11,6 +11,7 @@ ConvertFrom-StringData @' WeeksIntervalError = WeeksInterval must be greater than zero (0) for Weekly schedules. WeeksInterval specified is '{0}'. WeekDayMissingError = At least one weekday must be selected for Weekly schedule. OnEventSubscriptionError = No (valid) XML Event Subscription was provided. This is required when the scheduletype is OnEvent. + gMSAandCredentialError = 'Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two. TriggerCreationError = Error creating new scheduled task trigger. ConfigureTriggerRepetitionMessage = Configuring trigger repetition. RepetitionIntervalError = Repetition interval is set to '{0}' but repetition duration is '{1}'. diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 new file mode 100644 index 00000000..cf798a76 --- /dev/null +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Run As gMSA' + in the folder task folder 'MyTasks' that starts a new powershell process once. + The task will run as the user passed into the ExecuteAsGMSA parameter. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + # Group Managed Service Account must be in the form of DOMAIN\gMSA$ + [Parameter(Mandatory = $true)] + [ValidatePattern('^\w+\\\w+\$$')] + [System.String] + $GroupManagedServiceAccount + ) + + Import-DscResource -ModuleName ComputerManagementDsc + + Node $NodeName + { + ScheduledTask MaintenanceScriptExample + { + TaskName = 'Test task Run As gMSA' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + ActionWorkingPath = (Get-Location).Path + Enable = $true + ExecuteAsGMSA = $GroupManagedServiceAccount + } + } +} From 6eea02bceef8f3ab76db875e1fb794590965b97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Wed, 25 Jul 2018 21:50:37 +0000 Subject: [PATCH 02/30] ScheduledTask: Add parameter ExecuteAsGMSA to Test-TargetResource as well --- .../DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index f0b84017..75d8aa7a 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1554,6 +1554,10 @@ function Test-TargetResource [System.Management.Automation.PSCredential] $ExecuteAsCredential, + [Parameter()] + [System.String] + $ExecuteAsGMSA, + [Parameter()] [System.UInt32] $DaysInterval = 1, From 8a81cad6a133122d78f6fb8ef7931dc39fe464f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Thu, 26 Jul 2018 21:37:50 +0000 Subject: [PATCH 03/30] Add support for gMSA accounts in UPN format --- .../MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 75d8aa7a..f80e8831 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -512,7 +512,7 @@ function Get-TargetResource ScheduleType = $returnScheduleType RepeatInterval = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Interval ExecuteAsCredential = $task.Principal.UserId - ExecuteAsGMSA = $task.Principal.UserId -replace '^.+\\', $null + ExecuteAsGMSA = $task.Principal.UserId -replace '^.+\\|@.+', $null Enable = $settings.Enabled DaysInterval = $trigger.DaysInterval RandomDelay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.RandomDelay @@ -1773,10 +1773,13 @@ function Test-TargetResource There is a difference in W2012R2 and W2016 behaviour, W2012R2 returns the gMSA including the DOMAIN prefix, W2016 returns this without. So to be sure strip off the - domain part in Get & Test. + domain part in Get & Test. This means we either need to + remove everything before \ in the case of the DOMAIN\User + format, or we need to remove everything after @ in case + when the UPN format (User@domain.fqdn) is used. #> - $PSBoundParameters['ExecuteAsGMSA'] = $PSBoundParameters.ExecuteAsGMSA -replace '^.+\\', $null + $PSBoundParameters['ExecuteAsGMSA'] = $PSBoundParameters.ExecuteAsGMSA -replace '^.+\\|@.+', $null } $desiredValues = $PSBoundParameters From fbed8acadfe7aefa3ffc0334dbc5decdc98f812b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Fri, 27 Jul 2018 23:39:04 +0200 Subject: [PATCH 04/30] Adding ConfigurationData hashtable to example 14 --- ...-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 index cf798a76..33061ff0 100644 --- a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/14-RunPowerShellTaskOnceAsGroupManagedServiceAccount.ps1 @@ -12,11 +12,11 @@ Configuration Example [System.String[]] $NodeName = 'localhost', - # Group Managed Service Account must be in the form of DOMAIN\gMSA$ - [Parameter(Mandatory = $true)] - [ValidatePattern('^\w+\\\w+\$$')] + # Group Managed Service Account must be in the form of DOMAIN\gMSA$ or user@domain.fqdn (UPN) + [Parameter()] + [ValidatePattern('^\w+\\\w+\$$|\w+@\w+\.\w+')] [System.String] - $GroupManagedServiceAccount + $GroupManagedServiceAccount = 'DOMAIN\gMSA$' ) Import-DscResource -ModuleName ComputerManagementDsc From 8f48a64e709eca95e883823083c13a9d57a396d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Sun, 29 Jul 2018 18:48:55 +0200 Subject: [PATCH 05/30] Adding unit tests for scheduled tasks running in gMSA context --- .../MSFT_ScheduledTask.psm1 | 3 +- .../en-US/MSFT_ScheduledTask.strings.psd1 | 2 +- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 147 +++++++++++++++++- 3 files changed, 146 insertions(+), 6 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index f80e8831..40f8262e 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1220,7 +1220,8 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) { - $registerArguments.Add('User', $ExecuteAsGMSA) + $username = $ExecuteAsGMSA + $registerArguments.Add('User', $username) $LogonType = 'Password' } elseif ($PSBoundParameters.ContainsKey('ExecuteAsCredential')) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 index fa4cc27c..15b009fe 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 @@ -11,7 +11,7 @@ ConvertFrom-StringData @' WeeksIntervalError = WeeksInterval must be greater than zero (0) for Weekly schedules. WeeksInterval specified is '{0}'. WeekDayMissingError = At least one weekday must be selected for Weekly schedule. OnEventSubscriptionError = No (valid) XML Event Subscription was provided. This is required when the scheduletype is OnEvent. - gMSAandCredentialError = 'Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two. + gMSAandCredentialError = Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two. TriggerCreationError = Error creating new scheduled task trigger. ConfigureTriggerRepetitionMessage = Configuring trigger repetition. RepetitionIntervalError = Repetition interval is set to '{0}' but repetition duration is '{1}'. diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index e3943a7a..0f79bb34 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -1528,7 +1528,7 @@ try Settings = [pscustomobject] @{ Enabled = $true } - } + } } It 'Should return the correct values from Get-TargetResource' { @@ -1577,7 +1577,7 @@ try Settings = [pscustomobject] @{ Enabled = $true } - } + } } It 'Should return the correct values from Get-TargetResource' { @@ -1652,7 +1652,7 @@ try Settings = [pscustomobject] @{ Enabled = $true } - } + } } It 'Should return the correct values from Get-TargetResource' { @@ -1707,7 +1707,7 @@ try Settings = [pscustomobject] @{ Enabled = $true } - } + } } It 'Should return the correct values from Get-TargetResource' { @@ -1728,6 +1728,145 @@ try { Set-TargetResource @testParameters } | Should throw } } + + Context 'When a scheduled task is created using a Group Managed Service Account' { + + $testParameters = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + ExecuteAsGMSA = 'DOMAIN\gMSA$' + ExecuteAsCredential = [pscredential]::new('DEMO\RightUser', (ConvertTo-SecureString 'ExamplePassword' -AsPlainText -Force)) + Verbose = $true + } + + It 'Should return an error when both the ExecuteAsGMSA an ExecuteAsCredential ar specified' { + try + { + Set-TargetResource @testParameters -ErrorVariable duplicateCredential + } + catch + { + # Error from Set-TargetResource expected + } + finally + { + $duplicateCredential.Message | Should -Be "Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two.`r`nParameter name: ExecuteAsGMSA" + } + } + + $testParameters.Remove('ExecuteAsCredential') + + It 'Should call Register-ScheduledTask with the name of the Group Managed Service Account' { + Set-TargetResource @testParameters + Assert-MockCalled -CommandName Register-ScheduledTask -Times 1 -Scope It -ParameterFilter { + $User -eq $testParameters.ExecuteAsGMSA -and $Inputobject.Principal.UserId -eq $testParameters.ExecuteAsGMSA + } + } + + It 'Should set the LogonType to Password when a Group Managed Service Account is used' { + Set-TargetResource @testParameters + Assert-MockCalled -CommandName Register-ScheduledTask -Times 1 -Scope It -ParameterFilter { + $Inputobject.Principal.Logontype -eq 'Password' + } + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + Repetition = @{ + Duration = "PT$([System.TimeSpan]::Parse($testParameters.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParameters.RepeatInterval).TotalMinutes)M" + } + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + Principal = [pscustomobject] @{ + UserId = 'gMSA$' + } + } + } + + It 'Test should return true if the task is in desired state and given gMSA user in DOMAIN\User$ format' { + Test-TargetResource @testParameters | Should -Be $true + } + + $testParameters.ExecuteAsGMSA = 'gMSA$@domain.fqdn' + + It 'Test should return true if the task is in desired state and given gMSA user in UPN format' { + Test-TargetResource @testParameters | Should -Be $true + } + } + + Context 'When a scheduled task Group Managed Service Account is changed' { + $testParameters = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + ExecuteAsGMSA = 'DOMAIN\gMSA$' + Verbose = $true + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + Repetition = @{ + Duration = "PT$([System.TimeSpan]::Parse($testParameters.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParameters.RepeatInterval).TotalMinutes)M" + } + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + Principal = [pscustomobject] @{ + UserId = 'update_gMSA$' + } + } + } + + It 'Should return false on Test-TargetResource if the task is not in desired state and given gMSA user in DOMAIN\User$ format' { + Test-TargetResource @testParameters | Should -Be $false + } + + It 'Should call Set-ScheduledTask using the new Group Managed Service Account' { + Set-TargetResource @testParameters + Assert-MockCalled -CommandName Set-ScheduledTask -Times 1 -Scope It -ParameterFilter { + $Inputobject.Principal.UserId -eq $testParameters.ExecuteAsGMSA + } + } + + It 'Should set the LogonType to Password when a Group Managed Service Account is used' { + Set-TargetResource @testParameters + Assert-MockCalled -CommandName Set-ScheduledTask -Times 1 -Scope It -ParameterFilter { + $Inputobject.Principal.Logontype -eq 'Password' + } + } + } } } #endregion From 8370771889339ddf7ee073173718f8af567c083e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Mon, 30 Jul 2018 13:28:14 +0200 Subject: [PATCH 06/30] Fix to enable: Run whether user is logged on or not, in gMSA context --- .../DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 1 - Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 40f8262e..c503ed96 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1221,7 +1221,6 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) { $username = $ExecuteAsGMSA - $registerArguments.Add('User', $username) $LogonType = 'Password' } elseif ($PSBoundParameters.ContainsKey('ExecuteAsCredential')) diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index 0f79bb34..5eb2e9e0 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -1748,7 +1748,7 @@ try { Set-TargetResource @testParameters -ErrorVariable duplicateCredential } - catch + catch { # Error from Set-TargetResource expected } @@ -1763,7 +1763,7 @@ try It 'Should call Register-ScheduledTask with the name of the Group Managed Service Account' { Set-TargetResource @testParameters Assert-MockCalled -CommandName Register-ScheduledTask -Times 1 -Scope It -ParameterFilter { - $User -eq $testParameters.ExecuteAsGMSA -and $Inputobject.Principal.UserId -eq $testParameters.ExecuteAsGMSA + $User -eq $null -and $Inputobject.Principal.UserId -eq $testParameters.ExecuteAsGMSA } } From 0bb58eea3cb3dfb10b6cb044e5c0d076e4a16603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Mon, 30 Jul 2018 13:28:25 +0200 Subject: [PATCH 07/30] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95311714..d66effcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- ScheduledTask: + - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA + parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) + ## 5.2.0.0 - PowershellExecutionPolicy: From 12d803b0821df9a8fa0c05d2a478a6298dd4afc5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 20 Aug 2018 12:13:20 +0200 Subject: [PATCH 08/30] Add pull request template and issue templates --- .github/ISSUE_TEMPLATE.md | 13 ----- .github/ISSUE_TEMPLATE/General.md | 7 +++ .../ISSUE_TEMPLATE/Problem_with_resource.md | 57 +++++++++++++++++++ .github/ISSUE_TEMPLATE/Resource_proposal.md | 21 +++++++ .github/PULL_REQUEST_TEMPLATE.md | 56 +++++++++++++----- 5 files changed, 126 insertions(+), 28 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/General.md create mode 100644 .github/ISSUE_TEMPLATE/Problem_with_resource.md create mode 100644 .github/ISSUE_TEMPLATE/Resource_proposal.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 4b9bec30..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ - -**Details of the scenario you tried and the problem that is occurring:** - -**The DSC configuration that is using the resource (as detailed as possible):** - -**Version of the Operating System and PowerShell the DSC Target Node is running:** - -**Version of the DSC module you're using, or 'dev' if you're using current dev branch:** diff --git a/.github/ISSUE_TEMPLATE/General.md b/.github/ISSUE_TEMPLATE/General.md new file mode 100644 index 00000000..fbcdf240 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/General.md @@ -0,0 +1,7 @@ +--- +name: General question or documentation update +about: If you have a general question or documentation update suggestion around the resource module. +--- + diff --git a/.github/ISSUE_TEMPLATE/Problem_with_resource.md b/.github/ISSUE_TEMPLATE/Problem_with_resource.md new file mode 100644 index 00000000..2431f65c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Problem_with_resource.md @@ -0,0 +1,57 @@ +--- +name: Problem with a resource +about: If you have a problem, bug, or enhancement with a resource in this resource module. +--- + +#### Details of the scenario you tried and the problem that is occurring + +#### Verbose logs showing the problem + +#### Suggested solution to the issue + +#### The DSC configuration that is used to reproduce the issue (as detailed as possible) +```powershell +# insert configuration here +``` + +#### The operating system the target node is running + + +#### Version and build of PowerShell the target node is running + + +#### Version of the DSC module that was used ('dev' if using current dev branch) diff --git a/.github/ISSUE_TEMPLATE/Resource_proposal.md b/.github/ISSUE_TEMPLATE/Resource_proposal.md new file mode 100644 index 00000000..9f2a069a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Resource_proposal.md @@ -0,0 +1,21 @@ +--- +name: New resource proposal +about: If you have a new resource proposal that you think should be added to this resource module. +--- + +### Description + +### Proposed properties + +### Special considerations or limitations diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f400397c..84e1ea82 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,47 @@ +#### Pull Request (PR) description + -**Pull Request (PR) description** - -**This Pull Request (PR) fixes the following issues:** - +#### This Pull Request (PR) fixes the following issues + -**Task list:** -- [ ] Change details added to Unreleased section of CHANGELOG.md? -- [ ] Added/updated documentation, comment-based help and descriptions in .schema.mof files where appropriate? -- [ ] Examples appropriately updated? -- [ ] New/changed code adheres to [Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md)? -- [ ] [Unit and (optional) Integration tests](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md) created/updated where possible? +#### Task list + +- [ ] Added an entry under the Unreleased section of the change log in the README.md. + Entry should say what was changed, and how that affects users (if applicable). +- [ ] Resource documentation added/updated in README.md. +- [ ] Resource parameter descriptions added/updated in README.md, schema.mof + and comment-based help. +- [ ] Comment-based help added/updated. +- [ ] Localization strings added/updated in all localization files as appropriate. +- [ ] Examples appropriately added/updated. +- [ ] Unit tests added/updated. See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). +- [ ] Integration tests added/updated (where possible). See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). +- [ ] New/changed code adheres to [DSC Resource Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md) and [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md). From 7126e559c307310574f473e60013e88d2af06735 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 23 Aug 2018 21:41:44 +1200 Subject: [PATCH 09/30] Updated PR Template --- .github/PULL_REQUEST_TEMPLATE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 84e1ea82..8f9fed13 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -34,10 +34,10 @@ Change to [x] for each task in the task list that applies to your PR. For those task that don't apply to you PR, leave those as is. --> -- [ ] Added an entry under the Unreleased section of the change log in the README.md. +- [ ] Added an entry under the Unreleased section of the change log in the CHANGELOG.md. Entry should say what was changed, and how that affects users (if applicable). -- [ ] Resource documentation added/updated in README.md. -- [ ] Resource parameter descriptions added/updated in README.md, schema.mof +- [ ] Resource documentation added/updated in README.md in resource folder. +- [ ] Resource parameter descriptions added/updated in schema.mof and comment-based help. - [ ] Comment-based help added/updated. - [ ] Localization strings added/updated in all localization files as appropriate. From 858045f169214ced7ae2ef5801893361d905d3ae Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 23 Aug 2018 22:15:45 +1200 Subject: [PATCH 10/30] Updated per PR comments --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8f9fed13..925594ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -36,7 +36,7 @@ --> - [ ] Added an entry under the Unreleased section of the change log in the CHANGELOG.md. Entry should say what was changed, and how that affects users (if applicable). -- [ ] Resource documentation added/updated in README.md in resource folder. +- [ ] Resource documentation added/updated in README.md in the resource folder. - [ ] Resource parameter descriptions added/updated in schema.mof and comment-based help. - [ ] Comment-based help added/updated. From c8d0aa79eae491f2997cdd002dc81a59372ad2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Mon, 24 Sep 2018 18:49:12 +0200 Subject: [PATCH 11/30] Update formatting based on code review. --- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index 5eb2e9e0..b9071ee5 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -1730,7 +1730,6 @@ try } Context 'When a scheduled task is created using a Group Managed Service Account' { - $testParameters = @{ TaskName = 'Test task' TaskPath = '\Test\' @@ -1800,13 +1799,13 @@ try } } - It 'Test should return true if the task is in desired state and given gMSA user in DOMAIN\User$ format' { + It 'Should return true if the task is in desired state and given gMSA user in DOMAIN\User$ format' { Test-TargetResource @testParameters | Should -Be $true } $testParameters.ExecuteAsGMSA = 'gMSA$@domain.fqdn' - It 'Test should return true if the task is in desired state and given gMSA user in UPN format' { + It 'Should return true if the task is in desired state and given gMSA user in UPN format' { Test-TargetResource @testParameters | Should -Be $true } } From 31e840784a604ad5cd701432820402c63f0ed5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Fri, 28 Sep 2018 08:51:13 +0200 Subject: [PATCH 12/30] Add support to set synchronize across time zone --- .../MSFT_ScheduledTask.psm1 | 100 +++++++++++++++++- .../MSFT_ScheduledTask.schema.mof | 1 + .../en-US/MSFT_ScheduledTask.strings.psd1 | 1 + 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index c503ed96..35a9816c 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -259,6 +259,10 @@ function Get-TargetResource [System.DateTime] $StartTime = [System.DateTime]::Today, + [Parameter()] + [System.Boolean] + $SynchronizeAcrossTimeZone = $false, + [Parameter()] [System.String] [ValidateSet('Present', 'Absent')] @@ -493,17 +497,20 @@ function Get-TargetResource if ($startAt) { - $startAt = [System.DateTime] $startAt + $stringSynchronizeAcrossTimeZone = Get-DateTimeString -Date $startAt -SynchronizeAcrossTimeZone $true + $returnSynchronizeAcrossTimeZone = $startAt -eq $stringSynchronizeAcrossTimeZone } else { $startAt = $StartTime + $returnSynchronizeAcrossTimeZone = $false } return @{ TaskName = $task.TaskName TaskPath = $task.TaskPath StartTime = $startAt + SynchronizeAcrossTimeZone = $returnSynchronizeAcrossTimeZone Ensure = 'Present' Description = $task.Description ActionExecutable = $action.Execute @@ -756,6 +763,10 @@ function Set-TargetResource [System.DateTime] $StartTime = [System.DateTime]::Today, + [Parameter()] + [System.Boolean] + $SynchronizeAcrossTimeZone = $false, + [Parameter()] [System.String] [ValidateSet('Present', 'Absent')] @@ -976,6 +987,12 @@ function Set-TargetResource -ArgumentName ExecuteAsGMSA } + if($SynchronizeAcrossTimeZone -and ($ScheduleType -notin @('Once', 'Daily', 'Weekly'))) { + New-InvalidArgumentException ` + -Message ($script:localizedData.SynchronizeAcrossTimeZoneInvalidScheduleType) ` + -ArgumentName SynchronizeAcrossTimeZone + } + # Configure the action $actionParameters = @{ Execute = $ActionExecutable @@ -1304,6 +1321,29 @@ function Set-TargetResource $scheduledTask.Description = $Description } + if($scheduledTask.Triggers[0].StartBoundary) + { + <# + The way New-ScheduledTaskTrigger writes the StartBoundary has issues because it does not take + the setting "Synchronize across time zones" in consideration. What happens if synchronize across + time zone is enabled in the scheduled task GUI is that the time is written like this: + + 2018-09-27T18:45:08+02:00 + + When the setting synchronize across time zones is disabled, the time is written as: + + 2018-09-27T18:45:08 + + The problem in New-ScheduledTaskTrigger is that it always writes the time the format that + includes the full timezone offset (W2016 behaviour, W2012R2 does it the other way around). + Which means "Synchronize across time zones" is enabled by default. To prevent that, we are + overwriting the StartBoundary here to insert the time in the format we want it, so we can enable + or disable "Synchronize across time zones". + #> + + $scheduledTask.Triggers[0].StartBoundary = Get-DateTimeString -Date $StartTime -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone + } + if ($currentValues.Ensure -eq 'Present') { # Updating the scheduled task @@ -1541,6 +1581,10 @@ function Test-TargetResource [System.DateTime] $StartTime = [System.DateTime]::Today, + [Parameter()] + [System.Boolean] + $SynchronizeAcrossTimeZone = $false, + [Parameter()] [System.String] [ValidateSet('Present', 'Absent')] @@ -1744,8 +1788,18 @@ function Test-TargetResource $PSBoundParameters['RestartInterval'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RestartInterval).ToString() } + if ($ScheduleType -in @('Once', 'Daily', 'Weekly')) + { + $PSBoundParameters['StartTime'] = Get-DateTimeString -Date $StartTime -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone + } + $currentValues = Get-TargetResource @PSBoundParameters + foreach($key in $currentValues.Keys) + { + Write-Verbose "Current key $key has value $($currentvalues[$key])" + } + Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') @@ -1785,6 +1839,11 @@ function Test-TargetResource $desiredValues = $PSBoundParameters $desiredValues.TaskPath = $TaskPath + foreach($key in $desiredValues.Keys) + { + Write-Verbose "Desired Key $key has value $($desiredValues[$key])" + } + Write-Verbose -Message ($script:localizedData.TestingDscParameterStateMessage) return Test-DscParameterState -CurrentValues $currentValues -DesiredValues $desiredValues @@ -1944,3 +2003,42 @@ function Disable-ScheduledTask $existingTask.Settings.Enabled = $false $null = $existingTask | Register-ScheduledTask @PSBoundParameters -Force } + +<# + .SYNOPSIS + Returns a formatted datetime string for use in ScheduledTask resource + + .PARAMETER Date + The date to format + + .PARAMETER SynchronizeAcrossTimeZone + Boolean to specifiy if the returned string is formatted in synchronize + across time zone format. +#> +Function Get-DateTimeString +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [DateTime] + $Date, + + [Parameter(Mandatory = $true)] + [Boolean] + $SynchronizeAcrossTimeZone + ) + + $format = (Get-Culture).DateTimeFormat.SortableDateTimePattern + + if($SynchronizeAcrossTimeZone) + { + $returnDate = (Get-Date -Date $Date -Format $format) + (Get-Date -Format 'zzz') + } + else + { + $returnDate = Get-Date -Date $Date -Format $format + } + + $returnDate +} diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof index c1c84bd4..563b251f 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof @@ -10,6 +10,7 @@ class MSFT_ScheduledTask : OMI_BaseResource [Write, Description("When should the task be executed"), ValueMap{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}, Values{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}] string ScheduleType; [Write, Description("How many units (minutes, hours, days) between each run of this task?")] String RepeatInterval; [Write, Description("The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks")] DateTime StartTime; + [Write, Description("True to enable Synchronize across time zones, false to disable this setting. Defaults to false.")] boolean SynchronizeAcrossTimeZone; [Write, Description("Present if the task should exist, Absent if it should be removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; [Write, Description("True if the task should be enabled, false if it should be disabled")] boolean Enable; [Write, Description("The credential this task should execute as. If not specified defaults to running as the local system account"), EmbeddedInstance("MSFT_Credential")] string ExecuteAsCredential; diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 index 15b009fe..9d3b4309 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 @@ -12,6 +12,7 @@ ConvertFrom-StringData @' WeekDayMissingError = At least one weekday must be selected for Weekly schedule. OnEventSubscriptionError = No (valid) XML Event Subscription was provided. This is required when the scheduletype is OnEvent. gMSAandCredentialError = Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two. + SynchronizeAcrossTimeZoneInvalidScheduleType = Setting SynchronizeAcrossTimeZone to true when the ScheduleType is not Once, Daily or Weekly is not a valid configuration. Please keep the default value of false when using other schedule types. TriggerCreationError = Error creating new scheduled task trigger. ConfigureTriggerRepetitionMessage = Configuring trigger repetition. RepetitionIntervalError = Repetition interval is set to '{0}' but repetition duration is '{1}'. From 5041706b7654543454f32de6272fc0a0b4bb8ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Fri, 28 Sep 2018 22:33:08 +0200 Subject: [PATCH 13/30] Add unit tests for StartTime and synchronize across time zones --- .../MSFT_ScheduledTask.psm1 | 25 +-- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 152 ++++++++++++++++++ 2 files changed, 167 insertions(+), 10 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 35a9816c..b53010cc 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -64,6 +64,11 @@ $script:localizedData = Get-LocalizedData ` The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks. Not used in Get-TargetResource. + .PARAMETER SynchronizeAcrossTimeZone + Enable the scheduled task option to synchronize across time zones. This is enabled + by including the timezone offset in the scheduled task trigger. Defaults to false + which does not include the timezone offset. + .PARAMETER Ensure Present if the task should exist, Absent if it should be removed. @@ -588,6 +593,11 @@ function Get-TargetResource The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks. + .PARAMETER SynchronizeAcrossTimeZone + Enable the scheduled task option to synchronize across time zones. This is enabled + by including the timezone offset in the scheduled task trigger. Defaults to false + which does not include the timezone offset. + .PARAMETER Ensure Present if the task should exist, Absent if it should be removed. @@ -1405,6 +1415,11 @@ function Set-TargetResource The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks. + .PARAMETER SynchronizeAcrossTimeZone + Enable the scheduled task option to synchronize across time zones. This is enabled + by including the timezone offset in the scheduled task trigger. Defaults to false + which does not include the timezone offset. + .PARAMETER Ensure Present if the task should exist, Absent if it should be removed. @@ -1795,11 +1810,6 @@ function Test-TargetResource $currentValues = Get-TargetResource @PSBoundParameters - foreach($key in $currentValues.Keys) - { - Write-Verbose "Current key $key has value $($currentvalues[$key])" - } - Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') @@ -1839,11 +1849,6 @@ function Test-TargetResource $desiredValues = $PSBoundParameters $desiredValues.TaskPath = $TaskPath - foreach($key in $desiredValues.Keys) - { - Write-Verbose "Desired Key $key has value $($desiredValues[$key])" - } - Write-Verbose -Message ($script:localizedData.TestingDscParameterStateMessage) return Test-DscParameterState -CurrentValues $currentValues -DesiredValues $desiredValues diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index b9071ee5..ced44a04 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -1866,6 +1866,158 @@ try } } } + + Context 'When a scheduled task is created and synchronize across time zone is disabled' { + $startTimeString = '2018-10-01T01:00:00' + $startTimeStringWithOffset = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + $testParameters = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + StartTime = Get-Date -Date $startTimeString + SynchronizeAcrossTimeZone = $false + ScheduleType = 'Once' + Verbose = $true + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + StartBoundary = $startTimeString + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + } + } + + It 'Should return the time in string format and SynchronizeAcrossTimeZone with value false' { + $result = Get-TargetResource @testParameters + $result.StartTime | Should -Be $startTimeString + $result.SynchronizeAcrossTimeZone | Should -Be $false + } + + It 'Should return true given that startTime is set correctly' { + Test-TargetResource @testParameters | Should -Be $true + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + StartBoundary = $startTimeStringWithOffset + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + } + } + + It 'Should return false given that the task is configured with synchronize across time zone' { + Test-TargetResource @testParameters | Should -Be $false + } + + Set-TargetResource @testParameters + + It "Should set task trigger StartBoundary to $startTimeString" { + Assert-MockCalled -CommandName Set-ScheduledTask -ParameterFilter { + $InputObject.Triggers[0].StartBoundary -eq $startTimeString + } + } + } + + Context 'When a scheduled task is created and synchronize across time zone is enabled' { + $startTimeString = '2018-10-01T01:00:00' + $startTimeStringWithOffset = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + $testParameters = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + StartTime = Get-Date -Date $startTimeString + SynchronizeAcrossTimeZone = $true + ScheduleType = 'Once' + Verbose = $true + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + StartBoundary = $startTimeStringWithOffset + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + } + } + + It 'Should return the time in string format and SynchronizeAcrossTimeZone with value true' { + $result = Get-TargetResource @testParameters + $result.StartTime | Should -Be $startTimeStringWithOffset + $result.SynchronizeAcrossTimeZone | Should -Be $true + } + + It 'Should return true given that startTime is set correctly' { + Test-TargetResource @testParameters | Should -Be $true + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + StartBoundary = $startTimeString + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + } + } + + It 'Should return false given that the task is configured with synchronize across time zone disabled' { + Test-TargetResource @testParameters | Should -Be $false + } + + Set-TargetResource @testParameters + + It "Should set task trigger StartBoundary to $startTimeStringWithOffset" { + Assert-MockCalled -CommandName Set-ScheduledTask -ParameterFilter { + $InputObject.Triggers[0].StartBoundary -eq $startTimeStringWithOffset + } + } + } } } #endregion From 0b7d5d8eba79371e1d3d72724613b6a2389b3ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Sun, 30 Sep 2018 21:14:59 +0200 Subject: [PATCH 14/30] Adding integration tests for synchronize across time zone option. --- .../Integration/MSFT_ScheduledTask.Config.ps1 | 38 ++++++++ .../MSFT_ScheduledTask.Integration.Tests.ps1 | 94 +++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/Tests/Integration/MSFT_ScheduledTask.Config.ps1 b/Tests/Integration/MSFT_ScheduledTask.Config.ps1 index 8195dff4..728e9a72 100644 --- a/Tests/Integration/MSFT_ScheduledTask.Config.ps1 +++ b/Tests/Integration/MSFT_ScheduledTask.Config.ps1 @@ -22,6 +22,44 @@ Configuration ScheduledTaskOnceCrossTimezone } } +Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneDisabled +{ + Import-DscResource -ModuleName ComputerManagementDsc + node 'localhost' + { + ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneDisabled + { + TaskName = 'Test task sync across time zone disabled' + TaskPath = '\ComputerManagementDsc\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + StartTime = '2018-10-01T01:00:00' + SynchronizeAcrossTimeZone = $false + ActionWorkingPath = (Get-Location).Path + Enable = $true + } + } +} + +Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled +{ + Import-DscResource -ModuleName ComputerManagementDsc + node 'localhost' + { + ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled + { + TaskName = 'Test task sync across time zone enabled' + TaskPath = '\ComputerManagementDsc\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + StartTime = '2018-10-01T01:00:00' + SynchronizeAcrossTimeZone = $true + ActionWorkingPath = (Get-Location).Path + Enable = $true + } + } +} + Configuration ScheduledTaskOnceAdd { Import-DscResource -ModuleName ComputerManagementDsc diff --git a/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 b/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 index 38ae8f2f..a4e6aee7 100644 --- a/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 @@ -195,6 +195,100 @@ try } } + Context "When a scheduled task is created and synchronize across time zone is disabled" { + $currentConfig = 'ScheduledTaskOnceSynchronizeAcrossTimeZoneDisabled' + $configDir = (Join-Path -Path $TestDrive -ChildPath $currentConfig) + $configMof = (Join-Path -Path $configDir -ChildPath 'localhost.mof') + + It 'Should compile the MOF without throwing' { + { + . $currentConfig ` + -OutputPath $configDir + } | Should -Not -Throw + } + + It 'Should apply the MOF correctly' { + { + Start-DscConfiguration ` + -Path $configDir ` + -Wait ` + -Force ` + -Verbose ` + -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should return a compliant state after being applied' { + (Test-DscConfiguration -ReferenceConfiguration $configMof -Verbose).InDesiredState | Should -Be $true + } + + $expectedStartTime = '2018-10-01T01:00:00' + + It 'Should have set the resource and all the parameters should match' { + $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current.TaskName | Should -Be 'Test task sync across time zone disabled' + $current.TaskPath | Should -Be '\ComputerManagementDsc\' + $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + $current.ScheduleType | Should -Be 'Once' + $current.StartTime | Should -Be (Get-Date -Date $expectedStartTime) + $current.SynchronizeAcrossTimeZone | Should -Be $false + $current.ActionWorkingPath | Should -Be (Get-Location).Path + $current.Enable | Should -Be $true + } + + It "Should have the trigger startBoundary set to $expectedStartTime" { + $task = (Get-ScheduledTask -TaskName 'Test task sync across time zone disabled') + $task.Triggers[0].StartBoundary | Should -Be $expectedStartTime + } + } + + Context "When a scheduled task is created and synchronize across time zone is enabled" { + $currentConfig = 'ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled' + $configDir = (Join-Path -Path $TestDrive -ChildPath $currentConfig) + $configMof = (Join-Path -Path $configDir -ChildPath 'localhost.mof') + + It 'Should compile the MOF without throwing' { + { + . $currentConfig ` + -OutputPath $configDir + } | Should -Not -Throw + } + + It 'Should apply the MOF correctly' { + { + Start-DscConfiguration ` + -Path $configDir ` + -Wait ` + -Force ` + -Verbose ` + -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should return a compliant state after being applied' { + (Test-DscConfiguration -ReferenceConfiguration $configMof -Verbose).InDesiredState | Should -Be $true + } + + $expectedStartTime = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + + It 'Should have set the resource and all the parameters should match' { + $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current.TaskName | Should -Be 'Test task sync across time zone enabled' + $current.TaskPath | Should -Be '\ComputerManagementDsc\' + $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + $current.ScheduleType | Should -Be 'Once' + $current.StartTime | Should -Be (Get-Date -Date $expectedStartTime) + $current.SynchronizeAcrossTimeZone | Should -Be $true + $current.ActionWorkingPath | Should -Be (Get-Location).Path + $current.Enable | Should -Be $true + } + + It "Should have the trigger startBoundary set to $expectedStartTime" { + $task = (Get-ScheduledTask -TaskName 'Test task sync across time zone enabled') + $task.Triggers[0].StartBoundary | Should -Be $expectedStartTime + } + } + # Simulate a "built-in" scheduled task $action = New-ScheduledTaskAction -Execute 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) From 8785ffa29d9b446788aa11481add0e7e1ed8ea0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Sun, 30 Sep 2018 21:32:20 +0200 Subject: [PATCH 15/30] Update changelog --- CHANGELOG.md | 1 + Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d66effcc..0589e269 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - ScheduledTask: - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) + - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) ## 5.2.0.0 diff --git a/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 b/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 index a4e6aee7..53fc756c 100644 --- a/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 @@ -225,7 +225,7 @@ try $expectedStartTime = '2018-10-01T01:00:00' It 'Should have set the resource and all the parameters should match' { - $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} $current.TaskName | Should -Be 'Test task sync across time zone disabled' $current.TaskPath | Should -Be '\ComputerManagementDsc\' $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' @@ -272,7 +272,7 @@ try $expectedStartTime = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') It 'Should have set the resource and all the parameters should match' { - $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} $current.TaskName | Should -Be 'Test task sync across time zone enabled' $current.TaskPath | Should -Be '\ComputerManagementDsc\' $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' From 3bd8a161cad97a7c5a8f47f46da5a1efc539ce78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Sun, 30 Sep 2018 21:59:44 +0200 Subject: [PATCH 16/30] Add example for Synchronize Across TimeZone --- ...skOnceSynchronizeAcrossTimeZoneEnabled.ps1 | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 new file mode 100644 index 00000000..a9c86435 --- /dev/null +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 @@ -0,0 +1,35 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Once' in the folder + task folder 'MyTasks' that starts a new powershell process once at 00:00 repeating + every 15 minutes for 8 hours. The task is delayed by a random amount up to 1 hour + each time. The task will run even if the previous task is still running and it + will prevent hard termintaing of the previously running task instance. The task + execution will have no time limit. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName ComputerManagementDsc + + Node $NodeName + { + ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled + { + TaskName = 'Test task sync across time zone enabled' + TaskPath = '\MyTasks\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + StartTime = '2018-10-01T01:00:00' + SynchronizeAcrossTimeZone = $true + ActionWorkingPath = (Get-Location).Path + Enable = $true + } + } +} From 0a465e915184bd0283eda9ac922ee07a42cf015c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Mon, 1 Oct 2018 18:48:41 +0200 Subject: [PATCH 17/30] Minor syntax and comment updates --- .../MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 16 ++++++++-------- .../MSFT_ScheduledTask.schema.mof | 2 +- ...dTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 | 9 +++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index b53010cc..53f9d8c3 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1346,9 +1346,9 @@ function Set-TargetResource The problem in New-ScheduledTaskTrigger is that it always writes the time the format that includes the full timezone offset (W2016 behaviour, W2012R2 does it the other way around). - Which means "Synchronize across time zones" is enabled by default. To prevent that, we are - overwriting the StartBoundary here to insert the time in the format we want it, so we can enable - or disable "Synchronize across time zones". + Which means "Synchronize across time zones" is enabled by default on W2016 and disabled by + default on W2012R2. To prevent that, we are overwriting the StartBoundary here to insert + the time in the format we want it, so we can enable or disable "Synchronize across time zones". #> $scheduledTask.Triggers[0].StartBoundary = Get-DateTimeString -Date $StartTime -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone @@ -2011,10 +2011,10 @@ function Disable-ScheduledTask <# .SYNOPSIS - Returns a formatted datetime string for use in ScheduledTask resource + Returns a formatted datetime string for use in ScheduledTask resource. .PARAMETER Date - The date to format + The date to format. .PARAMETER SynchronizeAcrossTimeZone Boolean to specifiy if the returned string is formatted in synchronize @@ -2026,11 +2026,11 @@ Function Get-DateTimeString param ( [Parameter(Mandatory = $true)] - [DateTime] + [System.DateTime] $Date, [Parameter(Mandatory = $true)] - [Boolean] + [System.Boolean] $SynchronizeAcrossTimeZone ) @@ -2045,5 +2045,5 @@ Function Get-DateTimeString $returnDate = Get-Date -Date $Date -Format $format } - $returnDate + return $returnDate } diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof index 563b251f..65255166 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof @@ -10,7 +10,7 @@ class MSFT_ScheduledTask : OMI_BaseResource [Write, Description("When should the task be executed"), ValueMap{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}, Values{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}] string ScheduleType; [Write, Description("How many units (minutes, hours, days) between each run of this task?")] String RepeatInterval; [Write, Description("The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks")] DateTime StartTime; - [Write, Description("True to enable Synchronize across time zones, false to disable this setting. Defaults to false.")] boolean SynchronizeAcrossTimeZone; + [Write, Description("Enable the scheduled task option to synchronize across time zones. This is enabled by including the timezone offset in the scheduled task trigger. Defaults to false which does not include the timezone offset.")] boolean SynchronizeAcrossTimeZone; [Write, Description("Present if the task should exist, Absent if it should be removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; [Write, Description("True if the task should be enabled, false if it should be disabled")] boolean Enable; [Write, Description("The credential this task should execute as. If not specified defaults to running as the local system account"), EmbeddedInstance("MSFT_Credential")] string ExecuteAsCredential; diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 index a9c86435..263c901f 100644 --- a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/15-CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled.ps1 @@ -1,11 +1,8 @@ <# .EXAMPLE - This example creates a scheduled task called 'Test task Once' in the folder - task folder 'MyTasks' that starts a new powershell process once at 00:00 repeating - every 15 minutes for 8 hours. The task is delayed by a random amount up to 1 hour - each time. The task will run even if the previous task is still running and it - will prevent hard termintaing of the previously running task instance. The task - execution will have no time limit. + This example creates a scheduled task called 'Test task sync across time zone enabled' + in the folder 'MyTasks' that starts a new powershell process once 2018-10-01 01:00 + The task will have the option Synchronize across time zone enabled. #> Configuration Example { From 23ed50a8d78d16541cc884173d12072ec071f041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Mon, 1 Oct 2018 20:02:38 +0200 Subject: [PATCH 18/30] Add test for SynchronizeAcrossTimeZone with unsupported ScheduleType --- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index ced44a04..d97ba580 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -2018,6 +2018,23 @@ try } } } + + Context 'When a scheduled task is configured to SynchronizeAcrossTimeZone and the ScheduleType is not Once, Daily or Weekly' { + $startTimeString = '2018-10-01T01:00:00' + $testParameters = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + StartTime = Get-Date -Date $startTimeString + SynchronizeAcrossTimeZone = $true + ScheduleType = 'AtLogon' + Verbose = $true + } + + It 'Should throw when Set-TargetResource is called and SynchronizeAcrossTimeZone is used in combination with an unsupported trigger type' { + { Set-TargetResource @testParamers } | Should Throw + } + } } } #endregion From 5119c12d6fc231d6d76ffceaa487ab3490e71c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Both?= Date: Mon, 1 Oct 2018 20:19:52 +0200 Subject: [PATCH 19/30] Small syntax update --- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index d97ba580..6622572e 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -2020,7 +2020,7 @@ try } Context 'When a scheduled task is configured to SynchronizeAcrossTimeZone and the ScheduleType is not Once, Daily or Weekly' { - $startTimeString = '2018-10-01T01:00:00' + $startTimeString = '2018-10-01T01:00:00' $testParameters = @{ TaskName = 'Test task' TaskPath = '\Test\' From 2fa661bbaf144cae5b6f116bb4b9d78096b415e3 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Tue, 2 Oct 2018 21:21:47 +1300 Subject: [PATCH 20/30] Added .VSCode settings for applying DSC PSSA rules --- .vscode/analyzersettings.psd1 | 53 +++++++++++++++++++++++++++++++++++ .vscode/settings.json | 3 +- CHANGELOG.md | 1 + 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .vscode/analyzersettings.psd1 diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 new file mode 100644 index 00000000..be415e4d --- /dev/null +++ b/.vscode/analyzersettings.psd1 @@ -0,0 +1,53 @@ +@{ + <# + For the custom rules to work, the DscResource.Tests repo must be + cloned. It is automatically clone as soon as any unit or + integration tests are run. + #> + CustomRulePath = '.\DSCResource.Tests\DscResource.AnalyzerRules' + + IncludeRules = @( + # DSC Resource Kit style guideline rules. + 'PSAvoidDefaultValueForMandatoryParameter', + 'PSAvoidDefaultValueSwitchParameter', + 'PSAvoidInvokingEmptyMembers', + 'PSAvoidNullOrEmptyHelpMessageAttribute', + 'PSAvoidUsingCmdletAliases', + 'PSAvoidUsingComputerNameHardcoded', + 'PSAvoidUsingDeprecatedManifestFields', + 'PSAvoidUsingEmptyCatchBlock', + 'PSAvoidUsingInvokeExpression', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidShouldContinueWithoutForce', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingWriteHost', + 'PSDSCReturnCorrectTypesForDSCFunctions', + 'PSDSCStandardDSCFunctionsInResource', + 'PSDSCUseIdenticalMandatoryParametersForDSC', + 'PSDSCUseIdenticalParametersForDSC', + 'PSMisleadingBacktick', + 'PSMissingModuleManifestField', + 'PSPossibleIncorrectComparisonWithNull', + 'PSProvideCommentHelp', + 'PSReservedCmdletChar', + 'PSReservedParams', + 'PSUseApprovedVerbs', + 'PSUseCmdletCorrectly', + 'PSUseOutputTypeCorrectly', + 'PSAvoidGlobalVars', + 'PSAvoidUsingConvertToSecureStringWithPlainText', + 'PSAvoidUsingPlainTextForPassword', + 'PSAvoidUsingUsernameAndPasswordParams', + 'PSDSCUseVerboseMessageInDSCResource', + 'PSShouldProcess', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUsePSCredentialType', + + <# + This is to test all the DSC Resource Kit custom rules. + The name of the function-blocks of each custom rule start + with 'Measure*'. + #> + 'Measure-*' + ) +} diff --git a/.vscode/settings.json b/.vscode/settings.json index cd3d82e8..43f01cf9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "powershell.codeFormatting.ignoreOneLineBlock": false, "powershell.codeFormatting.preset": "Custom", "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true + "files.insertFinalNewline": true, + "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0589e269..a06c0cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) +- Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). ## 5.2.0.0 From dfa4758b6c3add5d039e6afc7e41be6dffcaaf5d Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Wed, 3 Oct 2018 22:44:33 +0000 Subject: [PATCH 21/30] PowerPlan: Adding IsActive Read-Only Property Closes #171 --- .../DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index 372dcf15..646c5fe7 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -67,12 +67,10 @@ function Get-TargetResource if ($plan.IsActive) { Write-Verbose -Message ($script:localizedData.PowerPlanIsActive -f $Name) - $activePlanName = $Name } else { Write-Verbose -Message ($script:localizedData.PowerPlanIsNotActive -f $Name) - $activePlanName = $null } } else @@ -83,7 +81,8 @@ function Get-TargetResource return @{ IsSingleInstance = $IsSingleInstance - Name = $activePlanName + Name = $Name + IsActive = $plan.IsActive } } From e6feb22dc3e48f2ab3e2db403aea37d1bf630f7a Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Wed, 3 Oct 2018 22:51:54 +0000 Subject: [PATCH 22/30] Update Changelog to contain info about IsActive Read-Only Property being added. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a06c0cb8..ec324f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). +- PowerPlan: + - Added IsActive Read-Only Property. Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171) ## 5.2.0.0 From 90f024be9fe625b576942ca5eeb77cad6fb0107b Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Wed, 3 Oct 2018 23:16:22 +0000 Subject: [PATCH 23/30] Read-Only properties must be in the schema.mof even if the parameter is not used as any parameters for the 3 dsc powershell functions (test, get, set). --- .../DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof index 26c9a2e9..9d4a885c 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof @@ -3,4 +3,5 @@ class MSFT_PowerPlan : OMI_BaseResource { [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'."), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance; [Required, Description("The name of the power plan to activate.")] String Name; + [Read, Description("Determines if the power plan is active.")] Boolean IsActive; }; From 1f7ca086210cf0c4951bedc0286ea920be6f0c14 Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Wed, 3 Oct 2018 23:25:30 +0000 Subject: [PATCH 24/30] Updated unit tests to properly handle the changes made for the IsActive Read-Only Property. --- .../DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 9 +-------- Tests/Unit/MSFT_PowerPlan.Tests.ps1 | 6 ++++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index 646c5fe7..d174ba8a 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -176,18 +176,11 @@ function Test-TargetResource $Name ) - $returnValue = $false - Write-Verbose -Message ($script:localizedData.PowerPlanIsBeingValidated -f $Name) $getTargetResourceResult = Get-TargetResource -IsSingleInstance $IsSingleInstance -Name $Name - if ($getTargetResourceResult.Name -eq $Name) - { - $returnValue = $true - } - - return $returnValue + return $getTargetResourceResult.IsActive } Export-ModuleMember -Function *-TargetResource diff --git a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 index 5eafc68c..b40b2b96 100644 --- a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 @@ -57,6 +57,7 @@ try $result = Get-TargetResource @testParameters $result.IsSingleInstance | Should -Be 'Yes' $result.Name | Should -Be $testParameters.Name + $result.IsActive | Should -Be $true } } @@ -72,10 +73,11 @@ try -Verifiable } - It 'Should not return any plan name' { + It 'Should return an inactive plan' { $result = Get-TargetResource @testParameters $result.IsSingleInstance | Should -Be 'Yes' - $result.Name | Should -Be $null + $result.Name | Should -Be $testParameters.Name + $result.IsActive | Should -Be $false } } From a39c1a4c23112506c59817655a8530713df880e9 Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Thu, 4 Oct 2018 14:16:20 +0000 Subject: [PATCH 25/30] Attempting to fix pull-request review recommendation. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec324f53..852ad538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). - PowerPlan: - - Added IsActive Read-Only Property. Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171) + - Added IsActive Read-Only Property - Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171). ## 5.2.0.0 From 16a59edaedb1262dc253822bbcd72535f801f5a0 Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Mon, 8 Oct 2018 00:43:42 -0500 Subject: [PATCH 26/30] Added note to changelog that powerplans that are inactive no longer are returned as a name set to null. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 852ad538..34e948ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). - PowerPlan: - Added IsActive Read-Only Property - Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171). + - InActive power plans are no longer returned with their Name set to null. Now, the name is always returned and the Read-Only property + of IsActive is set accordingly. ## 5.2.0.0 From 0b035ed0fa9eb7364d35aaba9c07c70e7845ba0a Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Mon, 8 Oct 2018 01:47:24 -0500 Subject: [PATCH 27/30] Reduce line length based on pr. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e948ba..f80dcc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). - PowerPlan: - Added IsActive Read-Only Property - Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171). - - InActive power plans are no longer returned with their Name set to null. Now, the name is always returned and the Read-Only property - of IsActive is set accordingly. + - InActive power plans are no longer returned with their Name set to null. Now, the name is + always returned and the Read-Only property of IsActive is set accordingly. ## 5.2.0.0 From a4d721388adb6c0480d9050fd8631820df597aae Mon Sep 17 00:00:00 2001 From: Michael Fyffe <6224270+TraGicCode@users.noreply.github.com> Date: Wed, 10 Oct 2018 01:36:43 -0500 Subject: [PATCH 28/30] Fix line length markdown violation. --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f80dcc86..9a8573ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). - PowerPlan: - Added IsActive Read-Only Property - Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171). - - InActive power plans are no longer returned with their Name set to null. Now, the name is - always returned and the Read-Only property of IsActive is set accordingly. + - InActive power plans are no longer returned with their Name set to null. + Now, the name is always returned and the Read-Only property of IsActive + is set accordingly. ## 5.2.0.0 From 54ea94b7440ba20fa3251f1b81a74296ef13eb99 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 11 Oct 2018 19:09:29 +1300 Subject: [PATCH 29/30] Add BREAKING CHANGE to CHANGELOG.MD --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8573ec..a064d980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). -- PowerPlan: +- BREAKING CHANGE: PowerPlan: - Added IsActive Read-Only Property - Fixes [Issue #171](https://github.com/PowerShell/ComputerManagementDsc/issues/171). - InActive power plans are no longer returned with their Name set to null. Now, the name is always returned and the Read-Only property of IsActive From 32eccd7bb0dbd88e38ad7a5aed859ba445c64e3f Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Wed, 24 Oct 2018 15:24:40 -0700 Subject: [PATCH 30/30] Releasing version 6.0.0.0 --- CHANGELOG.md | 2 ++ .../ComputerManagementDsc.psd1 | 23 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a064d980..c283e48e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.0.0.0 + - ScheduledTask: - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) diff --git a/Modules/ComputerManagementDsc/ComputerManagementDsc.psd1 b/Modules/ComputerManagementDsc/ComputerManagementDsc.psd1 index 866d4683..ad637fee 100644 --- a/Modules/ComputerManagementDsc/ComputerManagementDsc.psd1 +++ b/Modules/ComputerManagementDsc/ComputerManagementDsc.psd1 @@ -1,6 +1,6 @@ @{ # Version number of this module. -moduleVersion = '5.2.0.0' +moduleVersion = '6.0.0.0' # ID used to uniquely identify this module GUID = 'B5004952-489E-43EA-999C-F16A25355B89' @@ -49,16 +49,16 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '- PowershellExecutionPolicy: - - Updated to meet HQRM guidelines. - - Migrated the xPowershellExecutionPolicy from [xPowershellExecutionPolicy](https://github.com/PowerShell/xPowerShellExecutionPolicy) - and renamed to PowershellExecutionPolicy. - - Moved strings to localization file. -- Changed the scope from Global to Script in MSFT_ScheduledTask.Integration.Tests.ps1 -- Changed the scope from Global to Script ComputerManagementDsc.Common.Tests.ps1 -- ScheduledTask: - - Added support for event based triggers, implemented using the ScheduleType OnEvent - fixes [Issue 167](https://github.com/PowerShell/ComputerManagementDsc/issues/167) + ReleaseNotes = '- ScheduledTask: + - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA + parameter. Fixes [Issue 111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) + - Added support to set the Synchronize Across Time Zone option. Fixes [Issue 109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) +- Added .VSCode settings for applying DSC PSSA rules - fixes [Issue 189](https://github.com/PowerShell/ComputerManagementDsc/issues/189). +- BREAKING CHANGE: PowerPlan: + - Added IsActive Read-Only Property - Fixes [Issue 171](https://github.com/PowerShell/ComputerManagementDsc/issues/171). + - InActive power plans are no longer returned with their Name set to null. + Now, the name is always returned and the Read-Only property of IsActive + is set accordingly. ' @@ -69,3 +69,4 @@ PrivateData = @{ +