diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1d0ce1..3dedae19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - BREAKING CHANGE: ScheduledTask + - Fixed SynchronizeAcrossTimeZone issue where Test always throws False when a date & time is used + where Daylight Savings Time is in operation. Fixes [Issue #374](https://github.com/dsccommunity/ComputerManagementDsc/issues/374). + - Fixed Test-DateStringContainsTimeZone to correctly process date strings behind UTC (-), as well + as UTC Zulu 'Z' strings. - Fixed User parameter to correctly return the user that triggers an AtLogon or OnSessionState Schedule Type, instead of the current value of ExecuteAsCredential. This parameter is only valid when using the AtLogon and OnSessionState Schedule Types. @@ -41,8 +45,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - BREAKING CHANGE: ScheduledTask + - StartTime is now processed on the device, rather than at compile time. This makes it possible + to configure start times based on each device's timezone, rather than being fixed to the time zone + configured on the device where the Desired State Configuration compilation was run. - Allow StartTime to be used to set the 'Activate' setting when adding ScheduleType triggers other than 'Once', 'Daily' and 'Weekly'. + - Changed the default StartTime date from today to 1st January 1980 to prevent configuration flip flopping, + and added note to configuration README to advise always supplying a date, and not just a time. + Fixes [Issue #148](https://github.com/dsccommunity/ComputerManagementDsc/issues/148). + Fixes [Issue #411](https://github.com/dsccommunity/ComputerManagementDsc/issues/411). + - Added examples & note to configuration README to supply a timezone when using SynchronizeAcrossTimeZone. + - Allow SynchronizeAcrossTimeZone to be used when adding ScheduleType triggers other than 'Once', + 'Daily' and 'Weekly'. - Updated Delay parameter to support ScheduleType AtLogon, AtStartup, AtCreation, OnSessionState. Fixes [Issue #345](https://github.com/dsccommunity/ComputerManagementDsc/issues/345). - Updated User parameter for use with ScheduleType OnSessionState in addition to AtLogon. diff --git a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 index 0125e405..488f2373 100644 --- a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 +++ b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 @@ -97,7 +97,8 @@ function Get-TargetResource How many units (minutes, hours, days) between each run of this task? .PARAMETER StartTime - The time of day this task should start at, or activate on - defaults to 12:00 AM. + The date and time of day this task should start at, or activate on, represented + as a string for local conversion to DateTime format - defaults to 1st January 1980 at 12:00 AM. .PARAMETER SynchronizeAcrossTimeZone Enable the scheduled task option to synchronize across time zones. This is enabled @@ -301,8 +302,8 @@ function Set-TargetResource $RepeatInterval = '00:00:00', [Parameter()] - [System.DateTime] - $StartTime = [System.DateTime]::Today, + [System.String] + $StartTime = '1980-01-01T00:00:00', [Parameter()] [System.Boolean] @@ -480,6 +481,9 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.SetScheduledTaskMessage -f $TaskName, $TaskPath) + # Convert the strings containing dates & times to DateTime objects + [System.DateTime] $StartTime = [System.DateTime]::Parse($StartTime) + # Convert the strings containing time spans to TimeSpan Objects [System.TimeSpan] $RepeatInterval = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RepeatInterval [System.TimeSpan] $RandomDelay = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RandomDelay @@ -574,13 +578,6 @@ 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 @@ -1038,8 +1035,11 @@ function Set-TargetResource 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). + The problem in New-ScheduledTaskTrigger is that it always writes the time in the UTC format, which + includes the full timezone offset: (W2016+ behaviour, W2012R2 does it the other way around) + + 2018-09-27 16:45:08Z + 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". @@ -1106,7 +1106,8 @@ function Set-TargetResource How many units (minutes, hours, days) between each run of this task? .PARAMETER StartTime - The time of day this task should start at, or activate on - defaults to 12:00 AM. + The date and time of day this task should start at, or activate on, represented + as a string for local conversion to DateTime format - defaults to 1st January 1980 at 12:00 AM. .PARAMETER SynchronizeAcrossTimeZone Enable the scheduled task option to synchronize across time zones. This is enabled @@ -1311,8 +1312,8 @@ function Test-TargetResource $RepeatInterval = '00:00:00', [Parameter()] - [System.DateTime] - $StartTime = [System.DateTime]::Today, + [System.String] + $StartTime = '1980-01-01T00:00:00', [Parameter()] [System.Boolean] @@ -1492,6 +1493,12 @@ function Test-TargetResource $currentValues = Get-CurrentResource -TaskName $TaskName -TaskPath $TaskPath + # Convert the strings containing dates & times to DateTime objects + if ($PSBoundParameters.ContainsKey('StartTime')) + { + $PSBoundParameters['StartTime'] = [System.DateTime]::Parse($StartTime) + } + # Convert the strings containing time spans to TimeSpan Objects if ($PSBoundParameters.ContainsKey('RepeatInterval')) { @@ -1569,7 +1576,7 @@ function Test-TargetResource $PSBoundParameters['StartTime'] = Get-DateTimeString -Date $StartTime -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone <# If the current StartTime is null then we need to set it to - the desired StartTime (which defaults to Today if not passed) + the desired StartTime (which defaults to 1st January 1980 at 12:00 AM if not passed) so that the test does not fail. #> if ($currentValues['StartTime']) @@ -1877,7 +1884,7 @@ function Disable-ScheduledTaskEx The date to format. .PARAMETER SynchronizeAcrossTimeZone - Boolean to specifiy if the returned string is formatted in synchronize + Boolean to specify if the returned string is formatted in synchronize across time zone format. #> function Get-DateTimeString @@ -1894,14 +1901,14 @@ function Get-DateTimeString $SynchronizeAcrossTimeZone ) - $format = (Get-Culture).DateTimeFormat.SortableDateTimePattern - if ($SynchronizeAcrossTimeZone) { - $returnDate = (Get-Date -Date $Date -Format $format) + (Get-Date -Format 'zzz') + $format = (Get-Culture).DateTimeFormat.SortableDateTimePattern + 'zzz' + $returnDate = Get-Date -Date $Date.ToLocalTime() -Format $format } else { + $format = (Get-Culture).DateTimeFormat.SortableDateTimePattern $returnDate = Get-Date -Date $Date -Format $format } @@ -2040,7 +2047,7 @@ function Get-CurrentResource if ($startAt) { $synchronizeAcrossTimeZone = Test-DateStringContainsTimeZone -DateString $startAt - $startTime = [System.DateTime] $startAt + $startTime = $startAt } else { @@ -2228,7 +2235,9 @@ function Test-DateStringContainsTimeZone $DateString ) - return $DateString.Contains('+') + # When parsing a DateTime, Kind will be 'Unspecified' unless parsed string includes time zone information + # See https://learn.microsoft.com/en-us/dotnet/api/system.datetime.parse?view=netframework-4.5#the-return-value-and-datetimekind + return [DateTime]::Parse($DateString).Kind -ne 'Unspecified' } <# diff --git a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof index 7583fe4d..65b08f15 100644 --- a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof +++ b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof @@ -9,7 +9,7 @@ class DSC_ScheduledTask : OMI_BaseResource [Write, Description("The working path to specify for the executable.")] string ActionWorkingPath; [Write, Description("When should the task be executed."), ValueMap{"Once", "Daily", "Weekly", "AtStartup", "AtLogon", "OnIdle", "OnEvent", "AtCreation", "OnSessionState"}, Values{"Once", "Daily", "Weekly", "AtStartup", "AtLogon", "OnIdle", "OnEvent", "AtCreation", "OnSessionState"}] 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, or activate on - defaults to 12:00 AM.")] DateTime StartTime; + [Write, Description("The date and time of day this task should start at, or activate on, represented as a string for local conversion to DateTime format - defaults to 1st January 1980 at 12:00 AM.")] String StartTime; [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; diff --git a/source/DSCResources/DSC_ScheduledTask/README.md b/source/DSCResources/DSC_ScheduledTask/README.md index efc6df41..313ec691 100644 --- a/source/DSCResources/DSC_ScheduledTask/README.md +++ b/source/DSCResources/DSC_ScheduledTask/README.md @@ -6,6 +6,14 @@ scheduled tasks. ## Known Issues +When creating a scheduled task with a StartTime, you should always specify both +a date and a time, with the SortableDateTimePattern format (e.g. 1980-01-01T00:00:00). +Not providing a date may result in 'flip flopping' if the remote server enters daylight +savings time. The date and time specified will be set based on the time zone that has been +configured on the device. If you want to synchronize a scheduled task across timezones, +use the SynchronizeAcrossTimeZone parameter, and specify the timezone offset that is needed +(e.g. 1980-01-01T00:00:00-08:00). + One of the values needed for the `MultipleInstances` parameter is missing from the `Microsoft.PowerShell.Cmdletization.GeneratedTypes.ScheduledTask.MultipleInstancesEnum` enumerator. There are four valid values defined for the `MultipleInstances` property of the diff --git a/source/DSCResources/DSC_ScheduledTask/en-US/DSC_ScheduledTask.strings.psd1 b/source/DSCResources/DSC_ScheduledTask/en-US/DSC_ScheduledTask.strings.psd1 index 99ee4a03..9f8e0e4d 100644 --- a/source/DSCResources/DSC_ScheduledTask/en-US/DSC_ScheduledTask.strings.psd1 +++ b/source/DSCResources/DSC_ScheduledTask/en-US/DSC_ScheduledTask.strings.psd1 @@ -13,7 +13,6 @@ ConvertFrom-StringData @' OnEventSubscriptionError = No (valid) XML Event Subscription was provided. This is required when the scheduletype is OnEvent. OnSessionStateChangeError = No kind of session state change was provided. This is required when the scheduletype is OnSessionState. gMSAandCredentialError = Both ExecuteAsGMSA and (ExecuteAsCredential or BuiltInAccount) parameters have been specified. A task can run as a gMSA (Group Managed Service Account), a builtin service account or as a custom credential. Please modify your configuration to include just one of the three options. - 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}'. diff --git a/source/Examples/Resources/ScheduledTask/15-ScheduledTask_CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled_Config.ps1 b/source/Examples/Resources/ScheduledTask/15-ScheduledTask_CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled_Config.ps1 index 0b99012b..201d3f97 100644 --- a/source/Examples/Resources/ScheduledTask/15-ScheduledTask_CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled_Config.ps1 +++ b/source/Examples/Resources/ScheduledTask/15-ScheduledTask_CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled_Config.ps1 @@ -20,8 +20,8 @@ <# .DESCRIPTION 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 at 2018-10-01 01:00. - The task will have the option Synchronize across time zone enabled. + in the folder 'MyTasks' that starts a new powershell process once at 2018-10-01 01:00 + in the -08:00 timezone. The task will have the option Synchronize across time zone enabled. #> Configuration ScheduledTask_CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled_Config { @@ -35,7 +35,7 @@ Configuration ScheduledTask_CreateScheduledTaskOnceSynchronizeAcrossTimeZoneEnab TaskPath = '\MyTasks\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - StartTime = '2018-10-01T01:00:00' + StartTime = '2018-10-01T01:00:00-08:00' SynchronizeAcrossTimeZone = $true ActionWorkingPath = (Get-Location).Path Enable = $true diff --git a/tests/Integration/DSC_ScheduledTask.Integration.Tests.ps1 b/tests/Integration/DSC_ScheduledTask.Integration.Tests.ps1 index fdfc90c8..e4ce2205 100644 --- a/tests/Integration/DSC_ScheduledTask.Integration.Tests.ps1 +++ b/tests/Integration/DSC_ScheduledTask.Integration.Tests.ps1 @@ -331,7 +331,7 @@ Describe "$($script:dscResourceName)_Integration" { $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.StartTime | Should -Be $expectedStartTime $current.SynchronizeAcrossTimeZone | Should -BeFalse $current.ActionWorkingPath | Should -Be (Get-Location).Path $current.Enable | Should -BeTrue @@ -343,13 +343,14 @@ Describe "$($script:dscResourceName)_Integration" { } } - Context 'When a scheduled task is created and synchronize across time zone is enabled' { + Context 'When a scheduled task is created and synchronize across time zone is enabled positive' { BeforeAll { - $currentConfig = 'ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled' + $currentConfig = 'ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledPositive' $configDir = (Join-Path -Path $TestDrive -ChildPath $currentConfig) $configMof = (Join-Path -Path $configDir -ChildPath 'localhost.mof') - $expectedStartTime = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + $configuredStartTime = '2018-10-01T01:00:00+08:00' + $expectedStartTime = (Get-Date -Date $configuredStartTime).ToUniversalTime().ToString((Get-Culture).DateTimeFormat.SortableDateTimePattern + 'zzz') } AfterAll { @@ -380,18 +381,126 @@ Describe "$($script:dscResourceName)_Integration" { It 'Should have set the resource and all the parameters should match' { $current = Get-DscConfiguration | Where-Object -FilterScript { $_.ConfigurationName -eq $currentConfig } - $current.TaskName | Should -Be 'Test task sync across time zone enabled' + $current.TaskName | Should -Be 'Test task sync across time zone enabled positive' $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.StartTime | Should -Be $expectedStartTime $current.SynchronizeAcrossTimeZone | Should -BeTrue $current.ActionWorkingPath | Should -Be (Get-Location).Path $current.Enable | Should -BeTrue } - It 'Should have the trigger startBoundary set to ''2018-10-01T01:HH:MM''' { - $task = (Get-ScheduledTask -TaskName 'Test task sync across time zone enabled') + It 'Should have the trigger startBoundary set to ''2018-09-30T17:00:00+00:00''' { + $task = (Get-ScheduledTask -TaskName 'Test task sync across time zone enabled positive') + $task.Triggers[0].StartBoundary | Should -Be $expectedStartTime + } + } + + Context 'When a scheduled task is created and synchronize across time zone is enabled negative' { + BeforeAll { + $currentConfig = 'ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledNegative' + $configDir = (Join-Path -Path $TestDrive -ChildPath $currentConfig) + $configMof = (Join-Path -Path $configDir -ChildPath 'localhost.mof') + + $configuredStartTime = '2018-10-01T01:00:00-08:00' + $expectedStartTime = (Get-Date -Date $configuredStartTime).ToUniversalTime().ToString((Get-Culture).DateTimeFormat.SortableDateTimePattern + 'zzz') + } + + AfterAll { + Wait-ForIdleLcm -Clear + } + + 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 -BeTrue + } + + It 'Should have set the resource and all the parameters should match' { + $current = Get-DscConfiguration | Where-Object -FilterScript { $_.ConfigurationName -eq $currentConfig } + $current.TaskName | Should -Be 'Test task sync across time zone enabled negative' + $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 $expectedStartTime + $current.SynchronizeAcrossTimeZone | Should -BeTrue + $current.ActionWorkingPath | Should -Be (Get-Location).Path + $current.Enable | Should -BeTrue + } + + It 'Should have the trigger startBoundary set to ''2018-10-01T09:00:00+00:00''' { + $task = (Get-ScheduledTask -TaskName 'Test task sync across time zone enabled negative') + $task.Triggers[0].StartBoundary | Should -Be $expectedStartTime + } + } + + Context 'When a scheduled task is created and synchronize across time zone is enabled zulu' { + BeforeAll { + $currentConfig = 'ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledZulu' + $configDir = (Join-Path -Path $TestDrive -ChildPath $currentConfig) + $configMof = (Join-Path -Path $configDir -ChildPath 'localhost.mof') + + $configuredStartTime = '2018-10-01T01:00:00Z' + $expectedStartTime = (Get-Date -Date $configuredStartTime).ToUniversalTime().ToString((Get-Culture).DateTimeFormat.SortableDateTimePattern + 'zzz') + } + + AfterAll { + Wait-ForIdleLcm -Clear + } + + 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 -BeTrue + } + + It 'Should have set the resource and all the parameters should match' { + $current = Get-DscConfiguration | Where-Object -FilterScript { $_.ConfigurationName -eq $currentConfig } + $current.TaskName | Should -Be 'Test task sync across time zone enabled zulu' + $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 $expectedStartTime + $current.SynchronizeAcrossTimeZone | Should -BeTrue + $current.ActionWorkingPath | Should -Be (Get-Location).Path + $current.Enable | Should -BeTrue + } + + It 'Should have the trigger startBoundary set to ''2018-10-01T01:00:00+00:00''' { + $task = (Get-ScheduledTask -TaskName 'Test task sync across time zone enabled zulu') $task.Triggers[0].StartBoundary | Should -Be $expectedStartTime } } diff --git a/tests/Integration/DSC_ScheduledTask.config.ps1 b/tests/Integration/DSC_ScheduledTask.config.ps1 index f0f11a3d..2f269332 100644 --- a/tests/Integration/DSC_ScheduledTask.config.ps1 +++ b/tests/Integration/DSC_ScheduledTask.config.ps1 @@ -43,19 +43,59 @@ Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneDisabled } } -Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled +Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledPositive { Import-DscResource -ModuleName ComputerManagementDsc node 'localhost' { - ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabled + ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledPositive { - TaskName = 'Test task sync across time zone enabled' + TaskName = 'Test task sync across time zone enabled positive' TaskPath = '\ComputerManagementDsc\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - StartTime = '2018-10-01T01:00:00' + StartTime = '2018-10-01T01:00:00+08:00' + SynchronizeAcrossTimeZone = $true + ActionWorkingPath = (Get-Location).Path + Enable = $true + } + } +} + +Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledNegative +{ + Import-DscResource -ModuleName ComputerManagementDsc + + node 'localhost' + { + ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledNegative + { + TaskName = 'Test task sync across time zone enabled negative' + TaskPath = '\ComputerManagementDsc\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + StartTime = '2018-10-01T01:00:00-08:00' + SynchronizeAcrossTimeZone = $true + ActionWorkingPath = (Get-Location).Path + Enable = $true + } + } +} + +Configuration ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledZulu +{ + Import-DscResource -ModuleName ComputerManagementDsc + + node 'localhost' + { + ScheduledTask ScheduledTaskOnceSynchronizeAcrossTimeZoneEnabledZulu + { + TaskName = 'Test task sync across time zone enabled zulu' + TaskPath = '\ComputerManagementDsc\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + StartTime = '2018-10-01T01:00:00Z' SynchronizeAcrossTimeZone = $true ActionWorkingPath = (Get-Location).Path Enable = $true diff --git a/tests/Unit/DSC_ScheduledTask.Tests.ps1 b/tests/Unit/DSC_ScheduledTask.Tests.ps1 index 4fe07fd3..923fe734 100644 --- a/tests/Unit/DSC_ScheduledTask.Tests.ps1 +++ b/tests/Unit/DSC_ScheduledTask.Tests.ps1 @@ -2559,16 +2559,26 @@ Describe 'DSC_ScheduledTask' { } Context 'When a scheduled task is created and synchronize across time zone is disabled' { + BeforeDiscovery { + $startTimeString = '2018-10-01T01:00:00' + } + BeforeAll { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString SynchronizeAcrossTimeZone = $false ScheduleType = 'Once' } - $startTimeStringWithOffset = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + $startTimeStringWithOffset = '2018-10-01T01:00:00-08:00' + + InModuleScope -Parameters @{ + startTimeString = $startTimeString + } -ScriptBlock { + $script:startTimeString = $startTimeString + } Mock -CommandName Get-ScheduledTask -MockWith { @{ @@ -2595,12 +2605,12 @@ Describe 'DSC_ScheduledTask' { } } - It 'Should return the start time in DateTime format and SynchronizeAcrossTimeZone with value false' { + It 'Should return the start time and SynchronizeAcrossTimeZone with value false' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 $result = Get-TargetResource @getTargetResourceParameters - $result.StartTime | Should -Be (Get-Date -Date $testParameters.StartTime) + $result.StartTime | Should -Be $testParameters.StartTime $result.SynchronizeAcrossTimeZone | Should -BeFalse } } @@ -2649,7 +2659,7 @@ Describe 'DSC_ScheduledTask' { } - It "Should set task trigger StartBoundary to $startTimeString" { + It "Should set task trigger StartBoundary to '$startTimeString'" { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -2665,23 +2675,25 @@ Describe 'DSC_ScheduledTask' { Context 'When a scheduled task is created and synchronize across time zone is enabled' { BeforeDiscovery { - $startTimeStringWithOffset = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + $startTimeStringWithOffset = '2018-10-01T01:00:00-08:00' + $expectedStartTimeStringWithOffset = (Get-Date -Date $startTimeStringWithOffset).ToUniversalTime().ToString((Get-Culture).DateTimeFormat.SortableDateTimePattern + 'zzz') } BeforeAll { $startTimeString = '2018-10-01T01:00:00' - $startTimeStringWithOffset = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') + $startTimeStringWithOffset = '2018-10-01T01:00:00-08:00' + $expectedStartTimeStringWithOffset = (Get-Date -Date $startTimeStringWithOffset).ToUniversalTime().ToString((Get-Culture).DateTimeFormat.SortableDateTimePattern + 'zzz') $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeStringWithOffset SynchronizeAcrossTimeZone = $true ScheduleType = 'Once' } InModuleScope -Parameters @{ - startTimeStringWithOffset = $startTimeStringWithOffset + expectedStartTimeStringWithOffset = $expectedStartTimeStringWithOffset } -ScriptBlock { - $script:startTimeStringWithOffset = $startTimeStringWithOffset + $script:expectedStartTimeStringWithOffset = $expectedStartTimeStringWithOffset } Mock -CommandName Get-ScheduledTask -MockWith { @@ -2695,7 +2707,7 @@ Describe 'DSC_ScheduledTask' { ) Triggers = @( [pscustomobject] @{ - StartBoundary = $startTimeStringWithOffset + StartBoundary = $expectedStartTimeStringWithOffset CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' } @@ -2709,12 +2721,12 @@ Describe 'DSC_ScheduledTask' { } } - It 'Should return the start time in DateTime format and SynchronizeAcrossTimeZone with value true' { + It 'Should return the start time and SynchronizeAcrossTimeZone with value true' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 $result = Get-TargetResource @getTargetResourceParameters - $result.StartTime | Should -Be (Get-Date -Date $startTimeStringWithOffset) + $result.StartTime | Should -Be $expectedStartTimeStringWithOffset $result.SynchronizeAcrossTimeZone | Should -BeTrue } } @@ -2762,7 +2774,7 @@ Describe 'DSC_ScheduledTask' { } } - It "Should set task trigger StartBoundary to $startTimeStringWithOffset" { + It "Should set task trigger StartBoundary to '$expectedStartTimeStringWithOffset'" { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -2770,40 +2782,18 @@ Describe 'DSC_ScheduledTask' { } Should -Invoke -CommandName Set-ScheduledTask -ParameterFilter { - $InputObject.Triggers[0].StartBoundary -eq $startTimeStringWithOffset + $InputObject.Triggers[0].StartBoundary -eq $expectedStartTimeStringWithOffset } } } } - Context 'When a scheduled task is configured to SynchronizeAcrossTimeZone and the ScheduleType is not Once, Daily or Weekly' { - BeforeAll { - $startTimeString = '2018-10-01T01:00:00' - $testParameters = $getTargetResourceParameters + @{ - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString - SynchronizeAcrossTimeZone = $true - ScheduleType = 'AtLogon' - } - } - - It 'Should throw when Set-TargetResource is called and SynchronizeAcrossTimeZone is used in combination with an unsupported trigger type' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $errorRecord = Get-InvalidArgumentRecord -Message $LocalizedData.SynchronizeAcrossTimeZoneInvalidScheduleType -ArgumentName 'SynchronizeAcrossTimeZone' - - { Set-TargetResource @testParameters } | Should -Throw $errorRecord - } - } - } - Context 'When a scheduled task is configured with the ScheduleType AtLogon and is in desired state' { BeforeAll { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'AtLogon' User = 'MockedDomain\MockedUser' Delay = '00:01:00' @@ -2845,7 +2835,7 @@ Describe 'DSC_ScheduledTask' { $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be (Get-Date -Date $testParameters.StartTime) + $result.StartTime | Should -Be $testParameters.StartTime $result.ScheduleType | Should -Be $testParameters.ScheduleType $result.User | Should -Be $testParameters.User $result.Delay | Should -Be $testParameters.Delay @@ -3017,7 +3007,7 @@ Describe 'DSC_ScheduledTask' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'OnIdle' Enable = $true } @@ -3054,7 +3044,7 @@ Describe 'DSC_ScheduledTask' { $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be (Get-Date -Date $testParameters.StartTime) + $result.StartTime | Should -Be $testParameters.StartTime $result.ScheduleType | Should -Be $testParameters.ScheduleType } } @@ -3073,7 +3063,7 @@ Describe 'DSC_ScheduledTask' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'OnIdle' Enable = $true } @@ -3164,7 +3154,7 @@ Describe 'DSC_ScheduledTask' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'AtCreation' Delay = '00:01:00' Enable = $true @@ -3203,7 +3193,7 @@ Describe 'DSC_ScheduledTask' { $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be (Get-Date -Date $testParameters.StartTime) + $result.StartTime | Should -Be $testParameters.StartTime $result.ScheduleType | Should -Be $testParameters.ScheduleType $result.Delay | Should -Be $testParameters.Delay } @@ -3223,7 +3213,7 @@ Describe 'DSC_ScheduledTask' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'AtCreation' Delay = '00:01:00' Enable = $true @@ -3265,7 +3255,7 @@ Describe 'DSC_ScheduledTask' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'OnSessionState' StateChange = 'OnConnectionFromLocalComputer' User = 'MockedDomain\MockedUser' @@ -3308,7 +3298,7 @@ Describe 'DSC_ScheduledTask' { $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be (Get-Date -Date $testParameters.StartTime) + $result.StartTime | Should -Be $testParameters.StartTime $result.ScheduleType | Should -Be $testParameters.ScheduleType $result.User | Should -Be $testParameters.User $result.StateChange | Should -Be $testParameters.StateChange @@ -3330,7 +3320,7 @@ Describe 'DSC_ScheduledTask' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString + StartTime = $startTimeString ScheduleType = 'OnSessionState' StateChange = 'OnConnectionFromLocalComputer' User = 'MockedDomain\MockedUser' @@ -3455,12 +3445,32 @@ Describe 'DSC_ScheduledTask\Test-DateStringContainsTimeZone' -Tag 'Private' { } } - Context 'When the date string contains a date with a timezone' { + Context 'When the date string contains a date with a negative timezone offset' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + Test-DateStringContainsTimeZone -DateString '2018-10-01T01:00:00-08:00' | Should -BeTrue + } + } + } + + Context 'When the date string contains a date with a positive timezone offset' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + Test-DateStringContainsTimeZone -DateString '2018-10-01T01:00:00+08:00' | Should -BeTrue + } + } + } + + Context 'When the date string contains a date with Zulu timezone offset' { It 'Should return $true' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - Test-DateStringContainsTimeZone -DateString ('2018-10-01T01:00:00' + (Get-Date -Format 'zzz')) | Should -BeTrue + Test-DateStringContainsTimeZone -DateString '2018-10-01T01:00:00Z' | Should -BeTrue } } }