diff --git a/CHANGELOG.md b/CHANGELOG.md index 147f52b9..bddb9e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,18 @@ - RemoteDesktopAdmin: - New resource for configuring Remote Desktop for Administration - fixes [Issue #224](https://github.com/PowerShell/ComputerManagementDsc/issues/224). +- Updated common function `Test-DscParameterState` to support ordered comparison + of arrays by copying function and tests from `NetworkingDsc` - fixes [Issue #250](https://github.com/PowerShell/ComputerManagementDsc/issues/250). +- BREAKING CHANGE: ScheduledTask: + - Correct output type of `DaysInterval`,`StartTime`,`WeeksDaysOfWeek`, + and `WeeksInterval` parameters from `Get-TargetResource` to match MOF. + - Refactored `Get-TargetResource` to remove parameters that + are not key or required - fixes [Issue #249](https://github.com/PowerShell/ComputerManagementDsc/issues/249). + - Added function `Test-DateStringContainsTimeZone` to determine if a string + containing a date time includes a time zone. + - Enable verbose preference to be passed through to `Test-DscParameterState`. + - Changed `Test-TargetResource` so that `StartTime` is only compared for + trigger types `Daily`,`Weekly` or `Once`. ## 6.5.0.0 diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 7f644106..6374e1f4 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -26,548 +26,34 @@ $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ScheduledTask' <# .SYNOPSIS - Tests if the current resource state matches the desired resource state. + Gets the current state of the resource. .PARAMETER TaskName The name of the task. .PARAMETER TaskPath The path to the task - defaults to the root directory. - - .PARAMETER Description - The task description. Not used in Get-TargetResource. - - .PARAMETER ActionExecutable - The path to the .exe for this task. - - .PARAMETER ActionArguments - The arguments to pass the executable. Not used in Get-TargetResource. - - .PARAMETER ActionWorkingPath - The working path to specify for the executable. Not used in Get-TargetResource. - - .PARAMETER ScheduleType - When should the task be executed. - - .PARAMETER RepeatInterval - How many units (minutes, hours, days) between each run of this task? - Not used in Get-TargetResource. - - .PARAMETER StartTime - 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. - - .PARAMETER Enable - True if the task should be enabled, false if it should be disabled. - Not used in Get-TargetResource. - - .PARAMETER BuiltInAccount - Run the task as one of the built in service accounts. - When set ExecuteAsCredential will be ignored and LogonType will be set to 'ServiceAccount' - - .PARAMETER ExecuteAsCredential - The credential this task should execute as. If not specified defaults to running - 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 - 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. - Not used in Get-TargetResource. - - .PARAMETER RandomDelay - 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. Not used in Get-TargetResource. - - .PARAMETER RepetitionDuration - Specifies how long the repetition pattern repeats after the task starts. - Not used in Get-TargetResource. - - .PARAMETER DaysOfWeek - Specifies an array of the days of the week on which Task Scheduler runs the task. - Not used in Get-TargetResource. - - .PARAMETER WeeksInterval - Specifies the interval between the weeks in the schedule. An interval of 1 produces - a weekly schedule. An interval of 2 produces an every-other week schedule. - Not used in Get-TargetResource. - - .PARAMETER User - Specifies the identifier of the user for a trigger that starts a task when a - user logs on. Not used in Get-TargetResource. - - .PARAMETER DisallowDemandStart - Indicates whether the task is prohibited to run on demand or not. Defaults - to $false. Not used in Get-TargetResource. - - .PARAMETER DisallowHardTerminate - Indicates whether the task is prohibited to be terminated or not. Defaults - to $false - - .PARAMETER Compatibility - The task compatibility level. Defaults to Vista. Not used in - Get-TargetResource. - - .PARAMETER AllowStartIfOnBatteries - Indicates whether the task should start if the machine is on batteries or not. - Defaults to $false. Not used in Get-TargetResource. - - .PARAMETER Hidden - Indicates that the task is hidden in the Task Scheduler UI. - Not used in Get-TargetResource. - - .PARAMETER RunOnlyIfIdle - Indicates that Task Scheduler runs the task only when the computer is idle. - Not used in Get-TargetResource. - - .PARAMETER IdleWaitTimeout - Specifies the amount of time that Task Scheduler waits for an idle condition to occur. - Not used in Get-TargetResource. - - .PARAMETER NetworkName - Specifies the name of a network profile that Task Scheduler uses to determine - if the task can run. - The Task Scheduler UI uses this setting for display purposes. Specify a network - name if you specify the RunOnlyIfNetworkAvailable parameter. Not used in - Get-TargetResource. - - .PARAMETER DisallowStartOnRemoteAppSession - Indicates that the task does not start if the task is triggered to run in a Remote - Applications Integrated Locally (RAIL) session. Not used in Get-TargetResource. - - .PARAMETER StartWhenAvailable - Indicates that Task Scheduler can start the task at any time after its scheduled - time has passed. Not used in Get-TargetResource. - - .PARAMETER DontStopIfGoingOnBatteries - Indicates that the task does not stop if the computer switches to battery power. - Not used in Get-TargetResource. - - .PARAMETER WakeToRun - Indicates that Task Scheduler wakes the computer before it runs the task. - Not used in Get-TargetResource. - - .PARAMETER IdleDuration - Specifies the amount of time that the computer must be in an idle state before - Task Scheduler runs the task. Not used in Get-TargetResource. - - .PARAMETER RestartOnIdle - Indicates that Task Scheduler restarts the task when the computer cycles into an - idle condition more than once. Not used in Get-TargetResource. - - .PARAMETER DontStopOnIdleEnd - Indicates that Task Scheduler does not terminate the task if the idle condition - ends before the task is completed. Not used in Get-TargetResource. - - .PARAMETER ExecutionTimeLimit - Specifies the amount of time that Task Scheduler is allowed to complete the task. - Not used in Get-TargetResource. - - .PARAMETER MultipleInstances - Specifies the policy that defines how Task Scheduler handles multiple instances - of the task. Not used in Get-TargetResource. - - .PARAMETER Priority - 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. - Not used in Get-TargetResource. - - .PARAMETER RestartCount - Specifies the number of times that Task Scheduler attempts to restart the task. - Not used in Get-TargetResource. - - .PARAMETER RestartInterval - Specifies the amount of time that Task Scheduler attempts to restart the task. - Not used in Get-TargetResource. - - .PARAMETER RunOnlyIfNetworkAvailable - Indicates that Task Scheduler runs the task only when a network is available. Task - Scheduler uses the NetworkID parameter and NetworkName parameter that you specify - in this cmdlet to determine if the network is available. Not used in Get-TargetResource. - - .PARAMETER RunLevel - Specifies the level of user rights that Task Scheduler uses to run the tasks that - are associated with the principal. Defaults to 'Limited'. Not used in - Get-TargetResource. - - .PARAMETER LogonType - Specifies the security logon method that Task Scheduler uses to run the tasks that - are associated with the principal. Not used in Get-TargetResource. - - .PARAMETER EventSubscription - The event subscription in a string that can be parsed as valid XML. This parameter is only - valid in combination with the OnEvent Schedule Type. For the query schema please check: - https://docs.microsoft.com/en-us/windows/desktop/WES/queryschema-schema - - .PARAMETER Delay - The time to wait after an event based trigger was triggered. This parameter is only - valid in combination with the OnEvent Schedule Type. #> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param - ( - [Parameter(Mandatory = $true)] - [System.String] - $TaskName, - - [Parameter()] - [System.String] - $TaskPath = '\', - - [Parameter()] - [System.String] - $Description, - - [Parameter()] - [System.String] - $ActionExecutable, - - [Parameter()] - [System.String] - $ActionArguments, - - [Parameter()] - [System.String] - $ActionWorkingPath, - - [Parameter()] - [System.String] - [ValidateSet('Once', 'Daily', 'Weekly', 'AtStartup', 'AtLogOn', 'OnEvent')] - $ScheduleType, - - [Parameter()] - [System.String] - $RepeatInterval = '00:00:00', - - [Parameter()] - [System.DateTime] - $StartTime = [System.DateTime]::Today, - - [Parameter()] - [System.Boolean] - $SynchronizeAcrossTimeZone = $false, - - [Parameter()] - [System.String] - [ValidateSet('Present', 'Absent')] - $Ensure = 'Present', - - [Parameter()] - [System.Boolean] - $Enable = $true, - - [Parameter()] - [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] - [System.String] - $BuiltInAccount, - - [Parameter()] - [System.Management.Automation.PSCredential] - $ExecuteAsCredential, - - [Parameter()] - [System.String] - $ExecuteAsGMSA, - - [Parameter()] - [System.UInt32] - $DaysInterval = 1, - - [Parameter()] - [System.String] - $RandomDelay = '00:00:00', - - [Parameter()] - [System.String] - $RepetitionDuration = '00:00:00', - - [Parameter()] - [System.String[]] - $DaysOfWeek, - - [Parameter()] - [System.UInt32] - $WeeksInterval = 1, - - [Parameter()] - [System.String] - $User, - - [Parameter()] - [System.Boolean] - $DisallowDemandStart = $false, - - [Parameter()] - [System.Boolean] - $DisallowHardTerminate = $false, - - [Parameter()] - [ValidateSet('AT', 'V1', 'Vista', 'Win7', 'Win8')] - [System.String] - $Compatibility = 'Vista', - - [Parameter()] - [System.Boolean] - $AllowStartIfOnBatteries = $false, - - [Parameter()] - [System.Boolean] - $Hidden = $false, - - [Parameter()] - [System.Boolean] - $RunOnlyIfIdle = $false, - - [Parameter()] - [System.String] - $IdleWaitTimeout = '02:00:00', - - [Parameter()] - [System.String] - $NetworkName, - - [Parameter()] - [System.Boolean] - $DisallowStartOnRemoteAppSession = $false, - - [Parameter()] - [System.Boolean] - $StartWhenAvailable = $false, - - [Parameter()] - [System.Boolean] - $DontStopIfGoingOnBatteries = $false, - - [Parameter()] - [System.Boolean] - $WakeToRun = $false, - - [Parameter()] - [System.String] - $IdleDuration = '01:00:00', - - [Parameter()] - [System.Boolean] - $RestartOnIdle = $false, - - [Parameter()] - [System.Boolean] - $DontStopOnIdleEnd = $false, - - [Parameter()] - [System.String] - $ExecutionTimeLimit = '08:00:00', - - [Parameter()] - [ValidateSet('IgnoreNew', 'Parallel', 'Queue')] - [System.String] - $MultipleInstances = 'Queue', - - [Parameter()] - [System.UInt32] - $Priority = 7, - - [Parameter()] - [System.UInt32] - $RestartCount = 0, - - [Parameter()] - [System.String] - $RestartInterval = '00:00:00', - - [Parameter()] - [System.Boolean] - $RunOnlyIfNetworkAvailable = $false, - - [Parameter()] - [ValidateSet('Limited', 'Highest')] - [System.String] - $RunLevel = 'Limited', - - [Parameter()] - [ValidateSet('Group', 'Interactive', 'InteractiveOrPassword', 'None', 'Password', 'S4U', 'ServiceAccount')] - [System.String] - $LogonType, - - [Parameter()] - [System.String] - $EventSubscription, - - [Parameter()] - [System.String] - $Delay = '00:00:00' - ) - - $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath - - Write-Verbose -Message ($script:localizedData.GetScheduledTaskMessage -f $TaskName, $TaskPath) - - $task = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue - - if ($null -eq $task) - { - Write-Verbose -Message ($script:localizedData.TaskNotFoundMessage -f $TaskName, $TaskPath) - - return @{ - TaskName = $TaskName - TaskPath = $TaskPath - Ensure = 'Absent' - } - } - else - { - Write-Verbose -Message ($script:localizedData.TaskFoundMessage -f $TaskName, $TaskPath) - - $action = $task.Actions | Select-Object -First 1 - $trigger = $task.Triggers | Select-Object -First 1 - $settings = $task.Settings - $returnScheduleType = 'Unknown' - - switch ($trigger.CimClass.CimClassName) - { - 'MSFT_TaskTimeTrigger' - { - $returnScheduleType = 'Once' - break - } - - 'MSFT_TaskDailyTrigger' - { - $returnScheduleType = 'Daily' - break - } - - 'MSFT_TaskWeeklyTrigger' - { - $returnScheduleType = 'Weekly' - break - } - - 'MSFT_TaskBootTrigger' - { - $returnScheduleType = 'AtStartup' - break - } - - 'MSFT_TaskLogonTrigger' - { - $returnScheduleType = 'AtLogon' - break - } - - 'MSFT_TaskEventTrigger' - { - $returnScheduleType = 'OnEvent' - break - } - - default - { - $returnScheduleType = '' - Write-Verbose -Message ($script:localizedData.TriggerTypeUnknown -f $trigger.CimClass.CimClassName) - } - } - - Write-Verbose -Message ($script:localizedData.DetectedScheduleTypeMessage -f $returnScheduleType) - - $daysOfWeek = @() - - foreach ($binaryAdductor in 1, 2, 4, 8, 16, 32, 64) - { - $day = $trigger.DaysOfWeek -band $binaryAdductor - - if ($day -ne 0) - { - $daysOfWeek += [ScheduledTask.DaysOfWeek] $day - } - } - - $startAt = $trigger.StartBoundary + ( + [Parameter(Mandatory = $true)] + [System.String] + $TaskName, - if ($startAt) - { - $stringSynchronizeAcrossTimeZone = Get-DateTimeString -Date $startAt -SynchronizeAcrossTimeZone $true - $returnSynchronizeAcrossTimeZone = $startAt -eq $stringSynchronizeAcrossTimeZone - } - else - { - $startAt = $StartTime - $returnSynchronizeAcrossTimeZone = $false - } + [Parameter()] + [System.String] + $TaskPath = '\' + ) - $result = @{ - TaskName = $task.TaskName - TaskPath = $task.TaskPath - StartTime = $startAt - SynchronizeAcrossTimeZone = $returnSynchronizeAcrossTimeZone - Ensure = 'Present' - Description = $task.Description - ActionExecutable = $action.Execute - ActionArguments = $action.Arguments - ActionWorkingPath = $action.WorkingDirectory - 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 - RepetitionDuration = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Duration -AllowIndefinitely - DaysOfWeek = $daysOfWeek - WeeksInterval = $trigger.WeeksInterval - User = $task.Principal.UserId - DisallowDemandStart = -not $settings.AllowDemandStart - DisallowHardTerminate = -not $settings.AllowHardTerminate - Compatibility = $settings.Compatibility - AllowStartIfOnBatteries = -not $settings.DisallowStartIfOnBatteries - Hidden = $settings.Hidden - RunOnlyIfIdle = $settings.RunOnlyIfIdle - IdleWaitTimeout = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.IdleSettings.WaitTimeout - NetworkName = $settings.NetworkSettings.Name - DisallowStartOnRemoteAppSession = $settings.DisallowStartOnRemoteAppSession - StartWhenAvailable = $settings.StartWhenAvailable - DontStopIfGoingOnBatteries = -not $settings.StopIfGoingOnBatteries - WakeToRun = $settings.WakeToRun - IdleDuration = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.IdleSettings.IdleDuration - RestartOnIdle = $settings.IdleSettings.RestartOnIdle - DontStopOnIdleEnd = -not $settings.IdleSettings.StopOnIdleEnd - ExecutionTimeLimit = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.ExecutionTimeLimit - MultipleInstances = $settings.MultipleInstances - Priority = $settings.Priority - RestartCount = $settings.RestartCount - RestartInterval = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.RestartInterval - RunOnlyIfNetworkAvailable = $settings.RunOnlyIfNetworkAvailable - RunLevel = [System.String] $task.Principal.RunLevel - LogonType = [System.String] $task.Principal.LogonType - EventSubscription = $trigger.Subscription - Delay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Delay - } + $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath - if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) - { - $result.Add('BuiltInAccount', $task.Principal.UserId) - } + Write-Verbose -Message ($script:localizedData.GetScheduledTaskMessage -f $TaskName, $TaskPath) - return $result - } + return Get-CurrentResource @PSBoundParameters } <# @@ -954,7 +440,7 @@ function Set-TargetResource [System.TimeSpan] $ExecutionTimeLimit = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $ExecutionTimeLimit [System.TimeSpan] $RestartInterval = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RestartInterval - $currentValues = Get-TargetResource @PSBoundParameters + $currentValues = Get-CurrentResource -TaskName $TaskName -TaskPath $TaskPath if ($Ensure -eq 'Present') { @@ -1805,6 +1291,8 @@ function Test-TargetResource Write-Verbose -Message ($script:localizedData.TestScheduledTaskMessage -f $TaskName, $TaskPath) + $currentValues = Get-CurrentResource -TaskName $TaskName -TaskPath $TaskPath + # Convert the strings containing time spans to TimeSpan Objects if ($PSBoundParameters.ContainsKey('RepeatInterval')) { @@ -1861,11 +1349,30 @@ function Test-TargetResource if ($ScheduleType -in @('Once', 'Daily', 'Weekly')) { $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) + so that the test does not fail. + #> + if ($currentValues['StartTime']) + { + $currentValues['StartTime'] = Get-DateTimeString ` + -Date $currentValues['StartTime'] ` + -SynchronizeAcrossTimeZone $currentValues['SynchronizeAcrossTimeZone'] + } + else + { + $currentValues['StartTime'] = Get-DateTimeString ` + -Date $StartTime ` + -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone + } + } + else + { + # Do not compare StartTime for triggers that aren't Once, Daily or Weekly. + $null = $PSBoundParameters.Remove('StartTime') + $null = $currentValues.Remove('StartTime') } - - $currentValues = Get-TargetResource @PSBoundParameters - - Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') { @@ -1947,7 +1454,10 @@ function Test-TargetResource Write-Verbose -Message ($script:localizedData.TestingDscParameterStateMessage) - return Test-DscParameterState -CurrentValues $currentValues -DesiredValues $desiredValues + return Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$VerbosePreference } <# @@ -2143,3 +1653,211 @@ Function Get-DateTimeString return $returnDate } + +<# + .SYNOPSIS + Returns the current values of the resource. + + .PARAMETER TaskName + The name of the task. + + .PARAMETER TaskPath + The path to the task - defaults to the root directory. +#> +function Get-CurrentResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $TaskName, + + [Parameter()] + [System.String] + $TaskPath = '\' + ) + + $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath + + Write-Verbose -Message ($script:localizedData.GettingCurrentTaskValuesMessage -f $TaskName, $TaskPath) + + $task = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue + + if ($null -eq $task) + { + Write-Verbose -Message ($script:localizedData.TaskNotFoundMessage -f $TaskName, $TaskPath) + + $result = @{ + TaskName = $TaskName + TaskPath = $TaskPath + Ensure = 'Absent' + } + } + else + { + Write-Verbose -Message ($script:localizedData.TaskFoundMessage -f $TaskName, $TaskPath) + + $action = $task.Actions | Select-Object -First 1 + $trigger = $task.Triggers | Select-Object -First 1 + $settings = $task.Settings + $returnScheduleType = 'Unknown' + + switch ($trigger.CimClass.CimClassName) + { + 'MSFT_TaskTimeTrigger' + { + $returnScheduleType = 'Once' + break + } + + 'MSFT_TaskDailyTrigger' + { + $returnScheduleType = 'Daily' + break + } + + 'MSFT_TaskWeeklyTrigger' + { + $returnScheduleType = 'Weekly' + break + } + + 'MSFT_TaskBootTrigger' + { + $returnScheduleType = 'AtStartup' + break + } + + 'MSFT_TaskLogonTrigger' + { + $returnScheduleType = 'AtLogon' + break + } + + 'MSFT_TaskEventTrigger' + { + $returnScheduleType = 'OnEvent' + break + } + + default + { + $returnScheduleType = '' + Write-Verbose -Message ($script:localizedData.TriggerTypeUnknown -f $trigger.CimClass.CimClassName) + } + } + + Write-Verbose -Message ($script:localizedData.DetectedScheduleTypeMessage -f $returnScheduleType) + + $daysOfWeek = @() + + foreach ($binaryAdductor in 1, 2, 4, 8, 16, 32, 64) + { + $day = $trigger.DaysOfWeek -band $binaryAdductor + + if ($day -ne 0) + { + $daysOfWeek += [System.String][ScheduledTask.DaysOfWeek] $day + } + } + + $startAt = $trigger.StartBoundary + + if ($startAt) + { + $synchronizeAcrossTimeZone = Test-DateStringContainsTimeZone -DateString $startAt + $startTime = [System.DateTime] $startAt + } + else + { + $startTime = $null + $synchronizeAcrossTimeZone = $false + } + + $result = @{ + TaskName = $task.TaskName + TaskPath = $task.TaskPath + StartTime = $startTime + SynchronizeAcrossTimeZone = $synchronizeAcrossTimeZone + Ensure = 'Present' + Description = $task.Description + ActionExecutable = $action.Execute + ActionArguments = $action.Arguments + ActionWorkingPath = $action.WorkingDirectory + ScheduleType = $returnScheduleType + RepeatInterval = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Interval + ExecuteAsCredential = $task.Principal.UserId + ExecuteAsGMSA = $task.Principal.UserId -replace '^.+\\|@.+', $null + Enable = $settings.Enabled + DaysInterval = [System.Uint32] $trigger.DaysInterval + RandomDelay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.RandomDelay + RepetitionDuration = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Duration -AllowIndefinitely + DaysOfWeek = [System.String[]] $daysOfWeek + WeeksInterval = [System.Uint32] $trigger.WeeksInterval + User = $task.Principal.UserId + DisallowDemandStart = -not $settings.AllowDemandStart + DisallowHardTerminate = -not $settings.AllowHardTerminate + Compatibility = [System.String] $settings.Compatibility + AllowStartIfOnBatteries = -not $settings.DisallowStartIfOnBatteries + Hidden = $settings.Hidden + RunOnlyIfIdle = $settings.RunOnlyIfIdle + IdleWaitTimeout = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.IdleSettings.WaitTimeout + NetworkName = $settings.NetworkSettings.Name + DisallowStartOnRemoteAppSession = $settings.DisallowStartOnRemoteAppSession + StartWhenAvailable = $settings.StartWhenAvailable + DontStopIfGoingOnBatteries = -not $settings.StopIfGoingOnBatteries + WakeToRun = $settings.WakeToRun + IdleDuration = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.IdleSettings.IdleDuration + RestartOnIdle = $settings.IdleSettings.RestartOnIdle + DontStopOnIdleEnd = -not $settings.IdleSettings.StopOnIdleEnd + ExecutionTimeLimit = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.ExecutionTimeLimit + MultipleInstances = $settings.MultipleInstances + Priority = $settings.Priority + RestartCount = $settings.RestartCount + RestartInterval = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.RestartInterval + RunOnlyIfNetworkAvailable = $settings.RunOnlyIfNetworkAvailable + RunLevel = [System.String] $task.Principal.RunLevel + LogonType = [System.String] $task.Principal.LogonType + EventSubscription = $trigger.Subscription + Delay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Delay + } + + if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) + { + $result.Add('BuiltInAccount', $task.Principal.UserId) + } + } + + Write-Verbose -Message ($script:localizedData.CurrentTaskValuesRetrievedMessage -f $TaskName, $TaskPath) + + return $result +} + +<# + .SYNOPSIS + Test if a date string contains a time zone. + + .DESCRIPTION + This function returns true if the string contains a time + zone appended to it. This is used to determine if the + SynchronizeAcrossTimeZone parameter has been set in a + trigger. + + .PARAMETER DateString + The date string to test. +#> +function Test-DateStringContainsTimeZone +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DateString + ) + + return $DateString.Contains('+') +} diff --git a/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 b/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 index e9cd9ad5..8e6ef841 100644 --- a/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 +++ b/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 @@ -29,7 +29,8 @@ ConvertFrom-StringData @' RemoveScheduledTaskMessage = Removing scheduled task '{0}' from '{1}'. UpdateScheduledTaskMessage = Updating scheduled task '{0}' in '{1}'. TestScheduledTaskMessage = Testing scheduled task '{0}' in '{1}'. - GetCurrentTaskValuesMessage = Current scheduled task values retrieved. + GettingCurrentTaskValuesMessage = Getting current scheduled task values for task '{0}' in '{1}'. + CurrentTaskValuesRetrievedMessage = Current scheduled task values for task '{0}' in '{1}' retrieved. CurrentTaskValuesNullMessage = Current scheduled values were null. TestingDscParameterStateMessage = Testing DSC parameter state. '@ diff --git a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 6454a886..ebad769f 100644 --- a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -209,22 +209,28 @@ function Remove-CommonParameter <# .SYNOPSIS - Tests the status of DSC resource parameters + Tests the status of DSC resource parameters. .DESCRIPTION - This function tests the parameter status of DSC resource parameters against the current values present on the system + This function tests the parameter status of DSC resource parameters against the current values present on the system. .PARAMETER CurrentValues - A hashtable with the current values on the system, obtained by e.g. Get-TargetResource + A hashtable with the current values on the system, obtained by e.g. Get-TargetResource. .PARAMETER DesiredValues - The hashtable of desired values + The hashtable of desired values. .PARAMETER ValuesToCheck - The values to check if not all values should be checked + The values to check if not all values should be checked. .PARAMETER TurnOffTypeChecking - Indicates that the type of the parameter should not be checked + Indicates that the type of the parameter should not be checked. + + .PARAMETER ReverseCheck + Indicates that a reverse check should be done. The current and desired state are swapped for another test. + + .PARAMETER SortArrayValues + If the sorting of array values does not matter, values are sorted internally before doing the comparison. #> function Test-DscParameterState { @@ -232,7 +238,7 @@ function Test-DscParameterState param ( [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] + [System.Object] $CurrentValues, [Parameter(Mandatory = $true)] @@ -245,11 +251,31 @@ function Test-DscParameterState [Parameter()] [switch] - $TurnOffTypeChecking + $TurnOffTypeChecking, + + [Parameter()] + [switch] + $ReverseCheck, + + [Parameter()] + [switch] + $SortArrayValues ) $returnValue = $true + if ($CurrentValues -is [Microsoft.Management.Infrastructure.CimInstance] -or + $CurrentValues -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $CurrentValues = ConvertTo-HashTable -CimInstance $CurrentValues + } + + if ($DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance] -or + $DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $DesiredValues = ConvertTo-HashTable -CimInstance $DesiredValues + } + $types = 'System.Management.Automation.PSBoundParametersDictionary', 'System.Collections.Hashtable', 'Microsoft.Management.Infrastructure.CimInstance' if ($DesiredValues.GetType().FullName -notin $types) @@ -259,6 +285,13 @@ function Test-DscParameterState -ArgumentName 'DesiredValues' } + if ($CurrentValues.GetType().FullName -notin $types) + { + New-InvalidArgumentException ` + -Message ($script:localizedData.InvalidCurrentValuesError -f $CurrentValues.GetType().FullName) ` + -ArgumentName 'CurrentValues' + } + if ($DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance] -and -not $ValuesToCheck) { New-InvalidArgumentException ` @@ -279,24 +312,38 @@ function Test-DscParameterState foreach ($key in $keyList) { - if ($null -ne $desiredValuesClean.$key) + $desiredValue = $desiredValuesClean.$key + $currentValue = $CurrentValues.$key + + if ($desiredValue -is [Microsoft.Management.Infrastructure.CimInstance] -or + $desiredValue -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $desiredValue = ConvertTo-HashTable -CimInstance $desiredValue + } + if ($currentValue -is [Microsoft.Management.Infrastructure.CimInstance] -or + $currentValue -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $currentValue = ConvertTo-HashTable -CimInstance $currentValue + } + + if ($null -ne $desiredValue) { - $desiredType = $desiredValuesClean.$key.GetType() + $desiredType = $desiredValue.GetType() } else { - $desiredType = [PSObject] @{ + $desiredType = @{ Name = 'Unknown' } } - if ($null -ne $CurrentValues.$key) + if ($null -ne $currentValue) { - $currentType = $CurrentValues.$key.GetType() + $currentType = $currentValue.GetType() } else { - $currentType = [PSObject] @{ + $currentType = @{ Name = 'Unknown' } } @@ -304,26 +351,26 @@ function Test-DscParameterState if ($currentType.Name -ne 'Unknown' -and $desiredType.Name -eq 'PSCredential') { # This is a credential object. Compare only the user name - if ($currentType.Name -eq 'PSCredential' -and $CurrentValues.$key.UserName -eq $desiredValuesClean.$key.UserName) + if ($currentType.Name -eq 'PSCredential' -and $currentValue.UserName -eq $desiredValue.UserName) { - Write-Verbose -Message ($script:localizedData.MatchPsCredentialUsernameMessage -f $CurrentValues.$key.UserName, $desiredValuesClean.$key.UserName) + Write-Verbose -Message ($script:localizedData.MatchPsCredentialUsernameMessage -f $currentValue.UserName, $desiredValue.UserName) continue } else { - Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $CurrentValues.$key.UserName, $desiredValuesClean.$key.UserName) + Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $currentValue.UserName, $desiredValue.UserName) $returnValue = $false } # Assume the string is our username when the matching desired value is actually a credential - if ($currentType.Name -eq 'string' -and $CurrentValues.$key -eq $desiredValuesClean.$key.UserName) + if ($currentType.Name -eq 'string' -and $currentValue -eq $desiredValue.UserName) { - Write-Verbose -Message ($script:localizedData.MatchPsCredentialUsernameMessage -f $CurrentValues.$key, $desiredValuesClean.$key.UserName) + Write-Verbose -Message ($script:localizedData.MatchPsCredentialUsernameMessage -f $currentValue, $desiredValue.UserName) continue } else { - Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $CurrentValues.$key, $desiredValuesClean.$key.UserName) + Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $currentValue, $desiredValue.UserName) $returnValue = $false } } @@ -333,14 +380,15 @@ function Test-DscParameterState if (($desiredType.Name -ne 'Unknown' -and $currentType.Name -ne 'Unknown') -and $desiredType.FullName -ne $currentType.FullName) { - Write-Verbose -Message ($script:localizedData.NoMatchTypeMismatchMessage -f $key, $currentType.Name, $desiredType.Name) + Write-Verbose -Message ($script:localizedData.NoMatchTypeMismatchMessage -f $key, $currentType.FullName, $desiredType.FullName) + $returnValue = $false continue } } - if ($CurrentValues.$key -eq $desiredValuesClean.$key -and -not $desiredType.IsArray) + if ($currentValue -eq $desiredValue -and -not $desiredType.IsArray) { - Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.Name, $key, $CurrentValues.$key, $desiredValuesClean.$key) + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) continue } @@ -355,41 +403,41 @@ function Test-DscParameterState if (-not $checkDesiredValue) { - Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.Name, $key, $CurrentValues.$key, $desiredValuesClean.$key) + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) continue } if ($desiredType.IsArray) { - Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key) + Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key, $desiredType.FullName) - if ($CurrentValues.$key.Count -eq 0 -and $DesiredValues.$key.Count -eq 0) + if (-not $currentValue -and -not $desiredValue) { - Write-Verbose -Message ($script:localizedData.MatchEmptyCollectionMessage -f $desiredType.Name, $key) + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, 'empty array', 'empty array') continue } - <# - This evaluation needs to be performed before the next evaluation, - because we need to be able to handle when the current value - is a zero item collection, meaning `-not $CurrentValues.$key` - would otherwise return $true in the next evaluation. - #> - elseif ($CurrentValues.$key.Count -ne $DesiredValues.$key.Count) + elseif (-not $currentValue) { - Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.Name, $key, $CurrentValues.$key.Count, $desiredValuesClean.$key.Count) + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false continue } - elseif (-not $CurrentValues.ContainsKey($key) -or -not $CurrentValues.$key) + elseif ($currentValue.Count -ne $desiredValue.Count) { - Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.Name, $key, $CurrentValues.$key, $desiredValuesClean.$key) + Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.FullName, $key, $currentValue.Count, $desiredValue.Count) $returnValue = $false continue } else { - $desiredArrayValues = $DesiredValues.$key - $currentArrayValues = $CurrentValues.$key + $desiredArrayValues = $desiredValue + $currentArrayValues = $currentValue + + if ($SortArrayValues) + { + $desiredArrayValues = $desiredArrayValues | Sort-Object + $currentArrayValues = $currentArrayValues | Sort-Object + } for ($i = 0; $i -lt $desiredArrayValues.Count; $i++) { @@ -399,7 +447,7 @@ function Test-DscParameterState } else { - $desiredType = [PSObject]@{ + $desiredType = @{ Name = 'Unknown' } } @@ -410,7 +458,7 @@ function Test-DscParameterState } else { - $currentType = [PSObject]@{ + $currentType = @{ Name = 'Unknown' } } @@ -420,7 +468,7 @@ function Test-DscParameterState if (($desiredType.Name -ne 'Unknown' -and $currentType.Name -ne 'Unknown') -and $desiredType.FullName -ne $currentType.FullName) { - Write-Verbose -Message ($script:localizedData.NoMatchElementTypeMismatchMessage -f $key, $i, $currentType.Name, $desiredType.Name) + Write-Verbose -Message ($script:localizedData.NoMatchElementTypeMismatchMessage -f $key, $i, $currentType.FullName, $desiredType.FullName) $returnValue = $false continue } @@ -428,29 +476,64 @@ function Test-DscParameterState if ($desiredArrayValues[$i] -ne $currentArrayValues[$i]) { - Write-Verbose -Message ($script:localizedData.NoMatchElementValueMismatchMessage -f $i, $desiredType.Name, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) + Write-Verbose -Message ($script:localizedData.NoMatchElementValueMismatchMessage -f $i, $desiredType.FullName, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) $returnValue = $false continue } else { - Write-Verbose -Message ($script:localizedData.MatchElementValueMessage -f $i, $desiredType.Name, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) + Write-Verbose -Message ($script:localizedData.MatchElementValueMessage -f $i, $desiredType.FullName, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) continue } } } } + elseif ($desiredType -eq [System.Collections.Hashtable] -and $currentType -eq [System.Collections.Hashtable]) + { + $param = $PSBoundParameters + $param.CurrentValues = $currentValue + $param.DesiredValues = $desiredValue + $null = $param.Remove('ValuesToCheck') + + if ($returnValue) + { + $returnValue = Test-DscParameterState @param + } + else + { + Test-DscParameterState @param | Out-Null + } + continue + } else { - if ($desiredValuesClean.$key -ne $CurrentValues.$key) + if ($desiredValue -ne $currentValue) { - Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.Name, $key, $CurrentValues.$key, $desiredValuesClean.$key) + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false } } } + if ($ReverseCheck) + { + Write-Verbose -Message $script:localizedData.StartingReverseCheck + $reverseCheckParameters = $PSBoundParameters + $reverseCheckParameters.CurrentValues = $DesiredValues + $reverseCheckParameters.DesiredValues = $CurrentValues + $null = $reverseCheckParameters.Remove('ReverseCheck') + + if ($returnValue) + { + $returnValue = Test-DscParameterState @reverseCheckParameters + } + else + { + $null = Test-DscParameterState @reverseCheckParameters + } + } + Write-Verbose -Message ($script:localizedData.TestDscParameterResultMessage -f $returnValue) return $returnValue } @@ -488,6 +571,94 @@ function Test-DscObjectHasProperty return $false } +<# + .SYNOPSIS + Converts a hashtable into a CimInstance array. + + .DESCRIPTION + This function is used to convert a hashtable into MSFT_KeyValuePair objects. These are stored as an CimInstance array. + DSC cannot handle hashtables but CimInstances arrays storing MSFT_KeyValuePair. + + .PARAMETER Hashtable + A hashtable with the values to convert. + + .OUTPUTS + An object array with CimInstance objects. +#> +function ConvertTo-CimInstance +{ + [CmdletBinding()] + [OutputType([System.Object[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Collections.Hashtable] + $Hashtable + ) + + process + { + foreach ($item in $Hashtable.GetEnumerator()) + { + New-CimInstance -ClassName MSFT_KeyValuePair -Namespace root/microsoft/Windows/DesiredStateConfiguration -Property @{ + Key = $item.Key + Value = if ($item.Value -is [array]) + { + $item.Value -join ',' + } + else + { + $item.Value + } + } -ClientOnly + } + } +} + +<# + .SYNOPSIS + Converts CimInstances into a hashtable. + + .DESCRIPTION + This function is used to convert a CimInstance array containing MSFT_KeyValuePair objects into a hashtable. + + .PARAMETER CimInstance + An array of CimInstances or a single CimInstance object to convert. + + .OUTPUTS + Hashtable +#> +function ConvertTo-HashTable +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $CimInstance + ) + + begin + { + $result = @{ } + } + + process + { + foreach ($ci in $CimInstance) + { + $result.Add($ci.Key, $ci.Value) + } + } + + end + { + $result + } +} + <# .SYNOPSIS This function tests if a cmdlet exists. diff --git a/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 b/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 index 9ebf9b09..8521aad6 100644 --- a/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 +++ b/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 @@ -1,18 +1,19 @@ ConvertFrom-StringData @' - InvalidDesiredValuesError = Property 'DesiredValues' in Test-DscParameterState must be either a Hashtable or CimInstance. Type detected was '{0}'. - InvalidValuesToCheckError = If 'DesiredValues' is a CimInstance then property 'ValuesToCheck' must contain a value. - TestDscParameterCompareMessage = Comparing values in property '{0}'. - MatchPsCredentialUsernameMessage = MATCH: PSCredential username match. Current state is '{0}' and desired state is '{1}'. - NoMatchPsCredentialUsernameMessage = NOTMATCH: PSCredential username mismatch. Current state is '{0}' and desired state is '{1}'. - NoMatchTypeMismatchMessage = NOTMATCH: Type mismatch for property '{0}' Current state type is '{1}' and desired type is '{2}'. - MatchValueMessage = MATCH: Value (type '{0}') for property '{1}' does match. Current state is '{2}' and desired state is '{3}'. - MatchEmptyCollectionMessage = MATCH: Value (type '{0}') for property '{1}' does match. Current state and desired state both have zero items in the collection. - NoMatchValueMessage = NOTMATCH: Value (type '{0}') for property '{1}' does not match. Current state is '{2}' and desired state is '{3}'. - NoMatchValueDifferentCountMessage = NOTMATCH: Value (type '{0}') for property '{1}' does have a different count. Current state count is '{2}' and desired state count is '{3}'. - NoMatchElementTypeMismatchMessage = NOTMATCH: Type mismatch for property '{0}' Current state type of element [{1}] is '{2}' and desired type is '{3}'. - NoMatchElementValueMismatchMessage = NOTMATCH: Value [{0}] (type '{1}') for property '{2}' does match. Current state is '{3}' and desired state is '{4}'. - MatchElementValueMessage = MATCH: Value [{0}] (type '{1}') for property '{2}' does match. Current state is '{3}' and desired state is '{4}'. - TestDscParameterResultMessage = Test-DscParameter result is '{0}'. + InvalidCurrentValuesError = Property 'CurrentValues' in Test-DscParameterState must be either a Hashtable, CimInstance or CimIntance[]. Type detected was '{0}'. + InvalidDesiredValuesError = Property 'DesiredValues' in Test-DscParameterState must be either a Hashtable or CimInstance. Type detected was '{0}'. + InvalidValuesToCheckError = If 'DesiredValues' is a CimInstance then property 'ValuesToCheck' must contain a value. + TestDscParameterCompareMessage = Comparing values in property '{0}' of type '{1}'. + MatchPsCredentialUsernameMessage = MATCH: PSCredential username match. Current state is '{0}' and desired state is '{1}'. + NoMatchPsCredentialUsernameMessage = NOTMATCH: PSCredential username mismatch. Current state is '{0}' and desired state is '{1}'. + NoMatchTypeMismatchMessage = NOTMATCH: Type mismatch for property '{0}' Current state type is '{1}' and desired type is '{2}'. + MatchValueMessage = MATCH: Value (type '{0}') for property '{1}' does match. Current state is '{2}' and desired state is '{3}'. + NoMatchValueMessage = NOTMATCH: Value (type '{0}') for property '{1}' does not match. Current state is '{2}' and desired state is '{3}'. + NoMatchValueDifferentCountMessage = NOTMATCH: Value (type '{0}') for property '{1}' does have a different count. Current state count is '{2}' and desired state count is '{3}'. + NoMatchElementTypeMismatchMessage = NOTMATCH: Type mismatch for property '{0}' Current state type of element [{1}] is '{2}' and desired type is '{3}'. + NoMatchElementValueMismatchMessage = NOTMATCH: Value [{0}] (type '{1}') for property '{2}' does match. Current state is '{3}' and desired state is '{4}'. + MatchElementValueMessage = MATCH: Value [{0}] (type '{1}') for property '{2}' does match. Current state is '{3}' and desired state is '{4}'. + TestDscParameterResultMessage = Test-DscParameter result is '{0}'. + StartingReverseCheck = Starting with a reverse check. CurrentTimeZoneMessage = Current time zone is set to '{0}'. GettingTimeZoneMessage = Getting current time zone using '{0}'. SettingTimeZoneMessage = Setting time zone to '{0}' using '{1}'. diff --git a/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 b/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 index 707ace10..c7fc4a2b 100644 --- a/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 @@ -123,7 +123,7 @@ Describe "$($script:dcsResourceName)_Integration" { } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + Test-DscConfiguration -Verbose | Should -BeTrue } } @@ -190,7 +190,7 @@ Describe "$($script:dcsResourceName)_Integration" { } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + Test-DscConfiguration -Verbose | Should -BeTrue } } @@ -259,7 +259,7 @@ Describe "$($script:dcsResourceName)_Integration" { } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + Test-DscConfiguration -Verbose | Should -BeTrue } } @@ -311,7 +311,7 @@ Describe "$($script:dcsResourceName)_Integration" { } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + Test-DscConfiguration -Verbose | Should -BeTrue } } @@ -357,7 +357,7 @@ Describe "$($script:dcsResourceName)_Integration" { } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + Test-DscConfiguration -Verbose | Should -BeTrue } } @@ -403,7 +403,7 @@ Describe "$($script:dcsResourceName)_Integration" { } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + Test-DscConfiguration -Verbose | Should -BeTrue } } diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 178a04cd..144b3f50 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -69,350 +69,907 @@ try } Describe 'ComputerManagementDsc.Common\Test-DscParameterState' { - Context 'All current parameters match desired parameters' { + $verbose = $true + + Context 'When testing single values' { $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When all values match' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + Context 'When a string is mismatched' { + $desiredValues = [PSObject] @{ + String = 'different string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - It 'Should return $true' { - $script:result | Should -BeTrue + Context 'When a boolean is mismatched' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $false + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - } - Context 'The current parameters do not match desired parameters because a string mismatches' { - $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When an int is mismatched' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $desiredValues = [PSObject] @{ - parameterString = 'different string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When a type is mismatched' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a', 'b', 'c' + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + Context 'When a type is mismatched but TurnOffTypeChecking is used' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a', 'b', 'c' + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When a value is mismatched but valuesToCheck is used to exclude them' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $false + Int = 1 + Array = @( 'a', 'b' ) + } + + $valuesToCheck = @( + 'String' + ) + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ValuesToCheck $valuesToCheck ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } } - Context 'The current parameters do not match desired parameters because a boolean mismatches' { - $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When testing array values' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $false - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When array is missing a value' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + Context 'When array has an additional value' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'a', 'b', 'c', 1, 2 + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should return $false' { + $script:result | Should -Be $false + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When array has a different value' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'a', 'x', 'c', 1 + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - } - Context 'The current parameters do not match desired parameters because a int mismatches' { - $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When array has different order' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'c', 'b', 'a', 1 + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When array has different order but SortArrayValues is used' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'c', 'b', 'a', 1 + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -SortArrayValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + Context 'When array has a value with a different type' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', '1' + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When array has a value with a different type but TurnOffTypeChecking is used' { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', '1' + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } + } + + Context 'When both arrays are empty' { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @() + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = @() + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } } - Context 'The current parameters do not match desired parameters because an array is missing a value' { + Context 'When testing hashtables' { $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'b' ) + Context 'When hashtable is missing a value' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + Context 'When hashtable has an additional value' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99, 100 + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should return $false' { + $script:result | Should -Be $false + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When hashtable has a different value' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'xx', 'v2', 'v3', 99 + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - } - Context 'The current parameters do not match desired parameters because an array has an additional value' { - $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When an array in hashtable has different order' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v3', 'v2', 'v1', 99 + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'b', 'c', 'd' ) + Context 'When an array in hashtable has different order but SortArrayValues is used' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v3', 'v2', 'v1', 99 + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -SortArrayValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + Context 'When hashtable has a value with a different type' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', '99' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When hashtable has a value with a different type but TurnOffTypeChecking is used' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } } - Context 'The current parameters do not match desired parameters because an array has a different value' { + Context 'When testing CimInstances / hashtables' { $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) - } + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = [CimInstance[]](ConvertTo-CimInstance -Hashtable @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a, b, c' + }) + } + + Context 'When everything matches' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = [CimInstance[]](ConvertTo-CimInstance -Hashtable @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a, b, c' + }) + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'd', 'c' ) + It 'Should return $true' { + $script:result | Should -Be $true + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + Context 'When CimInstances missing a value in the desired state (not recognized)' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Array = 'a, b, c' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should return $true' { + $script:result | Should -Be $true + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When CimInstances missing a value in the desired state (recognized using ReverseCheck)' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Array = 'a, b, c' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ReverseCheck ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - } - Context 'The current parameters do not match desired parameters because an array has a different type' { - $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context 'When CimInstances have an additional value' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a, b, c' + Test = 'Some string' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 1, 'c' ) + Context 'When CimInstances have a different value' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'some other string' + Bool = $true + Int = 99 + Array = 'a, b, c' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + Context 'When CimInstances have a value with a different type' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a, b, c' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should return $false' { + $script:result | Should -Be $false + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When CimInstances have a value with a different type but TurnOffTypeChecking is used' { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a, b, c' + } + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true' { + $script:result | Should -Be $true + } } } - Context 'The current parameters do not match desired parameters because a parameter has a different type' { + Context 'When reverse checking' { $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } } - $desiredValues = [PSObject] @{ - parameterString = $false - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'b', 'c' ) - } + Context 'When even if missing property in the desired state' { + $desiredValues = [PSObject] @{ + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } - $valuesToCheck = @( - 'parameterString' - 'parameterBool' - 'ParameterInt' - 'ParameterArray' - ) + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should return $true' { + $script:result | Should -Be $true + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context 'When missing property in the desired state' { + $currentValues = @{ + String = 'a string' + Bool = $true + } + + $desiredValues = [PSObject] @{ + String = 'a string' + } + + It 'Should not throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ReverseCheck ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false' { + $script:result | Should -Be $false + } } } - Context 'Some of the current parameters do not match desired parameters but only matching parameter is compared' { - $currentValues = @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) - } + Context 'When testing parameter types' { + Context 'When desired value is of the wrong type' { + $currentValues = @{ + String = 'a string' + } + + $desiredValues = 1, 2, 3 - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $false - parameterInt = 1 - parameterArray = @( 'a', 'b' ) + It 'Should throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } } - $valuesToCheck = @( - 'parameterString' - ) + Context 'When current value is of the wrong type' { + $currentValues = 1, 2, 3 - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw - } + $desiredValues = @{ + String = 'a string' + } - It 'Should return $true' { - $script:result | Should -BeTrue + It 'Should throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } } } } @@ -421,23 +978,91 @@ try # Use the Get-Verb cmdlet to just get a simple object fast $testDscObject = (Get-Verb)[0] - Context 'The object contains the expected property' { + Context 'When the object contains the expected property' { It 'Should not throw exception' { { $script:result = Test-DscObjectHasProperty -Object $testDscObject -PropertyName 'Verb' -Verbose } | Should -Not -Throw } It 'Should return $true' { - $script:result | Should -BeTrue + $script:result | Should -Be $true } } - Context 'The object does not contain the expected property' { + Context 'When the object does not contain the expected property' { It 'Should not throw exception' { { $script:result = Test-DscObjectHasProperty -Object $testDscObject -PropertyName 'Missing' -Verbose } | Should -Not -Throw } It 'Should return $false' { - $script:result | Should -BeFalse + $script:result | Should -Be $false + } + } + } + + Describe 'ComputerManagementDsc.Common\ConvertTo-CimInstance' { + $hashtable = @{ + k1 = 'v1' + k2 = 100 + k3 = 1, 2, 3 + } + + Context 'When the array contains the expected record count' { + It 'Should not throw exception' { + { $script:result = [CimInstance[]]($hashtable | ConvertTo-CimInstance) } | Should -Not -Throw + } + + It "Should record count should be $($hashTable.Count)" { + $script:result.Count | Should -Be $hashtable.Count + } + + It 'Should return result of type CimInstance[]' { + $script:result.GetType().Name | Should -Be 'CimInstance[]' + } + + It 'Should return value "k1" in the CimInstance array should be "v1"' { + ($script:result | Where-Object Key -eq k1).Value | Should -Be 'v1' + } + + It 'Should return value "k2" in the CimInstance array should be "100"' { + ($script:result | Where-Object Key -eq k2).Value | Should -Be 100 + } + + It 'Should return value "k3" in the CimInstance array should be "1,2,3"' { + ($script:result | Where-Object Key -eq k3).Value | Should -Be '1,2,3' + } + } + } + + Describe 'ComputerManagementDsc.Common\ConvertTo-HashTable' { + [CimInstance[]]$cimInstances = ConvertTo-CimInstance -Hashtable @{ + k1 = 'v1' + k2 = 100 + k3 = 1, 2, 3 + } + + Context 'When the array contains the expected record count' { + It 'Should not throw exception' { + { $script:result = $cimInstances | ConvertTo-HashTable } | Should -Not -Throw + } + + It "Should return record count of $($cimInstances.Count)" { + $script:result.Count | Should -Be $cimInstances.Count + } + + It 'Should return result of type [System.Collections.Hashtable]' { + $script:result | Should -BeOfType [System.Collections.Hashtable] + } + + It 'Should return value "k1" in the hashtable should be "v1"' { + $script:result.k1 | Should -Be 'v1' + } + + It 'Should return value "k2" in the hashtable should be "100"' { + $script:result.k2 | Should -Be 100 + } + + It 'Should return value "k3" in the hashtable should be "1,2,3"' { + $script:result.k3 | Should -Be '1,2,3' } } } diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index fa625afe..da422210 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -94,12 +94,15 @@ try Mock -CommandName Register-ScheduledTask Mock -CommandName Set-ScheduledTask Mock -CommandName Unregister-ScheduledTask - } - Context 'No scheduled task exists, but it should' { - $testParameters = @{ + $getTargetResourceParameters = @{ TaskName = 'Test task' TaskPath = '\Test\' + } + } + + Context 'No scheduled task exists, but it should' { + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -110,7 +113,7 @@ try Mock -CommandName Get-ScheduledTask -MockWith { return $null } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Absent' } @@ -124,9 +127,7 @@ try } Context 'A scheduled task exists, but it should not' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -157,7 +158,7 @@ try } } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -172,9 +173,7 @@ try } Context 'A built-in scheduled task exists and is enabled, but it should be disabled' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ Enable = $false Verbose = $true } @@ -201,7 +200,7 @@ try } } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -BeTrue $result.Ensure | Should -Be 'Present' } @@ -217,9 +216,7 @@ try } Context 'A built-in scheduled task exists, but it should be absent' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ Ensure = 'Absent' Verbose = $true } @@ -251,7 +248,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -BeTrue $result.Ensure | Should -Be 'Present' } @@ -267,9 +264,7 @@ try } Context 'A scheduled task doesnt exist, and it should not' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' Ensure = 'Absent' @@ -279,7 +274,7 @@ try Mock -CommandName Get-ScheduledTask It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Absent' } @@ -289,9 +284,7 @@ try } Context 'A scheduled task with Once based repetition exists, but has the wrong settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -326,7 +319,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -341,9 +334,7 @@ try } Context 'A scheduled task with minutes based repetition exists and has the correct settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -378,7 +369,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -388,9 +379,7 @@ try } Context 'A scheduled task with hourly based repetition exists, but has the wrong settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Hours 4).ToString() @@ -425,7 +414,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -440,9 +429,7 @@ try } Context 'A scheduled task with hourly based repetition exists and has the correct settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Hours 4).ToString() @@ -477,7 +464,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -487,9 +474,7 @@ try } Context 'A scheduled task with daily based repetition exists, but has the wrong settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 3 @@ -523,7 +508,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -538,9 +523,7 @@ try } Context 'A scheduled task with daily based repetition exists and has the correct settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 3 @@ -571,7 +554,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -581,9 +564,7 @@ try } Context 'A scheduled task exists and is configured with the wrong execution account' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -619,7 +600,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -634,9 +615,7 @@ try } Context 'A scheduled task exists and is configured with the wrong logon type' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -674,7 +653,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' $result.LogonType | Should -Be 'Password' } @@ -690,9 +669,7 @@ try } Context 'A scheduled task exists and is configured with the wrong run level' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -730,7 +707,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' $result.RunLevel | Should -Be 'Limited' } @@ -746,9 +723,7 @@ try } Context 'A scheduled task exists and is configured with the wrong working directory' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ActionWorkingPath = 'C:\Example' ScheduleType = 'Once' @@ -785,7 +760,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -800,9 +775,7 @@ try } Context 'A scheduled task exists and is configured with the wrong executable arguments' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ActionArguments = '-File "C:\something\right.ps1"' ScheduleType = 'Once' @@ -839,7 +812,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -854,9 +827,7 @@ try } Context 'A scheduled task is enabled and should be disabled' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -896,7 +867,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -912,9 +883,7 @@ try } Context 'A scheduled task is enabled without an execution time limit and but has an execution time limit set' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -956,7 +925,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -971,9 +940,7 @@ try } Context 'A scheduled task is enabled and has the correct settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1025,7 +992,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1035,9 +1002,7 @@ try } Context 'A scheduled task is disabled and has the correct settings' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1077,7 +1042,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1087,9 +1052,7 @@ try } Context 'A scheduled task is disabled but should be enabled' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1129,7 +1092,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1144,9 +1107,7 @@ try } Context 'A Scheduled task exists, is disabled, and the optional parameter enable is not specified' -Fixture { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1185,7 +1146,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1217,9 +1178,7 @@ try } Context 'A scheduled task exists and is configured with the wrong interval, duration & random delay parameters' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 20).ToString() @@ -1269,7 +1228,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1283,9 +1242,7 @@ try } Context 'A scheduled task exists and is configured with the wrong idle timeout & idle duration parameters' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 20).ToString() @@ -1335,7 +1292,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1350,9 +1307,7 @@ try } Context 'A scheduled task exists and is configured with the wrong duration parameter for an indefinite trigger' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 20).ToString() @@ -1388,7 +1343,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1403,9 +1358,7 @@ try } Context 'A scheduled task exists and is configured with indefinite repetition duration for a trigger but should be fixed' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 20).ToString() @@ -1441,7 +1394,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1456,9 +1409,7 @@ try } Context 'A scheduled task exists and is configured with correctly with an indefinite duration trigger' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 20).ToString() @@ -1494,7 +1445,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Present' } @@ -1504,9 +1455,7 @@ try } Context 'When a built-in scheduled task exists and is enabled, but it should be disabled and the trigger type is not recognized' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ Enable = $false Verbose = $true } @@ -1534,7 +1483,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -BeTrue $result.Ensure | Should -Be 'Present' $result.ScheduleType | Should -BeNullOrEmpty @@ -1551,9 +1500,7 @@ try } Context 'When a scheduled task with an OnEvent scheduletype is in desired state' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ScheduleType = 'OnEvent' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' EventSubscription = '' @@ -1583,7 +1530,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -BeTrue $result.Ensure | Should -Be 'Present' $result.ScheduleType | Should -Be 'OnEvent' @@ -1597,9 +1544,7 @@ try } Context 'When a scheduled task with an OnEvent scheduletype needs to be created' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ScheduleType = 'OnEvent' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' EventSubscription = '' @@ -1611,7 +1556,7 @@ try Mock -CommandName Get-ScheduledTask It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Ensure | Should -Be 'Absent' } @@ -1626,9 +1571,7 @@ try } Context 'When a scheduled task with an OnEvent scheduletype needs to be updated' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ScheduleType = 'OnEvent' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' EventSubscription = '' @@ -1658,7 +1601,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -BeTrue $result.Ensure | Should -Be 'Present' $result.ScheduleType | Should -Be 'OnEvent' @@ -1680,9 +1623,7 @@ try } Context 'When a scheduled task with an OnEvent scheduletype is used on combination with unsupported parameters for this scheduletype' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ScheduleType = 'OnEvent' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' EventSubscription = '' @@ -1713,7 +1654,7 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -BeTrue $result.Ensure | Should -Be 'Present' $result.ScheduleType | Should -Be 'OnEvent' @@ -1732,9 +1673,7 @@ try } Context 'When a scheduled task is created using a Built In Service Account' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1795,9 +1734,7 @@ try } Context 'When a scheduled task is created using a Group Managed Service Account' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1870,9 +1807,7 @@ try } Context 'When a scheduled task Group Managed Service Account is changed' { - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() @@ -1929,9 +1864,7 @@ 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\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' StartTime = Get-Date -Date $startTimeString SynchronizeAcrossTimeZone = $false @@ -1959,9 +1892,9 @@ try } } - It 'Should return the time in string format and SynchronizeAcrossTimeZone with value false' { - $result = Get-TargetResource @testParameters - $result.StartTime | Should -Be $startTimeString + It 'Should return the start time in DateTime format and SynchronizeAcrossTimeZone with value false' { + $result = Get-TargetResource @getTargetResourceParameters + $result.StartTime | Should -Be (Get-Date -Date $startTimeString) $result.SynchronizeAcrossTimeZone | Should -BeFalse } @@ -2005,9 +1938,7 @@ try 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\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' StartTime = Get-Date -Date $startTimeString SynchronizeAcrossTimeZone = $true @@ -2035,9 +1966,9 @@ try } } - It 'Should return the time in string format and SynchronizeAcrossTimeZone with value true' { - $result = Get-TargetResource @testParameters - $result.StartTime | Should -Be $startTimeStringWithOffset + It 'Should return the start time in DateTime format and SynchronizeAcrossTimeZone with value true' { + $result = Get-TargetResource @getTargetResourceParameters + $result.StartTime | Should -Be (Get-Date -Date $startTimeStringWithOffset) $result.SynchronizeAcrossTimeZone | Should -BeTrue } @@ -2080,9 +2011,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' - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' StartTime = Get-Date -Date $startTimeString SynchronizeAcrossTimeZone = $true @@ -2097,9 +2026,7 @@ try Context 'When a scheduled task is configured with the ScheduleType AtLogon and is in desired state' { $startTimeString = '2018-10-01T01:00:00' - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' StartTime = Get-Date -Date $startTimeString ScheduleType = 'AtLogon' @@ -2133,10 +2060,10 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be $startTimeString + $result.StartTime | Should -Be (Get-Date -Date $startTimeString) $result.ScheduleType | Should -Be 'AtLogon' $result.Delay | Should -Be $testParameters.Delay } @@ -2147,12 +2074,8 @@ try } Context 'When a scheduled task is configured with the ScheduleType AtStartup and is in desired state' { - $startTimeString = '2018-10-01T01:00:00' - $testParameters = @{ - TaskName = 'Test task' - TaskPath = '\Test\' + $testParameters = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString ScheduleType = 'AtStartup' Delay = '00:01:00' Enable = $true @@ -2171,7 +2094,7 @@ try Triggers = @( [pscustomobject] @{ Delay = 'PT1M' - StartBoundary = $startTimeString + StartBoundary = '' CimClass = @{ CimClassName = 'MSFT_TaskBootTrigger' } @@ -2184,10 +2107,9 @@ try } It 'Should return the correct values from Get-TargetResource' { - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be $startTimeString $result.ScheduleType | Should -Be 'AtStartup' $result.Delay | Should -Be $testParameters.Delay } @@ -2197,6 +2119,20 @@ try } } } + + Describe 'MSFT_ScheduledTask\Test-DateStringContainsTimeZone' { + Context 'When the date string contains a date without a timezone' { + It 'Should return $false' { + Test-DateStringContainsTimeZone -DateString '2018-10-01T01:00:00' | Should -BeFalse + } + } + + Context 'When the date string contains a date with a timezone' { + It 'Should return $true' { + Test-DateStringContainsTimeZone -DateString ('2018-10-01T01:00:00' + (Get-Date -Format 'zzz')) | Should -BeTrue + } + } + } } #endregion }