diff --git a/CHANGELOG.md b/CHANGELOG.md index d89c7b09..935d3407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 by changing `CopyDirectories` to `CopyPaths` - Fixes [Issue #332](https://github.com/dsccommunity/ComputerManagementDsc/issues/332). - Pin `Pester` module to 4.10.1 because Pester 5.0 is missing code coverage - Fixes [Issue #336](https://github.com/dsccommunity/ComputerManagementDsc/issues/336). +- ScheduledTask + - Add "StopExisting" to valid values for MultipleInstances parameter - Fixes [Issue #333](https://github.com/dsccommunity/ComputerManagementDsc/issues/333). ## [8.2.0] - 2020-05-05 diff --git a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 index 1ac366e7..79b366ee 100644 --- a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 +++ b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 @@ -390,7 +390,7 @@ function Set-TargetResource $ExecutionTimeLimit = '08:00:00', [Parameter()] - [ValidateSet('IgnoreNew', 'Parallel', 'Queue')] + [ValidateSet('IgnoreNew', 'Parallel', 'Queue', 'StopExisting')] [System.String] $MultipleInstances = 'Queue', @@ -546,12 +546,16 @@ function Set-TargetResource WakeToRun = $WakeToRun RestartOnIdle = $RestartOnIdle DontStopOnIdleEnd = $DontStopOnIdleEnd - MultipleInstances = $MultipleInstances Priority = $Priority RestartCount = $RestartCount RunOnlyIfNetworkAvailable = $RunOnlyIfNetworkAvailable } + if ($MultipleInstances -ne 'StopExisting') + { + $settingParameters.Add('MultipleInstances', $MultipleInstances) + } + if ($IdleDuration -gt [System.TimeSpan] '00:00:00') { $settingParameters.Add('IdleDuration', $IdleDuration) @@ -579,6 +583,16 @@ function Set-TargetResource $setting = New-ScheduledTaskSettingsSet @settingParameters + <# The following workaround is needed because the TASK_INSTANCES_STOP_EXISTING value of + https://docs.microsoft.com/en-us/windows/win32/taskschd/tasksettings-multipleinstances is missing + from the Microsoft.PowerShell.Cmdletization.GeneratedTypes.ScheduledTask.MultipleInstancesEnum, + which is used for the other values of $MultipleInstances. (at least currently, as of June, 2020) + #> + if ($MultipleInstances -eq 'StopExisting') + { + $setting.CimInstanceProperties.Item('MultipleInstances').Value = 3 + } + $scheduledTaskArguments += @{ Settings = $setting } @@ -1262,7 +1276,7 @@ function Test-TargetResource $ExecutionTimeLimit = '08:00:00', [Parameter()] - [ValidateSet('IgnoreNew', 'Parallel', 'Queue')] + [ValidateSet('IgnoreNew', 'Parallel', 'Queue', 'StopExisting')] [System.String] $MultipleInstances = 'Queue', @@ -1810,6 +1824,19 @@ function Get-CurrentResource $PrincipalId = 'UserId' } + <# The following workaround is needed because Get-StartedTask currently returns NULL for the value + of $settings.MultipleInstances when the started task is set to "Stop the existing instance". + https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/40685125-bug-get-scheduledtask-returns-null-for-value-of-m + #> + $MultipleInstances = [System.String] $settings.MultipleInstances + if ([System.String]::IsNullOrEmpty($MultipleInstances)) + { + if ($task.settings.CimInstanceProperties.Item('MultipleInstances').Value -eq 3) + { + $MultipleInstances = 'StopExisting' + } + } + $result = @{ TaskName = $task.TaskName TaskPath = $task.TaskPath @@ -1847,7 +1874,7 @@ function Get-CurrentResource RestartOnIdle = $settings.IdleSettings.RestartOnIdle DontStopOnIdleEnd = -not $settings.IdleSettings.StopOnIdleEnd ExecutionTimeLimit = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.ExecutionTimeLimit - MultipleInstances = [System.String] $settings.MultipleInstances + MultipleInstances = $MultipleInstances Priority = $settings.Priority RestartCount = $settings.RestartCount RestartInterval = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.RestartInterval diff --git a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof index 6583b6b9..941b833b 100644 --- a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof +++ b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof @@ -38,7 +38,7 @@ class DSC_ScheduledTask : OMI_BaseResource [Write, Description("Indicates that Task Scheduler restarts the task when the computer cycles into an idle condition more than once.")] Boolean RestartOnIdle; [Write, Description("Indicates that Task Scheduler does not terminate the task if the idle condition ends before the task is completed.")] Boolean DontStopOnIdleEnd; [Write, Description("Specifies the amount of time that Task Scheduler is allowed to complete the task.")] String ExecutionTimeLimit; - [Write, Description("Specifies the policy that defines how Task Scheduler handles multiple instances of the task."), ValueMap{"IgnoreNew","Parallel","Queue"}, Values{"IgnoreNew","Parallel","Queue"}] String MultipleInstances; + [Write, Description("Specifies the policy that defines how Task Scheduler handles multiple instances of the task."), ValueMap{"IgnoreNew","Parallel","Queue", "StopExisting"}, Values{"IgnoreNew","Parallel","Queue", "StopExisting"}] String MultipleInstances; [Write, Description("Specifies the priority level of the task. Priority must be an integer from 0 (highest priority) to 10 (lowest priority). The default value is 7. Priority levels 7 and 8 are used for background tasks. Priority levels 4, 5, and 6 are used for interactive tasks.")] Uint32 Priority; [Write, Description("Specifies the number of times that Task Scheduler attempts to restart the task.")] Uint32 RestartCount; [Write, Description("Specifies the amount of time that Task Scheduler attempts to restart the task.")] String RestartInterval; diff --git a/source/DSCResources/DSC_ScheduledTask/README.md b/source/DSCResources/DSC_ScheduledTask/README.md index 8ea83832..efc6df41 100644 --- a/source/DSCResources/DSC_ScheduledTask/README.md +++ b/source/DSCResources/DSC_ScheduledTask/README.md @@ -6,6 +6,16 @@ scheduled tasks. ## Known Issues +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 +Task Settings ([TaskSettings.MultipleInstances Property](https://docs.microsoft.com/en-us/windows/win32/taskschd/tasksettings-multipleinstances "TaskSettings.MultipleInstances Property")). +The `MultipleInstancesEnum` enumerator has three values, which can be mapped to three +of the four valid values, but there is no value corresponding to `TASK_INSTANCES_STOP_EXISTING`. +The result of this omission is that a workaround is required to +accommodate the `StopExisting` value for the `MultipleInstances` parameter, +which would not be necessary if the enumerator had all four valid values. + ### ExecuteAsCredential #### When Using a BUILTIN Group diff --git a/tests/Unit/DSC_ScheduledTask.Tests.ps1 b/tests/Unit/DSC_ScheduledTask.Tests.ps1 index a15d4901..a1247c30 100644 --- a/tests/Unit/DSC_ScheduledTask.Tests.ps1 +++ b/tests/Unit/DSC_ScheduledTask.Tests.ps1 @@ -112,7 +112,9 @@ try Verbose = $true } - Mock -CommandName Get-ScheduledTask -MockWith { return $null } + $scheduledTask = $null + Mock -CommandName Get-ScheduledTask -MockWith { return $scheduledTask } + Mock -CommandName Register-ScheduledTask It 'Should return the correct values from Get-TargetResource' { $result = Get-TargetResource @getTargetResourceParameters @@ -125,7 +127,9 @@ try It 'Should create the scheduled task in the set method' { Set-TargetResource @testParameters + Assert-MockCalled Register-ScheduledTask -Exactly -Times 1 } + } Context 'A scheduled task exists, but it should not' { @@ -154,6 +158,10 @@ try CimClassName = 'MSFT_TaskTimeTrigger' } }) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = @{ UserId = 'SYSTEM' } @@ -198,6 +206,7 @@ try } Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'StopExisting' } } } @@ -245,6 +254,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'StopExisting' } } } @@ -314,6 +324,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -364,6 +378,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -409,6 +427,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -459,6 +481,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -503,6 +529,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -549,6 +579,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -595,6 +629,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'WrongUser' } @@ -647,6 +685,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'DEMO\RightUser' LogonType = 'Password' @@ -701,6 +743,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'DEMO\RightUser' RunLevel = 'Limited' @@ -755,6 +801,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -807,6 +857,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -859,8 +913,9 @@ try } } ) - Settings = [pscustomobject] @{ + Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -919,6 +974,7 @@ try Settings = [pscustomobject] @{ Enabled = $true ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.RepeatInterval).TotalSeconds + 60)S" + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -986,6 +1042,7 @@ try } ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.ExecutionTimeLimit).TotalMinutes)M" RestartInterval = "PT$([System.TimeSpan]::Parse($testParameters.RestartInterval).TotalMinutes)M" + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -1036,6 +1093,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $false + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -1086,6 +1144,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $false + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -1140,6 +1199,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $false + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -1222,6 +1282,7 @@ try } ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.ExecutionTimeLimit).TotalMinutes)M" RestartInterval = "PT$([System.TimeSpan]::Parse($testParameters.RestartInterval).TotalMinutes)M" + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -1287,6 +1348,7 @@ try } ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.ExecutionTimeLimit).TotalMinutes)M" RestartInterval = "PT$([System.TimeSpan]::Parse($testParameters.RestartInterval).TotalMinutes)M" + MultipleInstances = 'IgnoreNew' } Principal = [pscustomobject] @{ UserId = 'SYSTEM' @@ -1339,6 +1401,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -1390,6 +1456,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -1441,6 +1511,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'SYSTEM' } @@ -1481,6 +1555,7 @@ try } Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'StopExisting' } } } @@ -1528,6 +1603,7 @@ try } Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'StopExisting' } } } @@ -1599,6 +1675,7 @@ try } Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'StopExisting' } } } @@ -1652,6 +1729,7 @@ try } Settings = [pscustomobject] @{ Enabled = $true + MultipleInstances = 'StopExisting' } } } @@ -1722,6 +1800,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'NT AUTHORITY\' + $testParameters.BuiltInAccount LogonType = 'ServiceAccount' @@ -1792,6 +1874,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'gMSA$' } @@ -1839,6 +1925,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } Principal = [pscustomobject] @{ UserId = 'update_gMSA$' } @@ -1892,6 +1982,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } } } @@ -1922,6 +2016,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } } } @@ -1966,6 +2064,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } } } @@ -1996,6 +2098,10 @@ try } } ) + Settings = [pscustomobject] @{ + Enabled = $true + MultipleInstances = 'IgnoreNew' + } } } @@ -2030,12 +2136,13 @@ try Context 'When a scheduled task is configured with the ScheduleType AtLogon and is in desired state' { $startTimeString = '2018-10-01T01:00:00' $testParameters = $getTargetResourceParameters + @{ - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString - ScheduleType = 'AtLogon' - Delay = '00:01:00' - Enable = $true - Verbose = $true + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + StartTime = Get-Date -Date $startTimeString + ScheduleType = 'AtLogon' + Delay = '00:01:00' + Enable = $true + Verbose = $true + MultipleInstances = 'StopExisting' } Mock -CommandName Get-ScheduledTask -MockWith { @@ -2058,6 +2165,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $testParameters.Enable + MultipleInstances = $testParameters.MultipleInstances } } } @@ -2105,6 +2213,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $testParameters.Enable + MultipleInstances = 'StopExisting' } } } @@ -2157,6 +2266,7 @@ try ) Settings = [pscustomobject] @{ Enabled = $testParameters.Enable + MultipleInstances = 'IgnoreNew' } } }