From b5cebe87eba4104c9071f82e3a80c287008648af Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 16:44:46 +1200 Subject: [PATCH 01/15] Refactoring ScheduledTask --- CHANGELOG.md | 11 + .../MSFT_ScheduledTask.psm1 | 796 ++++-------- .../ComputerManagementDsc.Common.psm1 | 254 +++- .../ComputerManagementDsc.Common.strings.psd1 | 29 +- .../ComputerManagementDsc.Common.Tests.ps1 | 1095 +++++++++++++---- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 247 ++-- 6 files changed, 1423 insertions(+), 1009 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d3bf8a..983847c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,17 @@ - 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). +- ScheduledTask: + - Correct output type of `DaysInterval` parameter from `Get-TargetResource` to + match MOF. + - Correct output type of `StartTime` parameter from `Get-TargetResource` to + match MOF. + - BREAKING CHANGE: 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. ## 6.5.0.0 diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 521fee81..5cc16681 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -26,548 +26,30 @@ $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ScheduledTask' <# .SYNOPSIS - Tests if the current resource state matches the desired resource state. - - .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 - - if ($startAt) - { - $stringSynchronizeAcrossTimeZone = Get-DateTimeString -Date $startAt -SynchronizeAcrossTimeZone $true - $returnSynchronizeAcrossTimeZone = $startAt -eq $stringSynchronizeAcrossTimeZone - } - else - { - $startAt = $StartTime - $returnSynchronizeAcrossTimeZone = $false - } - - $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 - } + Gets the current state of the resource. - if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) - { - $result.Add('BuiltInAccount', $task.Principal.UserId) - } + .PARAMETER TaskName + The name of the task. - return $result - } + .PARAMETER TaskPath + The path to the task - defaults to the root directory. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $TaskName, + + [Parameter()] + [System.String] + $TaskPath = '\' + ) + + return Get-CurrentResource @PSBoundParameters } <# @@ -954,7 +436,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') { @@ -1795,6 +1277,26 @@ function Test-TargetResource Write-Verbose -Message ($script:localizedData.TestScheduledTaskMessage -f $TaskName, $TaskPath) + $currentValues = Get-CurrentResource -TaskName $TaskName -TaskPath $TaskPath + + <# + 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 + } + # Convert the strings containing time spans to TimeSpan Objects if ($PSBoundParameters.ContainsKey('RepeatInterval')) { @@ -1853,10 +1355,6 @@ function Test-TargetResource $PSBoundParameters['StartTime'] = Get-DateTimeString -Date $StartTime -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone } - $currentValues = Get-TargetResource @PSBoundParameters - - Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) - if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') { return $true @@ -2133,3 +1631,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.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) + + $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 += [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 = $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 + } + + if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) + { + $result.Add('BuiltInAccount', $task.Principal.UserId) + } + } + + Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) + + 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/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 6454a886..647aadea 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 } } @@ -334,13 +381,14 @@ function Test-DscParameterState $desiredType.FullName -ne $currentType.FullName) { Write-Verbose -Message ($script:localizedData.NoMatchTypeMismatchMessage -f $key, $currentType.Name, $desiredType.Name) + $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.Name, $key, $currentValue, $desiredValue) continue } @@ -355,7 +403,7 @@ 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.Name, $key, $currentValue, $desiredValue) continue } @@ -363,33 +411,28 @@ function Test-DscParameterState { Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key) - if ($CurrentValues.$key.Count -eq 0 -and $DesiredValues.$key.Count -eq 0) - { - Write-Verbose -Message ($script:localizedData.MatchEmptyCollectionMessage -f $desiredType.Name, $key) - 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) + if (-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.Name, $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.Name, $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 +442,7 @@ function Test-DscParameterState } else { - $desiredType = [PSObject]@{ + $desiredType = @{ Name = 'Unknown' } } @@ -410,7 +453,7 @@ function Test-DscParameterState } else { - $currentType = [PSObject]@{ + $currentType = @{ Name = 'Unknown' } } @@ -441,16 +484,51 @@ function Test-DscParameterState } } + 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.Name, $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 +566,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..7d7cc670 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}'. + 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/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 178a04cd..365a5426 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -69,350 +69,869 @@ 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 '== All 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 '!= string mismatch' { + $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 '!= boolean mismatch' { + $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 '!= int mismatch' { + $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 '!= Type mismatch' { + $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 '!= Type mismatch 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 '== mismatches 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' { + Context 'When testing array values' { $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 = 'a string' - parameterBool = $false - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context '!= Array 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 '!= 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 ` - -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 $false' { - $script:result | Should -BeFalse + Context '!= 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 '!= 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 '== 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 '!= 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 '== 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 '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 '!= Hashtable 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 '!= 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 '!= 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 '!= 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 '== 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 '!= 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 '== 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 '== 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' + }) + } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'd', 'c' ) + 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 '== 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 ` - -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 $true' { + $script:result | Should -Be $true + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context '!= 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 '!= 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 '!= 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 '!= CimInstaces 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 '== CimInstaces 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 '== 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 '!= 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' { - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $false - parameterInt = 1 - parameterArray = @( 'a', 'b' ) - } + Context 'When desired value is of the wrong type' { + $currentValues = @{ + String = 'a string' + } - $valuesToCheck = @( - 'parameterString' - ) + $desiredValues = 1, 2, 3 - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } } - It 'Should return $true' { - $script:result | Should -BeTrue + Context 'When current value is of the wrong type' { + $currentValues = 1, 2, 3 + + $desiredValues = @{ + String = 'a string' + } + + It 'Should throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } } } } @@ -421,23 +940,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 362392b2..e835a01f 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 @@ -2095,6 +2024,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 } From 6b477833763f8f73a53620f7f505c939c17a9b55 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 17:01:03 +1200 Subject: [PATCH 02/15] Correct CHANGELOG --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 983847c2..958e5483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,13 +23,13 @@ [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). -- ScheduledTask: +- BREAKING CHANGE: ScheduledTask: - Correct output type of `DaysInterval` parameter from `Get-TargetResource` to match MOF. - Correct output type of `StartTime` parameter from `Get-TargetResource` to match MOF. - - BREAKING CHANGE: Refactored `Get-TargetResource` to remove parameters that are not key or - required - fixes [Issue #249](https://github.com/PowerShell/ComputerManagementDsc/issues/249). + - 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. From cc413cec9e9c4e3cb8425cae47092f51a4c9afb5 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 16:44:46 +1200 Subject: [PATCH 03/15] Refactoring ScheduledTask --- CHANGELOG.md | 11 + .../MSFT_ScheduledTask.psm1 | 796 ++++-------- .../ComputerManagementDsc.Common.psm1 | 254 +++- .../ComputerManagementDsc.Common.strings.psd1 | 29 +- .../ComputerManagementDsc.Common.Tests.ps1 | 1095 +++++++++++++---- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 247 ++-- 6 files changed, 1423 insertions(+), 1009 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 147f52b9..047145ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,17 @@ - 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). +- ScheduledTask: + - Correct output type of `DaysInterval` parameter from `Get-TargetResource` to + match MOF. + - Correct output type of `StartTime` parameter from `Get-TargetResource` to + match MOF. + - BREAKING CHANGE: 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. ## 6.5.0.0 diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 7f644106..317bc15f 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -26,548 +26,30 @@ $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ScheduledTask' <# .SYNOPSIS - Tests if the current resource state matches the desired resource state. - - .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 - - if ($startAt) - { - $stringSynchronizeAcrossTimeZone = Get-DateTimeString -Date $startAt -SynchronizeAcrossTimeZone $true - $returnSynchronizeAcrossTimeZone = $startAt -eq $stringSynchronizeAcrossTimeZone - } - else - { - $startAt = $StartTime - $returnSynchronizeAcrossTimeZone = $false - } - - $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 - } + Gets the current state of the resource. - if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) - { - $result.Add('BuiltInAccount', $task.Principal.UserId) - } + .PARAMETER TaskName + The name of the task. - return $result - } + .PARAMETER TaskPath + The path to the task - defaults to the root directory. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $TaskName, + + [Parameter()] + [System.String] + $TaskPath = '\' + ) + + return Get-CurrentResource @PSBoundParameters } <# @@ -954,7 +436,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 +1287,26 @@ function Test-TargetResource Write-Verbose -Message ($script:localizedData.TestScheduledTaskMessage -f $TaskName, $TaskPath) + $currentValues = Get-CurrentResource -TaskName $TaskName -TaskPath $TaskPath + + <# + 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 + } + # Convert the strings containing time spans to TimeSpan Objects if ($PSBoundParameters.ContainsKey('RepeatInterval')) { @@ -1863,10 +1365,6 @@ function Test-TargetResource $PSBoundParameters['StartTime'] = Get-DateTimeString -Date $StartTime -SynchronizeAcrossTimeZone $SynchronizeAcrossTimeZone } - $currentValues = Get-TargetResource @PSBoundParameters - - Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) - if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') { return $true @@ -2143,3 +1641,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.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) + + $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 += [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 = $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 + } + + if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) + { + $result.Add('BuiltInAccount', $task.Principal.UserId) + } + } + + Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) + + 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/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 6454a886..647aadea 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 } } @@ -334,13 +381,14 @@ function Test-DscParameterState $desiredType.FullName -ne $currentType.FullName) { Write-Verbose -Message ($script:localizedData.NoMatchTypeMismatchMessage -f $key, $currentType.Name, $desiredType.Name) + $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.Name, $key, $currentValue, $desiredValue) continue } @@ -355,7 +403,7 @@ 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.Name, $key, $currentValue, $desiredValue) continue } @@ -363,33 +411,28 @@ function Test-DscParameterState { Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key) - if ($CurrentValues.$key.Count -eq 0 -and $DesiredValues.$key.Count -eq 0) - { - Write-Verbose -Message ($script:localizedData.MatchEmptyCollectionMessage -f $desiredType.Name, $key) - 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) + if (-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.Name, $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.Name, $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 +442,7 @@ function Test-DscParameterState } else { - $desiredType = [PSObject]@{ + $desiredType = @{ Name = 'Unknown' } } @@ -410,7 +453,7 @@ function Test-DscParameterState } else { - $currentType = [PSObject]@{ + $currentType = @{ Name = 'Unknown' } } @@ -441,16 +484,51 @@ function Test-DscParameterState } } + 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.Name, $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 +566,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..7d7cc670 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}'. + 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/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 178a04cd..365a5426 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -69,350 +69,869 @@ 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 '== All 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 '!= string mismatch' { + $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 '!= boolean mismatch' { + $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 '!= int mismatch' { + $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 '!= Type mismatch' { + $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 '!= Type mismatch 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 '== mismatches 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' { + Context 'When testing array values' { $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 = 'a string' - parameterBool = $false - parameterInt = 99 - parameterArray = @( 'a', 'b', 'c' ) + Context '!= Array 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 '!= 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 ` - -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 $false' { - $script:result | Should -BeFalse + Context '!= 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 '!= 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 '== 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 '!= 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 '== 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 '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 '!= Hashtable 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 '!= 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 '!= 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 '!= 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 '== 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 '!= 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 '== 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 '== 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' + }) + } - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $true - parameterInt = 1 - parameterArray = @( 'a', 'd', 'c' ) + 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 '== 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 ` - -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 $true' { + $script:result | Should -Be $true + } } - It 'Should return $false' { - $script:result | Should -BeFalse + Context '!= 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 '!= 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 '!= 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 '!= CimInstaces 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 '== CimInstaces 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 '== 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 '!= 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' { - $desiredValues = [PSObject] @{ - parameterString = 'a string' - parameterBool = $false - parameterInt = 1 - parameterArray = @( 'a', 'b' ) - } + Context 'When desired value is of the wrong type' { + $currentValues = @{ + String = 'a string' + } - $valuesToCheck = @( - 'parameterString' - ) + $desiredValues = 1, 2, 3 - It 'Should not throw exception' { - { $script:result = Test-DscParameterState ` - -CurrentValues $currentValues ` - -DesiredValues $desiredValues ` - -ValuesToCheck $valuesToCheck ` - -Verbose } | Should -Not -Throw + It 'Should throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } } - It 'Should return $true' { - $script:result | Should -BeTrue + Context 'When current value is of the wrong type' { + $currentValues = 1, 2, 3 + + $desiredValues = @{ + String = 'a string' + } + + It 'Should throw exception' { + { $script:result = Test-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } } } } @@ -421,23 +940,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..9305933e 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 @@ -2197,6 +2126,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 } From f17537d1aafffb7ed1964171df409db88d23598c Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 17:01:03 +1200 Subject: [PATCH 04/15] Correct CHANGELOG --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047145ad..0fbb0088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,13 +26,13 @@ [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). -- ScheduledTask: +- BREAKING CHANGE: ScheduledTask: - Correct output type of `DaysInterval` parameter from `Get-TargetResource` to match MOF. - Correct output type of `StartTime` parameter from `Get-TargetResource` to match MOF. - - BREAKING CHANGE: Refactored `Get-TargetResource` to remove parameters that are not key or - required - fixes [Issue #249](https://github.com/PowerShell/ComputerManagementDsc/issues/249). + - 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. From 5d3ae2e832736525c03cb0c9d36391e2dad98ad0 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 17:37:03 +1200 Subject: [PATCH 05/15] Fix SMB Tests --- CHANGELOG.md | 4 ++-- .../MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 8 ++++++-- .../en-US/MSFT_ScheduledTask.strings.psd1 | 3 ++- .../Integration/MSFT_SmbShare.Integration.Tests.ps1 | 12 ++++++------ Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 2 +- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbb0088..ef9a764e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,8 +27,8 @@ - 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` parameter from `Get-TargetResource` to - match MOF. + - Correct output type of `DaysInterval` parameter from `Get-TargetResource` + to match MOF. - Correct output type of `StartTime` parameter from `Get-TargetResource` to match MOF. - Refactored `Get-TargetResource` to remove parameters that diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 317bc15f..42c9bea4 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -49,6 +49,10 @@ function Get-TargetResource $TaskPath = '\' ) + $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath + + Write-Verbose -Message ($script:localizedData.GetScheduledTaskMessage -f $TaskName, $TaskPath) + return Get-CurrentResource @PSBoundParameters } @@ -1669,7 +1673,7 @@ function Get-CurrentResource $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath - Write-Verbose -Message ($script:localizedData.GetScheduledTaskMessage -f $TaskName, $TaskPath) + Write-Verbose -Message ($script:localizedData.GettingCurrentTaskValuesMessage -f $TaskName, $TaskPath) $task = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue @@ -1818,7 +1822,7 @@ function Get-CurrentResource } } - Write-Verbose -Message ($script:localizedData.GetCurrentTaskValuesMessage) + Write-Verbose -Message ($script:localizedData.CurrentTaskValuesRetrievedMessage -f $TaskName, $TaskPath) return $result } 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/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/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index 9305933e..f86d77e5 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -2136,7 +2136,7 @@ try 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 + Test-DateStringContainsTimeZone -DateString ('2018-10-01T01:00:00' + (Get-Date -Format 'zzz')) | Should -BeTrue } } } From ef0c22577c99d33312f73b3131e06a0b77ef3f96 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 17:44:10 +1200 Subject: [PATCH 06/15] Include Full Type name in Test-DscParameterState --- .../ComputerManagementDsc.Common.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 647aadea..c0f294b3 100644 --- a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -380,7 +380,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.NoMatchTypeMismatchMessage -f $key, $currentType.Name, $desiredType.Name) + Write-Verbose -Message ($script:localizedData.NoMatchTypeMismatchMessage -f $key, $currentType.FullName, $desiredType.FullName) $returnValue = $false continue } From 8bf272dc3c0d45140648dafc1aaaeba8d790c88c Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 17:56:22 +1200 Subject: [PATCH 07/15] Add remaining Full Type names --- CHANGELOG.md | 1 + .../MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 5 ++++- .../ComputerManagementDsc.Common.psm1 | 16 ++++++++-------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9a764e..1d410c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ 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`. ## 6.5.0.0 diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 42c9bea4..a9d34580 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1449,7 +1449,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 } <# diff --git a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index c0f294b3..6adb8b2c 100644 --- a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -388,7 +388,7 @@ function Test-DscParameterState if ($currentValue -eq $desiredValue -and -not $desiredType.IsArray) { - Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.Name, $key, $currentValue, $desiredValue) + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) continue } @@ -403,7 +403,7 @@ function Test-DscParameterState if (-not $checkDesiredValue) { - Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.Name, $key, $currentValue, $desiredValue) + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) continue } @@ -413,13 +413,13 @@ function Test-DscParameterState if (-not $currentValue) { - Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.Name, $key, $currentValue, $desiredValue) + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false continue } elseif ($currentValue.Count -ne $desiredValue.Count) { - Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.Name, $key, $currentValue.Count, $desiredValue.Count) + Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.FullName, $key, $currentValue.Count, $desiredValue.Count) $returnValue = $false continue } @@ -463,7 +463,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 } @@ -471,13 +471,13 @@ 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 } } @@ -505,7 +505,7 @@ function Test-DscParameterState { if ($desiredValue -ne $currentValue) { - Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.Name, $key, $currentValue, $desiredValue) + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false } } From d0d6438fd886d5a416083ad95afb244d7933aac1 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 18:03:04 +1200 Subject: [PATCH 08/15] Fix unit tests for ScheduledTasks --- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index f86d77e5..d0ae0c79 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -2026,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' @@ -2062,7 +2060,7 @@ 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 @@ -2077,9 +2075,7 @@ 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' @@ -2113,7 +2109,7 @@ 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 From 0f007f6d01b6e9d6284a42121e6230c84b3bd33c Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 18:34:14 +1200 Subject: [PATCH 09/15] Fix ScheduledTask tests --- .../ComputerManagementDsc.Common.psm1 | 2 +- .../en-US/ComputerManagementDsc.Common.strings.psd1 | 2 +- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 6adb8b2c..f986bc82 100644 --- a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -409,7 +409,7 @@ function Test-DscParameterState if ($desiredType.IsArray) { - Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key) + Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key, $desiredType.FullName) if (-not $currentValue) { diff --git a/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 b/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 index 7d7cc670..8521aad6 100644 --- a/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 +++ b/Modules/ComputerManagementDsc.Common/en-US/ComputerManagementDsc.Common.strings.psd1 @@ -2,7 +2,7 @@ ConvertFrom-StringData @' 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}'. + 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}'. diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index d0ae0c79..cdb91610 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -2063,7 +2063,7 @@ try $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 } @@ -2112,7 +2112,7 @@ try $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 'AtStartup' $result.Delay | Should -Be $testParameters.Delay } From 90693b6fe5995521171192a7ff809a8b8476e9cf Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 19:03:55 +1200 Subject: [PATCH 10/15] Fixed bug in Test-DscParameterState --- .../ComputerManagementDsc.Common.psm1 | 7 +- .../ComputerManagementDsc.Common.Tests.ps1 | 103 ++++++++++++------ 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index f986bc82..ebad769f 100644 --- a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -411,7 +411,12 @@ function Test-DscParameterState { Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key, $desiredType.FullName) - if (-not $currentValue) + if (-not $currentValue -and -not $desiredValue) + { + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, 'empty array', 'empty array') + continue + } + elseif (-not $currentValue) { Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 365a5426..4d944629 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -84,7 +84,7 @@ try } } - Context '== All match' { + Context 'When all values match' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -109,7 +109,7 @@ try } } - Context '!= string mismatch' { + Context 'When a string is mismatched' { $desiredValues = [PSObject] @{ String = 'different string' Bool = $true @@ -134,7 +134,7 @@ try } } - Context '!= boolean mismatch' { + Context 'When a boolean is mismatched' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $false @@ -159,7 +159,7 @@ try } } - Context '!= int mismatch' { + Context 'When an int is mismatched' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -184,7 +184,7 @@ try } } - Context '!= Type mismatch' { + Context 'When a type is mismatched' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -204,7 +204,7 @@ try } } - Context '!= Type mismatch but TurnOffTypeChecking is used' { + Context 'When a type is mismatched but TurnOffTypeChecking is used' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -225,7 +225,7 @@ try } } - Context '== mismatches but valuesToCheck is used to exclude them' { + Context 'When a value is mismatched but valuesToCheck is used to exclude them' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $false @@ -252,19 +252,21 @@ try } Context 'When testing array values' { - $currentValues = @{ - String = 'a string' - Bool = $true - Int = 99 - Array = 'a', 'b', 'c', 1 - Hashtable = @{ - k1 = 'Test' - k2 = 123 - k3 = 'v1', 'v2', 'v3' + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } } } - Context '!= Array missing a value' { + Context 'When array is missing a value' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -289,7 +291,7 @@ try } } - Context '!= Array has an additional value' { + Context 'When array has an additional value' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -309,7 +311,7 @@ try } } - Context '!= Array has a different value' { + Context 'When array has a different value' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -329,7 +331,7 @@ try } } - Context '!= Array has different order' { + Context 'When array has different order' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -349,7 +351,7 @@ try } } - Context '== Array has different order but SortArrayValues is used' { + Context 'When array has different order but SortArrayValues is used' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -371,7 +373,7 @@ try } - Context '!= Array has a value with a different type' { + Context 'When array has a value with a different type' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -391,7 +393,7 @@ try } } - Context '== Array has a value with a different type but TurnOffTypeChecking is used' { + Context 'When array has a value with a different type but TurnOffTypeChecking is used' { $desiredValues = [PSObject] @{ String = 'a string' Bool = $true @@ -411,6 +413,43 @@ try $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 'When testing hashtables' { @@ -426,7 +465,7 @@ try } } - Context '!= Hashtable missing a value' { + Context 'When hashtable is missing a value' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -451,7 +490,7 @@ try } } - Context '!= Hashtable has an additional value' { + Context 'When hashtable has an additional value' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -476,7 +515,7 @@ try } } - Context '!= Hashtable has a different value' { + Context 'When hashtable has a different value' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -501,7 +540,7 @@ try } } - Context '!= Array in hashtable has different order' { + Context 'When an array in hashtable has different order' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -526,7 +565,7 @@ try } } - Context '== Array in hashtable has different order but SortArrayValues is used' { + Context 'When an array in hashtable has different order but SortArrayValues is used' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -553,7 +592,7 @@ try } - Context '!= Hashtable has a value with a different type' { + Context 'When hashtable has a value with a different type' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -578,7 +617,7 @@ try } } - Context '== Hashtable has a value with a different type but TurnOffTypeChecking is used' { + Context 'When hashtable has a value with a different type but TurnOffTypeChecking is used' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -624,7 +663,7 @@ try }) } - Context '== Everything matches' { + Context 'When everything matches' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -655,7 +694,7 @@ try } } - Context '== CimInstances missing a value in the desired state (not recognized)' { + Context 'When CimInstances missing a value in the desired state (not recognized)' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true From 074620dde6f14d6d9c26d201809db0b3a64eec0f Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 19:04:29 +1200 Subject: [PATCH 11/15] Add missing file save --- Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 4d944629..144b3f50 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -724,7 +724,7 @@ try } } - Context '!= CimInstances missing a value in the desired state (recognized using ReverseCheck)' { + Context 'When CimInstances missing a value in the desired state (recognized using ReverseCheck)' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -755,7 +755,7 @@ try } } - Context '!= CimInstances have an additional value' { + Context 'When CimInstances have an additional value' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -787,7 +787,7 @@ try } } - Context '!= CimInstances have a different value' { + Context 'When CimInstances have a different value' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -818,7 +818,7 @@ try } } - Context '!= CimInstaces have a value with a different type' { + Context 'When CimInstances have a value with a different type' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -849,7 +849,7 @@ try } } - Context '== CimInstaces have a value with a different type but TurnOffTypeChecking is used' { + Context 'When CimInstances have a value with a different type but TurnOffTypeChecking is used' { $desiredValues = [PSObject]@{ String = 'a string' Bool = $true @@ -895,7 +895,7 @@ try } } - Context '== even if missing property in the desired state' { + Context 'When even if missing property in the desired state' { $desiredValues = [PSObject] @{ Array = 'a', 'b', 'c', 1 Hashtable = @{ @@ -917,7 +917,7 @@ try } } - Context '!= missing property in the desired state' { + Context 'When missing property in the desired state' { $currentValues = @{ String = 'a string' Bool = $true @@ -942,7 +942,6 @@ try } Context 'When testing parameter types' { - Context 'When desired value is of the wrong type' { $currentValues = @{ String = 'a string' From 40fd223eacd4d30db2f0b4b0e78161b73cf5347b Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 19:19:49 +1200 Subject: [PATCH 12/15] Suppress testing of Schedules not of Once, Weekly or Daily --- CHANGELOG.md | 2 + .../MSFT_ScheduledTask.psm1 | 41 +++++++++++-------- Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 5 +-- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d410c16..08205441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ - 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 a9d34580..08fd107e 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1293,24 +1293,6 @@ function Test-TargetResource $currentValues = Get-CurrentResource -TaskName $TaskName -TaskPath $TaskPath - <# - 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 - } - # Convert the strings containing time spans to TimeSpan Objects if ($PSBoundParameters.ContainsKey('RepeatInterval')) { @@ -1367,6 +1349,29 @@ 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') } if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index cdb91610..da422210 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -2074,10 +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 = $getTargetResourceParameters + @{ ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - StartTime = Get-Date -Date $startTimeString ScheduleType = 'AtStartup' Delay = '00:01:00' Enable = $true @@ -2096,7 +2094,7 @@ try Triggers = @( [pscustomobject] @{ Delay = 'PT1M' - StartBoundary = $startTimeString + StartBoundary = '' CimClass = @{ CimClassName = 'MSFT_TaskBootTrigger' } @@ -2112,7 +2110,6 @@ try $result = Get-TargetResource @getTargetResourceParameters $result.Enable | Should -Be $testParameters.Enable $result.Ensure | Should -Be 'Present' - $result.StartTime | Should -Be (Get-Date -Date $startTimeString) $result.ScheduleType | Should -Be 'AtStartup' $result.Delay | Should -Be $testParameters.Delay } From e13bed7d2cb83a4540d41fa6ba7e954859986b79 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 19:47:23 +1200 Subject: [PATCH 13/15] Fix ScheduledTask parameter output types --- CHANGELOG.md | 6 ++---- DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08205441..bddb9e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,10 +27,8 @@ - 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` parameter from `Get-TargetResource` - to match MOF. - - Correct output type of `StartTime` parameter from `Get-TargetResource` to - match MOF. + - 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 diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 08fd107e..98588a8c 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1759,7 +1759,7 @@ function Get-CurrentResource if ($day -ne 0) { - $daysOfWeek += [ScheduledTask.DaysOfWeek] $day + $daysOfWeek += [System.String][ScheduledTask.DaysOfWeek] $day } } @@ -1795,11 +1795,11 @@ function Get-CurrentResource RandomDelay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.RandomDelay RepetitionDuration = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Duration -AllowIndefinitely DaysOfWeek = $daysOfWeek - WeeksInterval = $trigger.WeeksInterval + WeeksInterval = [System.Uint32] $trigger.WeeksInterval User = $task.Principal.UserId DisallowDemandStart = -not $settings.AllowDemandStart DisallowHardTerminate = -not $settings.AllowHardTerminate - Compatibility = $settings.Compatibility + Compatibility = [System.String] $settings.Compatibility AllowStartIfOnBatteries = -not $settings.DisallowStartIfOnBatteries Hidden = $settings.Hidden RunOnlyIfIdle = $settings.RunOnlyIfIdle From 2a2625bca737f48073c08d0c5c717e0c900a9ac8 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 19:53:45 +1200 Subject: [PATCH 14/15] Fix last ScheduledTask parameter --- DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 98588a8c..95a046b1 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1759,7 +1759,7 @@ function Get-CurrentResource if ($day -ne 0) { - $daysOfWeek += [System.String][ScheduledTask.DaysOfWeek] $day + $daysOfWeek += [ScheduledTask.DaysOfWeek] $day } } @@ -1794,7 +1794,7 @@ function Get-CurrentResource DaysInterval = [System.Uint32] $trigger.DaysInterval RandomDelay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.RandomDelay RepetitionDuration = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Repetition.Duration -AllowIndefinitely - DaysOfWeek = $daysOfWeek + DaysOfWeek = [System.String[]] $daysOfWeek WeeksInterval = [System.Uint32] $trigger.WeeksInterval User = $task.Principal.UserId DisallowDemandStart = -not $settings.AllowDemandStart From f0544b9b6f8dbcf20096d7ecefe33594a0ce7d19 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Sat, 24 Aug 2019 19:53:51 +1200 Subject: [PATCH 15/15] Fix last ScheduledTask parameter --- DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 95a046b1..6374e1f4 100644 --- a/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -1759,7 +1759,7 @@ function Get-CurrentResource if ($day -ne 0) { - $daysOfWeek += [ScheduledTask.DaysOfWeek] $day + $daysOfWeek += [System.String][ScheduledTask.DaysOfWeek] $day } }