diff --git a/DSCResources/MSFT_xComputer/MSFT_xComputer.psm1 b/DSCResources/MSFT_xComputer/MSFT_xComputer.psm1 index 772885c5..dc90aea7 100644 --- a/DSCResources/MSFT_xComputer/MSFT_xComputer.psm1 +++ b/DSCResources/MSFT_xComputer/MSFT_xComputer.psm1 @@ -1,8 +1,38 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "", Scope = "Function")] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope = 'Function')] param ( ) +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xComputer' + +<# + .SYNOPSIS + Gets the current state of the computer. + + .PARAMETER Name + The desired computer name. + + .PARAMETER DomainName + The name of the domain to join. + + .PARAMETER JoinOU + The distinguished name of the organizational unit that the computer + account will be created in. + + .PARAMETER Credential + Credential to be used to join a domain. + + .PARAMETER UnjoinCredential + Credential to be used to leave a domain. + + .PARAMETER WorkGroupName + The name of the workgroup. + + .PARAMETER Description + The value assigned here will be set as the local computer description. +#> function Get-TargetResource { [CmdletBinding()] @@ -33,10 +63,14 @@ function Get-TargetResource [Parameter()] [System.String] - $WorkGroupName + $WorkGroupName, + + [Parameter()] + [System.String] + $Description ) - Write-Verbose -Message "Getting computer state for '$($Name)'." + Write-Verbose -Message ($script:localizedData.GettingComputerStateMessage -f $Name) $convertToCimCredential = New-CimInstance ` -ClassName MSFT_Credential ` @@ -51,7 +85,7 @@ function Get-TargetResource -ClassName MSFT_Credential ` -Property @{ Username = [System.String] $UnjoinCredential.UserName - Password = [System.String]$null + Password = [System.String] $null } ` -Namespace root/microsoft/windows/desiredstateconfiguration ` -ClientOnly @@ -61,14 +95,41 @@ function Get-TargetResource DomainName = Get-ComputerDomain JoinOU = $JoinOU CurrentOU = Get-ComputerOU - Credential = [ciminstance]$convertToCimCredential - UnjoinCredential = [ciminstance]$convertToCimUnjoinCredential + Credential = [ciminstance] $convertToCimCredential + UnjoinCredential = [ciminstance] $convertToCimUnjoinCredential WorkGroupName = (Get-CimInstance -Class 'Win32_ComputerSystem').Workgroup + Description = (Get-CimInstance -Class 'Win32_OperatingSystem').Description } - $returnValue + return $returnValue } +<# + .SYNOPSIS + Sets the current state of the computer. + + .PARAMETER Name + The desired computer name. + + .PARAMETER DomainName + The name of the domain to join. + + .PARAMETER JoinOU + The distinguished name of the organizational unit that the computer + account will be created in. + + .PARAMETER Credential + Credential to be used to join a domain. + + .PARAMETER UnjoinCredential + Credential to be used to leave a domain. + + .PARAMETER WorkGroupName + The name of the workgroup. + + .PARAMETER Description + The value assigned here will be set as the local computer description. +#> function Set-TargetResource { [CmdletBinding()] @@ -98,9 +159,15 @@ function Set-TargetResource [Parameter()] [System.String] - $WorkGroupName + $WorkGroupName, + + [Parameter()] + [System.String] + $Description ) + Write-Verbose -Message ($script:localizedData.SettingComputerStateMessage -f $Name) + Assert-DomainOrWorkGroup -DomainName $DomainName -WorkGroupName $WorkGroupName if ($Name -eq 'localhost') @@ -108,6 +175,14 @@ function Set-TargetResource $Name = $env:COMPUTERNAME } + if ($PSBoundParameters.ContainsKey('Description')) + { + Write-Verbose -Message ($script:localizedData.SettingComputerDescriptionMessage -f $Description) + $win32OperatingSystemCimInstance = Get-CimInstance -ClassName Win32_OperatingSystem + $win32OperatingSystemCimInstance.Description = $Description + Set-CimInstance -InputObject $win32OperatingSystemCimInstance + } + if ($Credential) { if ($DomainName) @@ -116,7 +191,7 @@ function Set-TargetResource { # Rename the computer, but stay joined to the domain. Rename-Computer -NewName $Name -DomainCredential $Credential -Force - Write-Verbose -Message "Renamed computer to '$($Name)'." + Write-Verbose -Message ($script:localizedData.RenamedComputerMessage -f $Name) } else { @@ -125,40 +200,67 @@ function Set-TargetResource # Rename the computer, and join it to the domain. if ($UnjoinCredential) { - Add-Computer -DomainName $DomainName -Credential $Credential -NewName $Name -UnjoinDomainCredential $UnjoinCredential -Force + Add-Computer ` + -DomainName $DomainName ` + -Credential $Credential ` + -NewName $Name ` + -UnjoinDomainCredential $UnjoinCredential ` + -Force } else { if ($JoinOU) { - Add-Computer -DomainName $DomainName -Credential $Credential -NewName $Name -OUPath $JoinOU -Force + Add-Computer ` + -DomainName $DomainName ` + -Credential $Credential ` + -NewName $Name ` + -OUPath $JoinOU ` + -Force } else { - Add-Computer -DomainName $DomainName -Credential $Credential -NewName $Name -Force + Add-Computer ` + -DomainName $DomainName ` + -Credential $Credential ` + -NewName $Name ` + -Force } } - Write-Verbose -Message "Renamed computer to '$($Name)' and added to the domain '$($DomainName)." + + Write-Verbose -Message ($script:localizedData.RenamedComputerAndJoinedDomainMessage -f $Name,$DomainName) } else { # Same computer name, and join it to the domain. if ($UnjoinCredential) { - Add-Computer -DomainName $DomainName -Credential $Credential -UnjoinDomainCredential $UnjoinCredential -Force + Add-Computer ` + -DomainName $DomainName ` + -Credential $Credential ` + -UnjoinDomainCredential $UnjoinCredential ` + -Force } else { if ($JoinOU) { - Add-Computer -DomainName $DomainName -Credential $Credential -OUPath $JoinOU -Force + Add-Computer ` + -DomainName $DomainName ` + -Credential $Credential ` + -OUPath $JoinOU ` + -Force } else { - Add-Computer -DomainName $DomainName -Credential $Credential -Force + Add-Computer ` + -DomainName $DomainName ` + -Credential $Credential ` + -Force } } - Write-Verbose -Message "Added computer to domain '$($DomainName)." + + Write-Verbose -Message ($script:localizedData.JoinedDomainMessage -f $DomainName) } } } @@ -167,22 +269,33 @@ function Set-TargetResource if ($WorkGroupName -eq (Get-CimInstance -Class 'Win32_ComputerSystem').Workgroup) { # Rename the computer, but stay in the same workgroup. - Rename-Computer -NewName $Name - Write-Verbose -Message "Renamed computer to '$($Name)'." + Rename-Computer ` + -NewName $Name + + Write-Verbose -Message ($script:localizedData.RenamedComputerMessage -f $Name) } else { if ($Name -ne $env:COMPUTERNAME) { # Rename the computer, and join it to the workgroup. - Add-Computer -NewName $Name -Credential $Credential -WorkgroupName $WorkGroupName -Force - Write-Verbose -Message "Renamed computer to '$($Name)' and addded to workgroup '$($WorkGroupName)'." + Add-Computer ` + -NewName $Name ` + -Credential $Credential ` + -WorkgroupName $WorkGroupName ` + -Force + + Write-Verbose -Message ($script:localizedData.RenamedComputerAndJoinedWorkgroupMessage -f $Name,$WorkGroupName) } else { # Same computer name, and join it to the workgroup. - Add-Computer -WorkGroupName $WorkGroupName -Credential $Credential -Force - Write-Verbose -Message "Added computer to workgroup '$($WorkGroupName)'." + Add-Computer ` + -WorkGroupName $WorkGroupName ` + -Credential $Credential ` + -Force + + Write-Verbose -Message ($script:localizedData.JoinedWorkgroupMessage -f $WorkGroupName) } } } @@ -190,13 +303,20 @@ function Set-TargetResource { if (Get-ComputerDomain) { - Rename-Computer -NewName $Name -DomainCredential $Credential -Force - Write-Verbose -Message "Renamed computer to '$($Name)'." + Rename-Computer ` + -NewName $Name ` + -DomainCredential $Credential ` + -Force + + Write-Verbose -Message ($script:localizedData.RenamedComputerMessage -f $Name) } else { - Rename-Computer -NewName $Name -Force - Write-Verbose -Message "Renamed computer to '$($Name)'." + Rename-Computer ` + -NewName $Name ` + -Force + + Write-Verbose -Message ($script:localizedData.RenamedComputerMessage -f $Name) } } } @@ -204,30 +324,40 @@ function Set-TargetResource { if ($DomainName) { - throw 'Missing domain join credentials.' + New-InvalidArgumentException ` + -Message ($script:localizedData.CredentialsNotSpecifiedError) ` + -ArgumentName 'Credentials' } + if ($WorkGroupName) { - if ($WorkGroupName -eq (Get-CimInstance -Class 'Win32_ComputerSystem').Workgroup) { # Same workgroup, new computer name - Rename-Computer -NewName $Name -force - Write-Verbose -Message "Renamed computer to '$($Name)'." + Rename-Computer ` + -NewName $Name ` + -Force + + Write-Verbose -Message ($script:localizedData.RenamedComputerMessage -f $Name) } else { if ($name -ne $env:COMPUTERNAME) { # New workgroup, new computer name - Add-Computer -WorkgroupName $WorkGroupName -NewName $Name - Write-Verbose -Message "Renamed computer to '$($Name)' and added to workgroup '$($WorkGroupName)'." + Add-Computer ` + -WorkgroupName $WorkGroupName ` + -NewName $Name + + Write-Verbose -Message ($script:localizedData.RenamedComputerAndJoinedWorkgroupMessage -f $Name,$WorkGroupName) } else { # New workgroup, same computer name - Add-Computer -WorkgroupName $WorkGroupName - Write-Verbose -Message "Added computer to workgroup '$($WorkGroupName)'." + Add-Computer ` + -WorkgroupName $WorkGroupName + + Write-Verbose -Message ($script:localizedData.JoinedWorkgroupMessage -f $WorkGroupName) } } } @@ -235,8 +365,10 @@ function Set-TargetResource { if ($Name -ne $env:COMPUTERNAME) { - Rename-Computer -NewName $Name - Write-Verbose -Message "Renamed computer to '$($Name)'." + Rename-Computer ` + -NewName $Name + + Write-Verbose -Message ($script:localizedData.RenamedComputerMessage -f $Name) } } } @@ -244,6 +376,32 @@ function Set-TargetResource $global:DSCMachineStatus = 1 } +<# + .SYNOPSIS + Tests the current state of the computer. + + .PARAMETER Name + The desired computer name. + + .PARAMETER DomainName + The name of the domain to join. + + .PARAMETER JoinOU + The distinguished name of the organizational unit that the computer + account will be created in. + + .PARAMETER Credential + Credential to be used to join a domain. + + .PARAMETER UnjoinCredential + Credential to be used to leave a domain. + + .PARAMETER WorkGroupName + The name of the workgroup. + + .PARAMETER Description + The value assigned here will be set as the local computer description. +#> function Test-TargetResource { [CmdletBinding()] @@ -274,29 +432,44 @@ function Test-TargetResource [Parameter()] [System.String] - $WorkGroupName + $WorkGroupName, + + [Parameter()] + [System.String] + $Description ) - Write-Verbose -Message 'Validate desired Name is a valid name' + Write-Verbose -Message ($script:localizedData.TestingComputerStateMessage -f $Name) - Write-Verbose -Message 'Checking if computer name is correct' if (($Name -ne 'localhost') -and ($Name -ne $env:COMPUTERNAME)) { return $false } + if ($PSBoundParameters.ContainsKey('Description')) + { + Write-Verbose -Message ($script:localizedData.CheckingComputerDescriptionMessage -f $Description) + + if ($Description -ne (Get-CimInstance -Class 'Win32_OperatingSystem').Description) + { + return $false + } + } + Assert-DomainOrWorkGroup -DomainName $DomainName -WorkGroupName $WorkGroupName if ($DomainName) { if (-not ($Credential)) { - throw 'Need to specify credentials with domain' + New-InvalidArgumentException ` + -Message ($script:localizedData.CredentialsNotSpecifiedError) ` + -ArgumentName 'Credentials' } try { - Write-Verbose "Checking if the machine is a member of $DomainName." + Write-Verbose -Message ($script:localizedData.CheckingDomainMemberMessage -f $DomainName) if ($DomainName.Contains('.')) { @@ -315,14 +488,14 @@ function Test-TargetResource } catch { - Write-Verbose 'The machine is not a domain member.' + Write-Verbose -Message ($script:localizedData.CheckingNotDomainMemberMessage) return $false } } elseif ($WorkGroupName) { - Write-Verbose -Message "Checking if workgroup name is $WorkGroupName" + Write-Verbose -Message ($script:localizedData.CheckingWorkgroupMemberMessage -f $WorkGroupName) return ($WorkGroupName -eq (Get-CimInstance -Class 'Win32_ComputerSystem').Workgroup) } @@ -333,17 +506,50 @@ function Test-TargetResource } } -function Assert-DomainOrWorkGroup($DomainName, $WorkGroupName) +<# + .SYNOPSIS + Throws an exception if both the domain name and workgroup + name is set. + + .PARAMETER DomainName + The name of the domain to join. + + .PARAMETER WorkGroupName + The name of the workgroup. +#> +function Assert-DomainOrWorkGroup { + [CmdletBinding()] + param + ( + [Parameter()] + [System.String] + $DomainName, + + [Parameter()] + [System.String] + $WorkGroupName + ) + if ($DomainName -and $WorkGroupName) { - throw 'Only DomainName or WorkGroupName can be specified at once.' + New-InvalidOperationException ` + -Message ($script:localizedData.DomainNameAndWorkgroupNameError) } } +<# + .SYNOPSIS + Returns the domain the computer is joined to. + + .PARAMETER NetBios + Specifies if the NetBIOS name is returned instead of + the fully qualified domain name. +#> function Get-ComputerDomain { [CmdletBinding()] + [OutputType([System.String])] param ( [Parameter()] @@ -361,16 +567,27 @@ function Get-ComputerDomain { $domainName = ([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()).Name } + return $domainName } catch [System.Management.Automation.MethodInvocationException] { - Write-Debug 'This machine is not a domain member.' + Write-Verbose -Message ($script:localizedData.ComputerNotInDomainMessage) } } +<# + .SYNOPSIS + Gets the organisation unit in the domain that the + computer account exists in. +#> function Get-ComputerOU { + [CmdletBinding()] + param + ( + ) + $ou = $null if (Get-ComputerDomain) diff --git a/DSCResources/MSFT_xComputer/MSFT_xComputer.schema.mof b/DSCResources/MSFT_xComputer/MSFT_xComputer.schema.mof index 44bc6b97..a81ddd4c 100644 --- a/DSCResources/MSFT_xComputer/MSFT_xComputer.schema.mof +++ b/DSCResources/MSFT_xComputer/MSFT_xComputer.schema.mof @@ -1,11 +1,12 @@ [ClassVersion("1.0.1.0"), FriendlyName("xComputer")] class MSFT_xComputer : OMI_BaseResource { - [key] string Name; - [write] string DomainName; - [write] string JoinOU; - [read] string CurrentOU; - [write,EmbeddedInstance("MSFT_Credential")] String Credential; - [write,EmbeddedInstance("MSFT_Credential")] String UnjoinCredential; - [write] string WorkGroupName; + [Key, Description("The desired computer name.")] String Name; + [Write, Description("The name of the domain to join.")] String DomainName; + [Write, Description("The distinguished name of the organizational unit that the computer account will be created in.")] String JoinOU; + [Write, Description("Credential to be used to join a domain."), EmbeddedInstance("MSFT_Credential")] String Credential; + [Write, Description("Credential to be used to leave a domain."), EmbeddedInstance("MSFT_Credential")] String UnjoinCredential; + [Write, Description("The name of the workgroup.")] String WorkGroupName; + [Write, Description("The value assigned here will be set as the local computer description.")] String Description; + [Read, Description("A read-only property that specifies the organizational unit that the computer account is currently in.")] String CurrentOU; }; diff --git a/DSCResources/MSFT_xComputer/en-US/MSFT_xComputer.strings.psd1 b/DSCResources/MSFT_xComputer/en-US/MSFT_xComputer.strings.psd1 new file mode 100644 index 00000000..7652bb17 --- /dev/null +++ b/DSCResources/MSFT_xComputer/en-US/MSFT_xComputer.strings.psd1 @@ -0,0 +1,18 @@ +ConvertFrom-StringData @' + GettingComputerStateMessage = Getting computer state for '{0}'. + SettingComputerStateMessage = Setting computer state for '{0}'. + SettingComputerDescriptionMessage = Setting computer description to '{0}'. + RenamedComputerMessage = Renamed computer to '{0}'. + RenamedComputerAndJoinedDomainMessage = Renamed computer to '{0}' and added to the domain '{1}'. + JoinedDomainMessage = Added computer to domain '{0}'. + RenamedComputerAndJoinedWorkgroupMessage = Renamed computer to '{0}' and addded to workgroup '{1}'. + JoinedWorkgroupMessage = Added computer to workgroup '{0}'. + CredentialsNotSpecifiedError = Must to specify credentials with domain. + TestingComputerStateMessage = Testing computer state for '{0}'. + CheckingComputerDescriptionMessage = Checking if computer description is '{0}'. + CheckingDomainMemberMessage = Checking if the machine is a member of domain '{0}'. + CheckingNotDomainMemberMessage = Checking if the machine is a not a member of a domain. + CheckingWorkgroupMemberMessage = Checking if the machine is a member of workgroup '{0}'. + DomainNameAndWorkgroupNameError = Only DomainName or WorkGroupName can be specified at once. + ComputerNotInDomainMessage = This machine is not a domain member. +'@ diff --git a/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.psm1 b/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.psm1 index 1d3c4f0e..5a45d753 100644 --- a/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.psm1 +++ b/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.psm1 @@ -14,92 +14,152 @@ namespace xScheduledTask } '@ -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'CommonResourceHelper.psm1') +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') <# -.SYNOPSIS - Gets the current 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 -.PARAMETER ActionExecutable - The path to the .exe for this task -.PARAMETER ActionArguments - The arguments to pass the executable -.PARAMETER ActionWorkingPath - The working path to specify for the executable -.PARAMETER ScheduleType - When should the task be executed -.PARAMETER RepeatInterval - How many units (minutes, hours, days) between each run of this task? -.PARAMETER StartTime - The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks -.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 -.PARAMETER ExecuteAsCredential - The credential this task should execute as. If not specified defaults to running as the local system account -.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. -.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. -.PARAMETER RepetitionDuration - Specifies how long the repetition pattern repeats after the task starts. -.PARAMETER DaysOfWeek - Specifies an array of the days of the week on which Task Scheduler runs the task. -.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. -.PARAMETER User - Specifies the identifier of the user for a trigger that starts a task when a user logs on. -.PARAMETER DisallowDemandStart - Indicates whether the task is prohibited to run on demand or not. Defaults to $false -.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. -.PARAMETER AllowStartIfOnBatteries - Indicates whether the task should start if the machine is on batteries or not. Defaults to $false -.PARAMETER Hidden - Indicates that the task is hidden in the Task Scheduler UI. -.PARAMETER RunOnlyIfIdle - Indicates that Task Scheduler runs the task only when the computer is idle. -.PARAMETER IdleWaitTimeout - Specifies the amount of time that Task Scheduler waits for an idle condition to occur. -.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. -.PARAMETER DisallowStartOnRemoteAppSession - Indicates that the task does not start if the task is triggered to run in a Remote Applications Integrated Locally (RAIL) session. -.PARAMETER StartWhenAvailable - Indicates that Task Scheduler can start the task at any time after its scheduled time has passed. -.PARAMETER DontStopIfGoingOnBatteries - Indicates that the task does not stop if the computer switches to battery power. -.PARAMETER WakeToRun - Indicates that Task Scheduler wakes the computer before it runs the task. -.PARAMETER IdleDuration - Specifies the amount of time that the computer must be in an idle state before Task Scheduler runs the task. -.PARAMETER RestartOnIdle - Indicates that Task Scheduler restarts the task when the computer cycles into an idle condition more than once. -.PARAMETER DontStopOnIdleEnd - Indicates that Task Scheduler does not terminate the task if the idle condition ends before the task is completed. -.PARAMETER ExecutionTimeLimit - Specifies the amount of time that Task Scheduler is allowed to complete the task. -.PARAMETER MultipleInstances - Specifies the policy that defines how Task Scheduler handles multiple instances of the task. -.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. -.PARAMETER RestartCount - Specifies the number of times that Task Scheduler attempts to restart the task. -.PARAMETER RestartInterval - Specifies the amount of time that Task Scheduler attempts to restart the task. -.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. + .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 + + .PARAMETER ActionExecutable + The path to the .exe for this task + + .PARAMETER ActionArguments + The arguments to pass the executable + + .PARAMETER ActionWorkingPath + The working path to specify for the executable + + .PARAMETER ScheduleType + When should the task be executed + + .PARAMETER RepeatInterval + How many units (minutes, hours, days) between each run of this task? + + .PARAMETER StartTime + The time of day this task should start at - defaults to 12:00 AM. Not valid for + AtLogon and AtStartup tasks + + .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 + + .PARAMETER ExecuteAsCredential + The credential this task should execute as. If not specified defaults to running + as the local system account + + .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. + + .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. + + .PARAMETER RepetitionDuration + Specifies how long the repetition pattern repeats after the task starts. + + .PARAMETER DaysOfWeek + Specifies an array of the days of the week on which Task Scheduler runs the task. + + .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. + + .PARAMETER User + Specifies the identifier of the user for a trigger that starts a task when a + user logs on. + + .PARAMETER DisallowDemandStart + Indicates whether the task is prohibited to run on demand or not. Defaults + to $false + + .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. + + .PARAMETER AllowStartIfOnBatteries + Indicates whether the task should start if the machine is on batteries or not. + Defaults to $false + + .PARAMETER Hidden + Indicates that the task is hidden in the Task Scheduler UI. + + .PARAMETER RunOnlyIfIdle + Indicates that Task Scheduler runs the task only when the computer is idle. + + .PARAMETER IdleWaitTimeout + Specifies the amount of time that Task Scheduler waits for an idle condition to occur. + + .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. + + .PARAMETER DisallowStartOnRemoteAppSession + Indicates that the task does not start if the task is triggered to run in a Remote + Applications Integrated Locally (RAIL) session. + + .PARAMETER StartWhenAvailable + Indicates that Task Scheduler can start the task at any time after its scheduled + time has passed. + + .PARAMETER DontStopIfGoingOnBatteries + Indicates that the task does not stop if the computer switches to battery power. + + .PARAMETER WakeToRun + Indicates that Task Scheduler wakes the computer before it runs the task. + + .PARAMETER IdleDuration + Specifies the amount of time that the computer must be in an idle state before + Task Scheduler runs the task. + + .PARAMETER RestartOnIdle + Indicates that Task Scheduler restarts the task when the computer cycles into an + idle condition more than once. + + .PARAMETER DontStopOnIdleEnd + Indicates that Task Scheduler does not terminate the task if the idle condition + ends before the task is completed. + + .PARAMETER ExecutionTimeLimit + Specifies the amount of time that Task Scheduler is allowed to complete the task. + + .PARAMETER MultipleInstances + Specifies the policy that defines how Task Scheduler handles multiple instances + of the task. + + .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. + + .PARAMETER RestartCount + Specifies the number of times that Task Scheduler attempts to restart the task. + + .PARAMETER RestartInterval + Specifies the amount of time that Task Scheduler attempts to restart the task. + + .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. #> function Get-TargetResource { @@ -137,8 +197,8 @@ function Get-TargetResource $ScheduleType, [Parameter()] - [System.DateTime] - $RepeatInterval = [System.DateTime] '00:00:00', + [System.String] + $RepeatInterval = '00:00:00', [Parameter()] [System.DateTime] @@ -162,12 +222,12 @@ function Get-TargetResource $DaysInterval = 1, [Parameter()] - [System.DateTime] - $RandomDelay = [System.DateTime] '00:00:00', + [System.String] + $RandomDelay = '00:00:00', [Parameter()] - [System.DateTime] - $RepetitionDuration = [System.DateTime] '00:00:00', + [System.String] + $RepetitionDuration = '00:00:00', [Parameter()] [System.String[]] @@ -207,8 +267,8 @@ function Get-TargetResource $RunOnlyIfIdle = $false, [Parameter()] - [System.DateTime] - $IdleWaitTimeout = [System.DateTime] '02:00:00', + [System.String] + $IdleWaitTimeout = '02:00:00', [Parameter()] [System.String] @@ -231,8 +291,8 @@ function Get-TargetResource $WakeToRun = $false, [Parameter()] - [System.DateTime] - $IdleDuration = [System.DateTime] '01:00:00', + [System.String] + $IdleDuration = '01:00:00', [Parameter()] [System.Boolean] @@ -243,8 +303,8 @@ function Get-TargetResource $DontStopOnIdleEnd = $false, [Parameter()] - [System.DateTime] - $ExecutionTimeLimit = [System.DateTime] '8:00:00', + [System.String] + $ExecutionTimeLimit = '08:00:00', [Parameter()] [ValidateSet('IgnoreNew', 'Parallel', 'Queue')] @@ -260,8 +320,8 @@ function Get-TargetResource $RestartCount = 0, [Parameter()] - [System.DateTime] - $RestartInterval = [System.DateTime] '00:00:00', + [System.String] + $RestartInterval = '00:00:00', [Parameter()] [System.Boolean] @@ -270,28 +330,29 @@ function Get-TargetResource $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath - Write-Verbose -Message ('Retrieving existing task ({0} in {1})' -f $TaskName, $TaskPath) + Write-Verbose -Message ('Retrieving existing task ({0} in {1}).' -f $TaskName, $TaskPath) $task = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue if ($null -eq $task) { - Write-Verbose -Message ('No task found. returning empty task {0} with Ensure = "Absent"' -f $Taskname) + Write-Verbose -Message ('No task found. returning empty task {0} with Ensure = "Absent".' -f $Taskname) + return @{ - TaskName = $TaskName + TaskName = $TaskName ActionExecutable = $ActionExecutable - Ensure = 'Absent' - ScheduleType = $ScheduleType + Ensure = 'Absent' + ScheduleType = $ScheduleType } } else { - Write-Verbose -Message ('Task {0} found in {1}. Retrieving settings, first action, first trigger and repetition settings' -f $TaskName, $TaskPath) + Write-Verbose -Message ('Task {0} found in {1}. Retrieving settings, first action, first trigger and repetition settings.' -f $TaskName, $TaskPath) + $action = $task.Actions | Select-Object -First 1 $trigger = $task.Triggers | Select-Object -First 1 $settings = $task.Settings $returnScheduleType = 'Unknown' - $returnInveral = 0 switch ($trigger.CimClass.CimClassName) { @@ -300,6 +361,7 @@ function Get-TargetResource $returnScheduleType = 'Once' break } + 'MSFT_TaskDailyTrigger' { $returnScheduleType = 'Daily' @@ -330,334 +392,218 @@ function Get-TargetResource } } - Write-Verbose -Message ('Detected schedule type {0} for first trigger' -f $returnScheduleType) - - Write-Verbose -Message 'Calculating timespans/datetimes from trigger repetition settings' + Write-Verbose -Message ('Detected schedule type {0} for first trigger.' -f $returnScheduleType) - $repInterval = $trigger.Repetition.Interval - $Days = $Hours = $Minutes = $Seconds = 0 + $daysOfWeek = @() - if ($repInterval -match 'P(?\d{0,3})D') + foreach ($binaryAdductor in 1, 2, 4, 8, 16, 32, 64) { - $Days = $matches.Days - } + $day = $trigger.DaysOfWeek -band $binaryAdductor - if ($repInterval -match '(?\d{0,2})H') - { - $Hours = $matches.Hours + if ($day -ne 0) + { + $daysOfWeek += [xScheduledTask.DaysOfWeek] $day + } } - if ($repInterval -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } + $startAt = $trigger.StartBoundary - if ($repInterval -match '(?\d{0,2})S') + if ($startAt) { - $Seconds = $matches.Seconds + $startAt = [System.DateTime] $startAt } - - $returnInveral = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds - - $repDuration = $trigger.Repetition.Duration - $Days = $Hours = $Minutes = $Seconds = 0 - - if ($repDuration -match 'P(?\d{0,3})D') + else { - $Days = $matches.Days + $startAt = $StartTime } - if ($repDuration -match '(?\d{0,2})H') - { - $Hours = $matches.Hours + return @{ + TaskName = $task.TaskName + TaskPath = $task.TaskPath + StartTime = $startAt + 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 + 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.IdleWaitTimeout + 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 } + } +} - if ($repDuration -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } +<# + .SYNOPSIS + Tests if the current resource state matches the desired resource state - if ($repDuration -match '(?\d{0,2})S') - { - $Seconds = $matches.Seconds - } + .PARAMETER TaskName + The name of the task - $repetitionDurationReturn = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds + .PARAMETER TaskPath + The path to the task - defaults to the root directory - $resInterval = $settings.RestartInterval - $Days = $Hours = $Minutes = $Seconds = 0 + .PARAMETER Description + The task description - if ($resInterval -match 'P(?\d{0,3})D') - { - $Days = $matches.Days - } + .PARAMETER ActionExecutable + The path to the .exe for this task - if ($resInterval -match '(?\d{0,2})H') - { - $Hours = $matches.Hours - } + .PARAMETER ActionArguments + The arguments to pass the executable - if ($resInterval -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } + .PARAMETER ActionWorkingPath + The working path to specify for the executable - if ($resInterval -match '(?\d{0,2})S') - { - $Seconds = $matches.Seconds - } + .PARAMETER ScheduleType + When should the task be executed - $restartIntervalReturn = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds + .PARAMETER RepeatInterval + How many units (minutes, hours, days) between each run of this task? - $exeLim = $settings.ExecutionTimeLimit - $Days = $Hours = $Minutes = $Seconds = 0 + .PARAMETER StartTime + The time of day this task should start at - defaults to 12:00 AM. Not valid for + AtLogon and AtStartup tasks - if ($exeLim -match 'P(?\d{0,3})D') - { - $Days = $matches.Days - } + .PARAMETER Ensure + Present if the task should exist, Absent if it should be removed - if ($exeLim -match '(?\d{0,2})H') - { - $Hours = $matches.Hours - } + .PARAMETER Enable + True if the task should be enabled, false if it should be disabled - if ($exeLim -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } + .PARAMETER ExecuteAsCredential + The credential this task should execute as. If not specified defaults to running + as the local system account - if ($exeLim -match '(?\d{0,2})S') - { - $Seconds = $matches.Seconds - } + .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. - $executionTimeLimitReturn = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds + .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. - $idleDur = $settings.IdleSettings.IdleDuration - $Days = $Hours = $Minutes = $Seconds = 0 + .PARAMETER RepetitionDuration + Specifies how long the repetition pattern repeats after the task starts. - if ($idleDur -match 'P(?\d{0,3})D') - { - $Days = $matches.Days - } + .PARAMETER DaysOfWeek + Specifies an array of the days of the week on which Task Scheduler runs the task. - if ($idleDur -match '(?\d{0,2})H') - { - $Hours = $matches.Hours - } + .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. - if ($idleDur -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } + .PARAMETER User + Specifies the identifier of the user for a trigger that starts a task when a + user logs on. - if ($idleDur -match '(?\d{0,2})S') - { - $Seconds = $matches.Seconds - } + .PARAMETER DisallowDemandStart + Indicates whether the task is prohibited to run on demand or not. Defaults + to $false - $idleDurationReturn = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds + .PARAMETER DisallowHardTerminate + Indicates whether the task is prohibited to be terminated or not. Defaults + to $false - $idleWait = $settings.IdleSettings.IdleWaitTimeout - $Days = $Hours = $Minutes = $Seconds = 0 + .PARAMETER Compatibility + The task compatibility level. Defaults to Vista. - if ($idleWait -match 'P(?\d{0,3})D') - { - $Days = $matches.Days - } + .PARAMETER AllowStartIfOnBatteries + Indicates whether the task should start if the machine is on batteries or not. + Defaults to $false - if ($idleWait -match '(?\d{0,2})H') - { - $Hours = $matches.Hours - } + .PARAMETER Hidden + Indicates that the task is hidden in the Task Scheduler UI. - if ($idleWait -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } + .PARAMETER RunOnlyIfIdle + Indicates that Task Scheduler runs the task only when the computer is idle. - if ($idleWait -match '(?\d{0,2})S') - { - $Seconds = $matches.Seconds - } + .PARAMETER IdleWaitTimeout + Specifies the amount of time that Task Scheduler waits for an idle condition to occur. - $idleWaitTimeoutReturn = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds + .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. - $rndDelay = $trigger.RandomDelay - $Days = $Hours = $Minutes = $Seconds = 0 + .PARAMETER DisallowStartOnRemoteAppSession + Indicates that the task does not start if the task is triggered to run in a Remote + Applications Integrated Locally (RAIL) session. - if ($rndDelay -match 'P(?\d{0,3})D') - { - $Days = $matches.Days - } + .PARAMETER StartWhenAvailable + Indicates that Task Scheduler can start the task at any time after its scheduled + time has passed. - if ($rndDelay -match '(?\d{0,2})H') - { - $Hours = $matches.Hours - } + .PARAMETER DontStopIfGoingOnBatteries + Indicates that the task does not stop if the computer switches to battery power. - if ($rndDelay -match '(?\d{0,2})M') - { - $Minutes = $matches.Minutes - } + .PARAMETER WakeToRun + Indicates that Task Scheduler wakes the computer before it runs the task. - if ($rndDelay -match '(?\d{0,2})S') - { - $Seconds = $matches.Seconds - } + .PARAMETER IdleDuration + Specifies the amount of time that the computer must be in an idle state before + Task Scheduler runs the task. - $randomDelayReturn = New-TimeSpan -Days $Days -Hours $Hours -Minutes $Minutes -Seconds $seconds + .PARAMETER RestartOnIdle + Indicates that Task Scheduler restarts the task when the computer cycles into an + idle condition more than once. - $DaysOfWeek = @() - foreach ($binaryAdductor in 1, 2, 4, 8, 16, 32, 64) - { - $Day = $trigger.DaysOfWeek -band $binaryAdductor - if ($Day -ne 0) - { - $DaysOfWeek += [xScheduledTask.DaysOfWeek] $Day - } - } + .PARAMETER DontStopOnIdleEnd + Indicates that Task Scheduler does not terminate the task if the idle condition + ends before the task is completed. - $startAt = $trigger.StartBoundary + .PARAMETER ExecutionTimeLimit + Specifies the amount of time that Task Scheduler is allowed to complete the task. - if ($startAt) - { - $startAt = [System.DateTime] $startAt - } - else - { - $startAt = $StartTime - } + .PARAMETER MultipleInstances + Specifies the policy that defines how Task Scheduler handles multiple instances + of the task. - return @{ - TaskName = $task.TaskName - TaskPath = $task.TaskPath - StartTime = $startAt - Ensure = 'Present' - Description = $task.Description - ActionExecutable = $action.Execute - ActionArguments = $action.Arguments - ActionWorkingPath = $action.WorkingDirectory - ScheduleType = $returnScheduleType - RepeatInterval = [System.DateTime]::Today.Add($returnInveral) - ExecuteAsCredential = $task.Principal.UserId - Enable = $settings.Enabled - DaysInterval = $trigger.DaysInterval - RandomDelay = [System.DateTime]::Today.Add($randomDelayReturn) - RepetitionDuration = [System.DateTime]::Today.Add($repetitionDurationReturn) - 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 = $idleWaitTimeoutReturn - NetworkName = $settings.NetworkSettings.Name - DisallowStartOnRemoteAppSession = $settings.DisallowStartOnRemoteAppSession - StartWhenAvailable = $settings.StartWhenAvailable - DontStopIfGoingOnBatteries = -not $settings.StopIfGoingOnBatteries - WakeToRun = $settings.WakeToRun - IdleDuration = [System.DateTime]::Today.Add($idleDurationReturn) - RestartOnIdle = $settings.IdleSettings.RestartOnIdle - DontStopOnIdleEnd = -not $settings.IdleSettings.StopOnIdleEnd - ExecutionTimeLimit = [System.DateTime]::Today.Add($executionTimeLimitReturn) - MultipleInstances = $settings.MultipleInstances - Priority = $settings.Priority - RestartCount = $settings.RestartCount - RestartInterval = [System.DateTime]::Today.Add($restartIntervalReturn) - RunOnlyIfNetworkAvailable = $settings.RunOnlyIfNetworkAvailable - } - } -} + .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. -<# -.SYNOPSIS - Applies 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 -.PARAMETER ActionExecutable - The path to the .exe for this task -.PARAMETER ActionArguments - The arguments to pass the executable -.PARAMETER ActionWorkingPath - The working path to specify for the executable -.PARAMETER ScheduleType - When should the task be executed -.PARAMETER RepeatInterval - How many units (minutes, hours, days) between each run of this task? -.PARAMETER StartTime - The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks -.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 -.PARAMETER ExecuteAsCredential - The credential this task should execute as. If not specified defaults to running as the local system account -.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. -.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. -.PARAMETER RepetitionDuration - Specifies how long the repetition pattern repeats after the task starts. -.PARAMETER DaysOfWeek - Specifies an array of the days of the week on which Task Scheduler runs the task. -.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. -.PARAMETER User - Specifies the identifier of the user for a trigger that starts a task when a user logs on. -.PARAMETER DisallowDemandStart - Indicates whether the task is prohibited to run on demand or not. Defaults to $false -.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. -.PARAMETER AllowStartIfOnBatteries - Indicates whether the task should start if the machine is on batteries or not. Defaults to $false -.PARAMETER Hidden - Indicates that the task is hidden in the Task Scheduler UI. -.PARAMETER RunOnlyIfIdle - Indicates that Task Scheduler runs the task only when the computer is idle. -.PARAMETER IdleWaitTimeout - Specifies the amount of time that Task Scheduler waits for an idle condition to occur. -.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. -.PARAMETER DisallowStartOnRemoteAppSession - Indicates that the task does not start if the task is triggered to run in a Remote Applications Integrated Locally (RAIL) session. -.PARAMETER StartWhenAvailable - Indicates that Task Scheduler can start the task at any time after its scheduled time has passed. -.PARAMETER DontStopIfGoingOnBatteries - Indicates that the task does not stop if the computer switches to battery power. -.PARAMETER WakeToRun - Indicates that Task Scheduler wakes the computer before it runs the task. -.PARAMETER IdleDuration - Specifies the amount of time that the computer must be in an idle state before Task Scheduler runs the task. -.PARAMETER RestartOnIdle - Indicates that Task Scheduler restarts the task when the computer cycles into an idle condition more than once. -.PARAMETER DontStopOnIdleEnd - Indicates that Task Scheduler does not terminate the task if the idle condition ends before the task is completed. -.PARAMETER ExecutionTimeLimit - Specifies the amount of time that Task Scheduler is allowed to complete the task. -.PARAMETER MultipleInstances - Specifies the policy that defines how Task Scheduler handles multiple instances of the task. -.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. -.PARAMETER RestartCount - Specifies the number of times that Task Scheduler attempts to restart the task. -.PARAMETER RestartInterval - Specifies the amount of time that Task Scheduler attempts to restart the task. -.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. + .PARAMETER RestartCount + Specifies the number of times that Task Scheduler attempts to restart the task. + + .PARAMETER RestartInterval + Specifies the amount of time that Task Scheduler attempts to restart the task. + + .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. #> function Set-TargetResource { @@ -694,8 +640,8 @@ function Set-TargetResource $ScheduleType, [Parameter()] - [System.DateTime] - $RepeatInterval = [System.DateTime] '00:00:00', + [System.String] + $RepeatInterval = '00:00:00', [Parameter()] [System.DateTime] @@ -719,12 +665,12 @@ function Set-TargetResource $DaysInterval = 1, [Parameter()] - [System.DateTime] - $RandomDelay = [System.DateTime] '00:00:00', + [System.String] + $RandomDelay = '00:00:00', [Parameter()] - [System.DateTime] - $RepetitionDuration = [System.DateTime] '00:00:00', + [System.String] + $RepetitionDuration = '00:00:00', [Parameter()] [System.String[]] @@ -764,8 +710,8 @@ function Set-TargetResource $RunOnlyIfIdle = $false, [Parameter()] - [System.DateTime] - $IdleWaitTimeout = [System.DateTime] '02:00:00', + [System.String] + $IdleWaitTimeout = '02:00:00', [Parameter()] [System.String] @@ -788,8 +734,8 @@ function Set-TargetResource $WakeToRun = $false, [Parameter()] - [System.DateTime] - $IdleDuration = [System.DateTime] '01:00:00', + [System.String] + $IdleDuration = '01:00:00', [Parameter()] [System.Boolean] @@ -800,8 +746,8 @@ function Set-TargetResource $DontStopOnIdleEnd = $false, [Parameter()] - [System.DateTime] - $ExecutionTimeLimit = [System.DateTime] '8:00:00', + [System.String] + $ExecutionTimeLimit = '08:00:00', [Parameter()] [ValidateSet('IgnoreNew', 'Parallel', 'Queue')] @@ -817,8 +763,8 @@ function Set-TargetResource $RestartCount = 0, [Parameter()] - [System.DateTime] - $RestartInterval = [System.DateTime] '00:00:00', + [System.String] + $RestartInterval = '00:00:00', [Parameter()] [System.Boolean] @@ -827,200 +773,248 @@ function Set-TargetResource $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath - Write-Verbose -Message ('Entering Set-TargetResource for {0} in {1}' -f $TaskName, $TaskPath) + Write-Verbose -Message ('Entering Set-TargetResource for {0} in {1}.' -f $TaskName, $TaskPath) + + # Convert the strings containing time spans to TimeSpan Objects + [System.TimeSpan] $RepeatInterval = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RepeatInterval + [System.TimeSpan] $RandomDelay = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RandomDelay + [System.TimeSpan] $RepetitionDuration = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RepetitionDuration -AllowIndefinitely + [System.TimeSpan] $IdleWaitTimeout = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $IdleWaitTimeout + [System.TimeSpan] $IdleDuration = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $IdleDuration + [System.TimeSpan] $ExecutionTimeLimit = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $ExecutionTimeLimit + [System.TimeSpan] $RestartInterval = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RestartInterval + $currentValues = Get-TargetResource @PSBoundParameters if ($Ensure -eq 'Present') { - if ($RepetitionDuration.TimeOfDay -lt $RepeatInterval.TimeOfDay) + if ($RepetitionDuration -lt $RepeatInterval) { - $exceptionMessage = 'Repetition duration {0} is less than repetition interval {1}. Please set RepeatInterval to a value lower or equal to RepetitionDuration' -f $RepetitionDuration.TimeOfDay, $RepeatInterval.TimeOfDay + $exceptionMessage = 'Repetition duration {0} is less than repetition interval {1}. Please set RepeatInterval to a value lower or equal to RepetitionDuration.' -f $RepetitionDuration, $RepeatInterval New-InvalidArgumentException -Message $exceptionMessage -ArgumentName RepeatInterval } if ($ScheduleType -eq 'Daily' -and $DaysInterval -eq 0) { - $exceptionMessage = 'Schedules of the type Daily must have a DaysInterval greater than 0 (value entered: {0})' -f $DaysInterval + $exceptionMessage = 'Schedules of the type Daily must have a DaysInterval greater than 0 (value entered: {0}).' -f $DaysInterval New-InvalidArgumentException -Message $exceptionMessage -ArgumentName DaysInterval } if ($ScheduleType -eq 'Weekly' -and $WeeksInterval -eq 0) { - $exceptionMessage = 'Schedules of the type Weekly must have a WeeksInterval greater than 0 (value entered: {0})' -f $WeeksInterval + $exceptionMessage = 'Schedules of the type Weekly must have a WeeksInterval greater than 0 (value entered: {0}).' -f $WeeksInterval New-InvalidArgumentException -Message $exceptionMessage -ArgumentName WeeksInterval } if ($ScheduleType -eq 'Weekly' -and $DaysOfWeek.Count -eq 0) { - $exceptionMessage = 'Schedules of the type Weekly must have at least one weekday selected' + $exceptionMessage = 'Schedules of the type Weekly must have at least one weekday selected.' New-InvalidArgumentException -Message $exceptionMessage -ArgumentName DaysOfWeek } - $actionArgs = @{ + $actionParameters = @{ Execute = $ActionExecutable } if ($ActionArguments) { - $actionArgs.Add('Argument', $ActionArguments) + $actionParameters.Add('Argument', $ActionArguments) } if ($ActionWorkingPath) { - $actionArgs.Add('WorkingDirectory', $ActionWorkingPath) + $actionParameters.Add('WorkingDirectory', $ActionWorkingPath) } - $action = New-ScheduledTaskAction @actionArgs + $action = New-ScheduledTaskAction @actionParameters - $settingArgs = @{ - DisallowDemandStart = $DisallowDemandStart - DisallowHardTerminate = $DisallowHardTerminate - Compatibility = $Compatibility - AllowStartIfOnBatteries = $AllowStartIfOnBatteries - Disable = -not $Enable - Hidden = $Hidden - RunOnlyIfIdle = $RunOnlyIfIdle + $settingParameters = @{ + DisallowDemandStart = $DisallowDemandStart + DisallowHardTerminate = $DisallowHardTerminate + Compatibility = $Compatibility + AllowStartIfOnBatteries = $AllowStartIfOnBatteries + Disable = -not $Enable + Hidden = $Hidden + RunOnlyIfIdle = $RunOnlyIfIdle DisallowStartOnRemoteAppSession = $DisallowStartOnRemoteAppSession - StartWhenAvailable = $StartWhenAvailable - DontStopIfGoingOnBatteries = $DontStopIfGoingOnBatteries - WakeToRun = $WakeToRun - RestartOnIdle = $RestartOnIdle - DontStopOnIdleEnd = $DontStopOnIdleEnd - MultipleInstances = $MultipleInstances - Priority = $Priority - RestartCount = $RestartCount - RunOnlyIfNetworkAvailable = $RunOnlyIfNetworkAvailable + StartWhenAvailable = $StartWhenAvailable + DontStopIfGoingOnBatteries = $DontStopIfGoingOnBatteries + WakeToRun = $WakeToRun + RestartOnIdle = $RestartOnIdle + DontStopOnIdleEnd = $DontStopOnIdleEnd + MultipleInstances = $MultipleInstances + Priority = $Priority + RestartCount = $RestartCount + RunOnlyIfNetworkAvailable = $RunOnlyIfNetworkAvailable } - if ($IdleDuration.TimeOfDay -gt [System.TimeSpan] '00:00:00') + if ($IdleDuration -gt [System.TimeSpan] '00:00:00') { - $settingArgs.Add('IdleDuration', $IdleDuration.TimeOfDay) + $settingParameters.Add('IdleDuration', $IdleDuration) } - if ($IdleWaitTimeout.TimeOfDay -gt [System.TimeSpan] '00:00:00') + if ($IdleWaitTimeout -gt [System.TimeSpan] '00:00:00') { - $settingArgs.Add('IdleWaitTimeout', $IdleWaitTimeout.TimeOfDay) + $settingParameters.Add('IdleWaitTimeout', $IdleWaitTimeout) } - if ($ExecutionTimeLimit.TimeOfDay -gt [System.TimeSpan] '00:00:00') + if ($ExecutionTimeLimit -gt [System.TimeSpan] '00:00:00') { - $settingArgs.Add('ExecutionTimeLimit', $ExecutionTimeLimit.TimeOfDay) + $settingParameters.Add('ExecutionTimeLimit', $ExecutionTimeLimit) } - if ($RestartInterval.TimeOfDay -gt [System.TimeSpan] '00:00:00') + if ($RestartInterval -gt [System.TimeSpan] '00:00:00') { - $settingArgs.Add('RestartInterval', $RestartInterval.TimeOfDay) + $settingParameters.Add('RestartInterval', $RestartInterval) } if (-not [System.String]::IsNullOrWhiteSpace($NetworkName)) { - $setting.Add('NetworkName', $NetworkName) + $settingParameters.Add('NetworkName', $NetworkName) } - $setting = New-ScheduledTaskSettingsSet @settingArgs - $triggerArgs = @{} - if ($RandomDelay.TimeOfDay -gt [System.TimeSpan]::FromSeconds(0)) + $setting = New-ScheduledTaskSettingsSet @settingParameters + + $triggerParameters = @{} + + if ($RandomDelay -gt [System.TimeSpan]::FromSeconds(0)) { - $triggerArgs.Add('RandomDelay', $RandomDelay.TimeOfDay) + $triggerParameters.Add('RandomDelay', $RandomDelay) } switch ($ScheduleType) { 'Once' { - $triggerArgs.Add('Once', $true) - $triggerArgs.Add('At', $StartTime) + $triggerParameters.Add('Once', $true) + $triggerParameters.Add('At', $StartTime) break } + 'Daily' { - $triggerArgs.Add('Daily', $true) - $triggerArgs.Add('At', $StartTime) - $triggerArgs.Add('DaysInterval', $DaysInterval) + $triggerParameters.Add('Daily', $true) + $triggerParameters.Add('At', $StartTime) + $triggerParameters.Add('DaysInterval', $DaysInterval) break } + 'Weekly' { - $triggerArgs.Add('Weekly', $true) - $triggerArgs.Add('At', $StartTime) + $triggerParameters.Add('Weekly', $true) + $triggerParameters.Add('At', $StartTime) if ($DaysOfWeek.Count -gt 0) { - $triggerArgs.Add('DaysOfWeek', $DaysOfWeek) + $triggerParameters.Add('DaysOfWeek', $DaysOfWeek) } if ($WeeksInterval -gt 0) { - $triggerArgs.Add('WeeksInterval', $WeeksInterval) + $triggerParameters.Add('WeeksInterval', $WeeksInterval) } break } + 'AtStartup' { - $triggerArgs.Add('AtStartup', $true) + $triggerParameters.Add('AtStartup', $true) break } + 'AtLogOn' { - $triggerArgs.Add('AtLogOn', $true) + $triggerParameters.Add('AtLogOn', $true) if (-not [System.String]::IsNullOrWhiteSpace($User)) { - $triggerArgs.Add('User', $User) + $triggerParameters.Add('User', $User) } break } } - $trigger = New-ScheduledTaskTrigger @triggerArgs -ErrorAction SilentlyContinue + $trigger = New-ScheduledTaskTrigger @triggerParameters -ErrorAction SilentlyContinue + if (-not $trigger) { - New-InvalidOperationException -Message 'Error creating new scheduled task trigger' -ErrorRecord $_ + $exceptionMessage = 'Error creating new scheduled task trigger.' + New-InvalidOperationException -Message $exceptionMessage -ErrorRecord $_ } - # To overcome the issue of not being able to set the task repetition for tasks with a schedule type other than Once - if ($RepeatInterval.TimeOfDay -gt (New-TimeSpan -Seconds 0) -and $PSVersionTable.PSVersion.Major -gt 4) + if ($RepeatInterval -gt [System.TimeSpan]::Parse('0:0:0')) { - if ($RepetitionDuration.TimeOfDay -le $RepeatInterval.TimeOfDay) + # A repetition pattern is required so create it and attach it to the trigger object + Write-Verbose -Message ('Configuring trigger repetition.') + + if ($RepetitionDuration -le $RepeatInterval) { - $exceptionMessage = 'Repetition interval is set to {0} but repetition duration is {1}' -f $RepeatInterval.TimeOfDay, $RepetitionDuration.TimeOfDay + $exceptionMessage = 'Repetition interval is set to {0} but repetition duration is {1}.' -f $RepeatInterval, $RepetitionDuration New-InvalidArgumentException -Message $exceptionMessage -ArgumentName RepetitionDuration } - $tempTrigger = New-ScheduledTaskTrigger -Once -At 6:6:6 -RepetitionInterval $RepeatInterval.TimeOfDay -RepetitionDuration $RepetitionDuration.TimeOfDay - Write-Verbose -Message 'PS V5 Copying values from temporary trigger to property Repetition of $trigger.Repetition' - - try - { - $trigger.Repetition = $tempTrigger.Repetition + $tempTriggerParameters = @{ + Once = $true + At = '6:6:6' + RepetitionInterval = $RepeatInterval } - catch + + Write-Verbose -Message ('Creating MSFT_TaskRepetitionPattern CIM instance to configure repetition in trigger.') + + switch ($trigger.GetType().FullName) { - $triggerRepetitionFailed = $true + 'Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger' + { + # This is the type of trigger object returned in Windows Server 2012 R2/Windows 8.1 and below + Write-Verbose -Message ('Creating temporary task and trigger to get MSFT_TaskRepetitionPattern CIM instance.') + + $tempTriggerParameters.Add('RepetitionDuration', $RepetitionDuration) + + # Create a temporary trigger and task and copy the repetition CIM object from the temporary task + $tempTrigger = New-ScheduledTaskTrigger @tempTriggerParameters + $tempTask = New-ScheduledTask -Action $action -Trigger $tempTrigger + + # Store the repetition settings + $repetition = $tempTask.Triggers[0].Repetition + } + + 'Microsoft.Management.Infrastructure.CimInstance' + { + # This is the type of trigger object returned in Windows Server 2016/Windows 10 and above + Write-Verbose -Message ('Creating temporary trigger to get MSFT_TaskRepetitionPattern CIM instance.') + + if ($RepetitionDuration -gt [System.TimeSpan]::Parse('0:0:0') -and $RepetitionDuration -lt [System.TimeSpan]::MaxValue) + { + $tempTriggerParameters.Add('RepetitionDuration', $RepetitionDuration) + } + + # Create a temporary trigger and copy the repetition CIM object from it to the actual trigger + $tempTrigger = New-ScheduledTaskTrigger @tempTriggerParameters + + # Store the repetition settings + $repetition = $tempTrigger.Repetition + } + + default + { + New-InvalidOperationException ` + -Message ('Trigger object that was created was of unexpected type {0}.' -f $trigger.GetType().FullName) + } } } if ($currentValues.Ensure -eq 'Present') { - Write-Verbose -Message ('Removing previous scheduled task {0}' -f $TaskName) + Write-Verbose -Message ('Removing previous scheduled task {0}.' -f $TaskName) $null = Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$false } - Write-Verbose -Message ('Creating new scheduled task {0}' -f $TaskName) + Write-Verbose -Message ('Creating new scheduled task {0}.' -f $TaskName) $scheduledTask = New-ScheduledTask -Action $action -Trigger $trigger -Settings $setting - if ($RepeatInterval.TimeOfDay -gt (New-TimeSpan -Seconds 0) -and ($PSVersionTable.PSVersion.Major -eq 4 -or $triggerRepetitionFailed)) + if ($repetition) { - if ($RepetitionDuration.TimeOfDay -le $RepeatInterval.TimeOfDay) - { - $exceptionMessage = 'Repetition interval is set to {0} but repetition duration is {1}' -f $RepeatInterval.TimeOfDay, $RepetitionDuration.TimeOfDay - New-InvalidArgumentException -Message $exceptionMessage -ArgumentName RepetitionDuration - } - - $tempTrigger = New-ScheduledTaskTrigger -Once -At 6:6:6 -RepetitionInterval $RepeatInterval.TimeOfDay -RepetitionDuration $RepetitionDuration.TimeOfDay - $tempTask = New-ScheduledTask -Trigger $tempTrigger -Action $action - Write-Verbose -Message 'PS V4 Copying values from temporary trigger to property Repetition of $trigger.Repetition' - - $scheduledTask.Triggers[0].Repetition = $tempTask.Triggers[0].Repetition + Write-Verbose -Message ('Setting repetition trigger settings on task {0}.' -f $TaskName) + $scheduledTask.Triggers[0].Repetition = $repetition } if (-not [System.String]::IsNullOrWhiteSpace($Description)) @@ -1028,116 +1022,178 @@ function Set-TargetResource $scheduledTask.Description = $Description } - $registerArgs = @{ - TaskName = $TaskName - TaskPath = $TaskPath + $registerArguments = @{ + TaskName = $TaskName + TaskPath = $TaskPath InputObject = $scheduledTask } if ($PSBoundParameters.ContainsKey('ExecuteAsCredential') -eq $true) { - $registerArgs.Add('User', $ExecuteAsCredential.UserName) - $registerArgs.Add('Password', $ExecuteAsCredential.GetNetworkCredential().Password) + $registerArguments.Add('User', $ExecuteAsCredential.UserName) + $registerArguments.Add('Password', $ExecuteAsCredential.GetNetworkCredential().Password) } else { - $registerArgs.Add('User', 'NT AUTHORITY\SYSTEM') + $registerArguments.Add('User', 'NT AUTHORITY\SYSTEM') } - $null = Register-ScheduledTask @registerArgs + Write-Verbose -Message ('Registering the scheduled task {0}.' -f $TaskName) + + $null = Register-ScheduledTask @registerArguments } if ($Ensure -eq 'Absent') { - Write-Verbose -Message ('Removing scheduled task {0}' -f $TaskName) + Write-Verbose -Message ('Removing the scheduled task {0}.' -f $TaskName) + Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$false } } <# -.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 -.PARAMETER ActionExecutable - The path to the .exe for this task -.PARAMETER ActionArguments - The arguments to pass the executable -.PARAMETER ActionWorkingPath - The working path to specify for the executable -.PARAMETER ScheduleType - When should the task be executed -.PARAMETER RepeatInterval - How many units (minutes, hours, days) between each run of this task? -.PARAMETER StartTime - The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks -.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 -.PARAMETER ExecuteAsCredential - The credential this task should execute as. If not specified defaults to running as the local system account -.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. -.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. -.PARAMETER RepetitionDuration - Specifies how long the repetition pattern repeats after the task starts. -.PARAMETER DaysOfWeek - Specifies an array of the days of the week on which Task Scheduler runs the task. -.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. -.PARAMETER User - Specifies the identifier of the user for a trigger that starts a task when a user logs on. -.PARAMETER DisallowDemandStart - Indicates whether the task is prohibited to run on demand or not. Defaults to $false -.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. -.PARAMETER AllowStartIfOnBatteries - Indicates whether the task should start if the machine is on batteries or not. Defaults to $false -.PARAMETER Hidden - Indicates that the task is hidden in the Task Scheduler UI. -.PARAMETER RunOnlyIfIdle - Indicates that Task Scheduler runs the task only when the computer is idle. -.PARAMETER IdleWaitTimeout - Specifies the amount of time that Task Scheduler waits for an idle condition to occur. -.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. -.PARAMETER DisallowStartOnRemoteAppSession - Indicates that the task does not start if the task is triggered to run in a Remote Applications Integrated Locally (RAIL) session. -.PARAMETER StartWhenAvailable - Indicates that Task Scheduler can start the task at any time after its scheduled time has passed. -.PARAMETER DontStopIfGoingOnBatteries - Indicates that the task does not stop if the computer switches to battery power. -.PARAMETER WakeToRun - Indicates that Task Scheduler wakes the computer before it runs the task. -.PARAMETER IdleDuration - Specifies the amount of time that the computer must be in an idle state before Task Scheduler runs the task. -.PARAMETER RestartOnIdle - Indicates that Task Scheduler restarts the task when the computer cycles into an idle condition more than once. -.PARAMETER DontStopOnIdleEnd - Indicates that Task Scheduler does not terminate the task if the idle condition ends before the task is completed. -.PARAMETER ExecutionTimeLimit - Specifies the amount of time that Task Scheduler is allowed to complete the task. -.PARAMETER MultipleInstances - Specifies the policy that defines how Task Scheduler handles multiple instances of the task. -.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. -.PARAMETER RestartCount - Specifies the number of times that Task Scheduler attempts to restart the task. -.PARAMETER RestartInterval - Specifies the amount of time that Task Scheduler attempts to restart the task. -.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. + .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 + + .PARAMETER ActionExecutable + The path to the .exe for this task + + .PARAMETER ActionArguments + The arguments to pass the executable + + .PARAMETER ActionWorkingPath + The working path to specify for the executable + + .PARAMETER ScheduleType + When should the task be executed + + .PARAMETER RepeatInterval + How many units (minutes, hours, days) between each run of this task? + + .PARAMETER StartTime + The time of day this task should start at - defaults to 12:00 AM. Not valid for + AtLogon and AtStartup tasks + + .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 + + .PARAMETER ExecuteAsCredential + The credential this task should execute as. If not specified defaults to running + as the local system account + + .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. + + .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. + + .PARAMETER RepetitionDuration + Specifies how long the repetition pattern repeats after the task starts. + + .PARAMETER DaysOfWeek + Specifies an array of the days of the week on which Task Scheduler runs the task. + + .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. + + .PARAMETER User + Specifies the identifier of the user for a trigger that starts a task when a + user logs on. + + .PARAMETER DisallowDemandStart + Indicates whether the task is prohibited to run on demand or not. Defaults + to $false + + .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. + + .PARAMETER AllowStartIfOnBatteries + Indicates whether the task should start if the machine is on batteries or not. + Defaults to $false + + .PARAMETER Hidden + Indicates that the task is hidden in the Task Scheduler UI. + + .PARAMETER RunOnlyIfIdle + Indicates that Task Scheduler runs the task only when the computer is idle. + + .PARAMETER IdleWaitTimeout + Specifies the amount of time that Task Scheduler waits for an idle condition to occur. + + .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. + + .PARAMETER DisallowStartOnRemoteAppSession + Indicates that the task does not start if the task is triggered to run in a Remote + Applications Integrated Locally (RAIL) session. + + .PARAMETER StartWhenAvailable + Indicates that Task Scheduler can start the task at any time after its scheduled + time has passed. + + .PARAMETER DontStopIfGoingOnBatteries + Indicates that the task does not stop if the computer switches to battery power. + + .PARAMETER WakeToRun + Indicates that Task Scheduler wakes the computer before it runs the task. + + .PARAMETER IdleDuration + Specifies the amount of time that the computer must be in an idle state before + Task Scheduler runs the task. + + .PARAMETER RestartOnIdle + Indicates that Task Scheduler restarts the task when the computer cycles into an + idle condition more than once. + + .PARAMETER DontStopOnIdleEnd + Indicates that Task Scheduler does not terminate the task if the idle condition + ends before the task is completed. + + .PARAMETER ExecutionTimeLimit + Specifies the amount of time that Task Scheduler is allowed to complete the task. + + .PARAMETER MultipleInstances + Specifies the policy that defines how Task Scheduler handles multiple instances + of the task. + + .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. + + .PARAMETER RestartCount + Specifies the number of times that Task Scheduler attempts to restart the task. + + .PARAMETER RestartInterval + Specifies the amount of time that Task Scheduler attempts to restart the task. + + .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. #> function Test-TargetResource { @@ -1175,8 +1231,8 @@ function Test-TargetResource $ScheduleType, [Parameter()] - [System.DateTime] - $RepeatInterval = [System.DateTime] '00:00:00', + [System.String] + $RepeatInterval = '00:00:00', [Parameter()] [System.DateTime] @@ -1200,12 +1256,12 @@ function Test-TargetResource $DaysInterval = 1, [Parameter()] - [System.DateTime] - $RandomDelay = [System.DateTime] '00:00:00', + [System.String] + $RandomDelay = '00:00:00', [Parameter()] - [System.DateTime] - $RepetitionDuration = [System.DateTime] '00:00:00', + [System.String] + $RepetitionDuration = '00:00:00', [Parameter()] [System.String[]] @@ -1245,8 +1301,8 @@ function Test-TargetResource $RunOnlyIfIdle = $false, [Parameter()] - [System.DateTime] - $IdleWaitTimeout = [System.DateTime] '02:00:00', + [System.String] + $IdleWaitTimeout = '02:00:00', [Parameter()] [System.String] @@ -1269,8 +1325,8 @@ function Test-TargetResource $WakeToRun = $false, [Parameter()] - [System.DateTime] - $IdleDuration = [System.DateTime] '01:00:00', + [System.String] + $IdleDuration = '01:00:00', [Parameter()] [System.Boolean] @@ -1281,8 +1337,8 @@ function Test-TargetResource $DontStopOnIdleEnd = $false, [Parameter()] - [System.DateTime] - $ExecutionTimeLimit = [System.DateTime] '8:00:00', + [System.String] + $ExecutionTimeLimit = '08:00:00', [Parameter()] [ValidateSet('IgnoreNew', 'Parallel', 'Queue')] @@ -1298,8 +1354,8 @@ function Test-TargetResource $RestartCount = 0, [Parameter()] - [System.DateTime] - $RestartInterval = [System.DateTime] '00:00:00', + [System.String] + $RestartInterval = '00:00:00', [Parameter()] [System.Boolean] @@ -1308,38 +1364,81 @@ function Test-TargetResource $TaskPath = ConvertTo-NormalizedTaskPath -TaskPath $TaskPath + # Convert the strings containing time spans to TimeSpan Objects + if ($PSBoundParameters.ContainsKey('RepeatInterval')) + { + $PSBoundParameters['RepeatInterval'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RepeatInterval).ToString() + } + + if ($PSBoundParameters.ContainsKey('RandomDelay')) + { + $PSBoundParameters['RandomDelay'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RandomDelay).ToString() + } + + if ($PSBoundParameters.ContainsKey('RepetitionDuration')) + { + $RepetitionDuration = ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RepetitionDuration -AllowIndefinitely + if ($RepetitionDuration -eq [System.TimeSpan]::MaxValue) + { + $PSBoundParameters['RepetitionDuration'] = 'Indefinitely' + } + else + { + $PSBoundParameters['RepetitionDuration'] = $RepetitionDuration.ToString() + } + + } + + if ($PSBoundParameters.ContainsKey('IdleWaitTimeout')) + { + $PSBoundParameters['IdleWaitTimeout'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $IdleWaitTimeout).ToString() + } + + if ($PSBoundParameters.ContainsKey('IdleDuration')) + { + $PSBoundParameters['IdleDuration'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $IdleDuration).ToString() + } + + if ($PSBoundParameters.ContainsKey('ExecutionTimeLimit')) + { + $PSBoundParameters['ExecutionTimeLimit'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $ExecutionTimeLimit).ToString() + } + + if ($PSBoundParameters.ContainsKey('RestartInterval')) + { + $PSBoundParameters['RestartInterval'] = (ConvertTo-TimeSpanFromTimeSpanString -TimeSpanString $RestartInterval).ToString() + } + Write-Verbose -Message ('Testing scheduled task {0}' -f $TaskName) - $CurrentValues = Get-TargetResource @PSBoundParameters + $currentValues = Get-TargetResource @PSBoundParameters Write-Verbose -Message 'Current values retrieved' - if ($Ensure -eq 'Absent' -and $CurrentValues.Ensure -eq 'Absent') + if ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Absent') { return $true } - if ($null -eq $CurrentValues) + if ($null -eq $currentValues) { - Write-Verbose -Message 'Current values were null' + Write-Verbose -Message 'Current values were null.' return $false } $desiredValues = $PSBoundParameters $desiredValues.TaskPath = $TaskPath - - Write-Verbose -Message 'Testing DSC parameter state' - return Test-DscParameterState -CurrentValues $CurrentValues -DesiredValues $desiredValues + Write-Verbose -Message 'Testing DSC parameter state.' + return Test-DscParameterState -CurrentValues $currentValues -DesiredValues $desiredValues } <# -.SYNOPSIS -Helper function to convert TaskPath to the right form + .SYNOPSIS + Helper function to convert TaskPath to the right form -.PARAMETER TaskPath -The path to the task + .PARAMETER TaskPath + The path to the task #> - function ConvertTo-NormalizedTaskPath { [CmdletBinding()] @@ -1359,3 +1458,102 @@ function ConvertTo-NormalizedTaskPath return $TaskPath } + +<# + .SYNOPSIS + Helper function convert a standard timespan string + into a TimeSpan object. It can support returning the + maximum timespan if the AllowIndefinitely switch is set + and the timespan is set to 'indefinte'. + + .PARAMETER TimeSpan + The standard timespan string to convert to a TimeSpan + object. + + .PARAMETER AllowIndefinitely + Allow the keyword 'Indefinitely' to be translated into + the maximum valid timespan. +#> +function ConvertTo-TimeSpanFromTimeSpanString +{ + [CmdletBinding()] + [OutputType([System.TimeSpan])] + param + ( + [Parameter()] + [System.String] + $TimeSpanString = '00:00:00', + + [Parameter()] + [Switch] + $AllowIndefinitely + ) + + if ($AllowIndefinitely -eq $True -and $TimeSpanString -eq 'Indefinitely') + { + return [System.TimeSpan]::MaxValue + } + + return [System.TimeSpan]::Parse($TimeSpanString) +} + +<# + .SYNOPSIS + Helper function convert a task schedule timespan string + into a TimeSpan string. If AllowIndefinitely is set to + true and the TimeSpan string is empty then return + 'Indefinitely'. + + .PARAMETER TimeSpan + The scheduled task timespan string to convert to a TimeSpan + string. + + .PARAMETER AllowIndefinitely + Allow an empty TimeSpan to return the keyword 'Indefinitely'. + +#> +function ConvertTo-TimeSpanStringFromScheduledTaskString +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.String] + $TimeSpan, + + [Parameter()] + [Switch] + $AllowIndefinitely + ) + + # If AllowIndefinitely is true and the timespan is empty then return Indefinitely + if ($AllowIndefinitely -eq $true -and [String]::IsNullOrEmpty($TimeSpan)) + { + return 'Indefinitely' + } + + $days = $hours = $minutes = $seconds = 0 + + if ($TimeSpan -match 'P(?\d{0,3})D') + { + $days = $matches.Days + } + + if ($TimeSpan -match '(?\d{0,2})H') + { + $hours = $matches.Hours + } + + if ($TimeSpan -match '(?\d{0,2})M') + { + $minutes = $matches.Minutes + } + + if ($TimeSpan -match '(?\d{0,2})S') + { + $seconds = $matches.Seconds + } + + return (New-TimeSpan -Days $days -Hours $hours -Minutes $minutes -Seconds $seconds).ToString() +} diff --git a/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.schema.mof b/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.schema.mof index 3f1e1a69..3b23f8f1 100644 --- a/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.schema.mof +++ b/DSCResources/MSFT_xScheduledTask/MSFT_xScheduledTask.schema.mof @@ -8,14 +8,14 @@ class MSFT_xScheduledTask : OMI_BaseResource [Write, Description("The arguments to pass the executable")] string ActionArguments; [Write, Description("The working path to specify for the executable")] string ActionWorkingPath; [Required, Description("When should the task be executed"), ValueMap{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn"}, Values{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn"}] string ScheduleType; - [Write, Description("How many units (minutes, hours, days) between each run of this task?")] DateTime RepeatInterval; + [Write, Description("How many units (minutes, hours, days) between each run of this task?")] String RepeatInterval; [Write, Description("The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks")] DateTime StartTime; [Write, Description("Present if the task should exist, Absent if it should be removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; [Write, Description("True if the task should be enabled, false if it should be disabled")] boolean Enable; [Write, Description("The credential this task should execute as. If not specified defaults to running as the local system account"), EmbeddedInstance("MSFT_Credential")] string ExecuteAsCredential; [Write, Description("Specifies the interval between the days in the schedule. An interval of 1 produces a daily schedule. An interval of 2 produces an every-other day schedule.")] Uint32 DaysInterval; - [Write, Description("Specifies a random amount of time to delay the start time of the trigger. The delay time is a random time between the time the task triggers and the time that you specify in this setting.")] DateTime RandomDelay; - [Write, Description("Specifies how long the repetition pattern repeats after the task starts.")] DateTime RepetitionDuration; + [Write, Description("Specifies a random amount of time to delay the start time of the trigger. The delay time is a random time between the time the task triggers and the time that you specify in this setting.")] String RandomDelay; + [Write, Description("Specifies how long the repetition pattern repeats after the task starts. May be set to `Indefinitely` to specify an indefinite duration.")] String RepetitionDuration; [Write, Description("Specifies an array of the days of the week on which Task Scheduler runs the task.")] String DaysOfWeek[]; [Write, Description("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.")] Uint32 WeeksInterval; [Write, Description("Specifies the identifier of the user for a trigger that starts a task when a user logs on.")] String User; @@ -25,19 +25,19 @@ class MSFT_xScheduledTask : OMI_BaseResource [Write, Description("Indicates whether the task should start if the machine is on batteries or not. Defaults to $false")] Boolean AllowStartIfOnBatteries; [Write, Description("Indicates that the task is hidden in the Task Scheduler UI.")] Boolean Hidden; [Write, Description("Indicates that Task Scheduler runs the task only when the computer is idle.")] Boolean RunOnlyIfIdle; - [Write, Description("Specifies the amount of time that Task Scheduler waits for an idle condition to occur.")] DateTime IdleWaitTimeout; + [Write, Description("Specifies the amount of time that Task Scheduler waits for an idle condition to occur.")] String IdleWaitTimeout; [Write, Description("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.")] String NetworkName; [Write, Description("Indicates that the task does not start if the task is triggered to run in a Remote Applications Integrated Locally (RAIL) session.")] Boolean DisallowStartOnRemoteAppSession; [Write, Description("Indicates that Task Scheduler can start the task at any time after its scheduled time has passed.")] Boolean StartWhenAvailable; [Write, Description("Indicates that the task does not stop if the computer switches to battery power.")] Boolean DontStopIfGoingOnBatteries; [Write, Description("Indicates that Task Scheduler wakes the computer before it runs the task.")] Boolean WakeToRun; - [Write, Description("Specifies the amount of time that the computer must be in an idle state before Task Scheduler runs the task.")] DateTime IdleDuration; + [Write, Description("Specifies the amount of time that the computer must be in an idle state before Task Scheduler runs the task.")] String IdleDuration; [Write, Description("Indicates that Task Scheduler restarts the task when the computer cycles into an idle condition more than once.")] Boolean RestartOnIdle; [Write, Description("Indicates that Task Scheduler does not terminate the task if the idle condition ends before the task is completed.")] Boolean DontStopOnIdleEnd; - [Write, Description("Specifies the amount of time that Task Scheduler is allowed to complete the task.")] DateTime ExecutionTimeLimit; + [Write, Description("Specifies the amount of time that Task Scheduler is allowed to complete the task.")] String ExecutionTimeLimit; [Write, Description("Specifies the policy that defines how Task Scheduler handles multiple instances of the task."), ValueMap{"IgnoreNew","Parallel","Queue"}, Values{"IgnoreNew","Parallel","Queue"}] String MultipleInstances; [Write, Description("Specifies the priority level of the task. Priority must be an integer from 0 (highest priority) to 10 (lowest priority). The default value is 7. Priority levels 7 and 8 are used for background tasks. Priority levels 4, 5, and 6 are used for interactive tasks.")] Uint32 Priority; [Write, Description("Specifies the number of times that Task Scheduler attempts to restart the task.")] Uint32 RestartCount; - [Write, Description("Specifies the amount of time that Task Scheduler attempts to restart the task.")] DateTime RestartInterval; + [Write, Description("Specifies the amount of time that Task Scheduler attempts to restart the task.")] String RestartInterval; [Write, Description("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.")] Boolean RunOnlyIfNetworkAvailable; }; diff --git a/DSCResources/MSFT_xVirtualMemory/MSFT_xVirtualMemory.psm1 b/DSCResources/MSFT_xVirtualMemory/MSFT_xVirtualMemory.psm1 index 78515d07..3ab64a36 100644 --- a/DSCResources/MSFT_xVirtualMemory/MSFT_xVirtualMemory.psm1 +++ b/DSCResources/MSFT_xVirtualMemory/MSFT_xVirtualMemory.psm1 @@ -3,6 +3,10 @@ param ( ) +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xVirtualMemory' + <# .SYNOPSIS Returns the current state of the virtual memory configuration @@ -29,7 +33,7 @@ function Get-TargetResource $Type ) - Write-Verbose -Message 'Getting current page file settings' + Write-Verbose -Message ($script:localizedData.GettingVirtualMemoryMessage) $returnValue = @{ Drive = [string]::Empty @@ -38,7 +42,7 @@ function Get-TargetResource MaximumSize = 0 } - [bool] $isSystemManaged = (Get-CimInstance -ClassName Win32_ComputerSystem).AutomaticManagedPagefile + [System.Boolean] $isSystemManaged = (Get-CimInstance -ClassName 'Win32_ComputerSystem').AutomaticManagedPagefile if ($isSystemManaged) { @@ -46,33 +50,32 @@ function Get-TargetResource return $returnValue } - $driveItem = [System.IO.DriveInfo] $Drive - - Write-Verbose -Message "Pagefile was not automatically managed. Retrieving detailed page file settings with query Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveItem.Name.Substring(0,2))'" + $driveInfo = [System.IO.DriveInfo] $Drive - # Find existing page file settings by drive letter - $virtualMemoryInstance = Get-CimInstance -Namespace root\cimv2 -Query "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveItem.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) - if (-not $virtualMemoryInstance) + if (-not $existingPageFileSetting) { $returnValue.Type = 'NoPagingFile' - return $returnValue - } - - if ($virtualMemoryInstance.InitialSize -eq 0 -and $virtualMemoryInstance.MaximumSize -eq 0) - { - $returnValue.Type = 'SystemManagedSize' } else { - $returnValue.Type = 'CustomSize' - } + if ($existingPageFileSetting.InitialSize -eq 0 -and $existingPageFileSetting.MaximumSize -eq 0) + { + $returnValue.Type = 'SystemManagedSize' + } + else + { + $returnValue.Type = 'CustomSize' + } - $returnValue.Drive = $virtualMemoryInstance.Name.Substring(0, 3) - $returnValue.InitialSize = $virtualMemoryInstance.InitialSize - $returnValue.MaximumSize = $virtualMemoryInstance.MaximumSize + $returnValue.Drive = $existingPageFileSetting.Name.Substring(0, 3) + $returnValue.InitialSize = $existingPageFileSetting.InitialSize + $returnValue.MaximumSize = $existingPageFileSetting.MaximumSize + } - $returnValue + return $returnValue } <# @@ -114,174 +117,114 @@ function Set-TargetResource $MaximumSize ) - Write-Verbose -Message 'Setting page file' + Write-Verbose -Message ($script:localizedData.SettingVirtualMemoryMessage) - $SystemInfo = Get-CimInstance -Class Win32_ComputerSystem + $systemInfo = Get-CimInstance -ClassName 'Win32_ComputerSystem' switch ($Type) { 'AutoManagePagingFile' { - $setParams = @{ - Namespace = 'root\cimv2' - Query = 'Select * from Win32_ComputerSystem' - Property = @{AutomaticManagedPageFile = $true} - } - - Write-Verbose -Message 'Enabling AutoManagePagingFile' + Set-AutoManagePaging -State Enable - $null = Set-CimInstance @setParams $global:DSCMachineStatus = 1 + break } 'CustomSize' { - if ($SystemInfo.AutomaticManagedPageFile) - { - # First set AutomaticManagedPageFile to $false to be able to set a custom one later - - $setParams = @{ - Namespace = 'root\cimv2' - Query = 'Select * from Win32_ComputerSystem' - Property = @{AutomaticManagedPageFile = $false} - } - - Write-Verbose -Message 'Disabling AutoManagePagingFile' - - $null = Set-CimInstance @setParams - } - - $driveInfo = [System.IO.DriveInfo] $Drive - - if (-not $driveInfo.IsReady) + if ($systemInfo.AutomaticManagedPageFile) { - throw "Drive $($driveInfo.Name) is not ready. Please ensure that the drive exists and is available" + # First Disable Automatic Managed Page File + Set-AutoManagePaging -State Disable } - $pageFileName = Join-Path -Path $driveInfo.Name -ChildPath 'pagefile.sys' + $driveInfo = Get-DriveInfo -Drive $Drive - Write-Verbose -Message ('Checking if a paging file already exists at {0}' -f $pageFileName) - $existingPageFileSetting = Get-CimInstance ` - -Namespace root\cimv2 ` - -Query "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) if (-not $existingPageFileSetting) { - $null = New-CimInstance -Namespace 'root\cimv2' -ClassName 'Win32_PageFileSetting' -Property @{Name = $pageFileName} - } + $pageFileName = Join-Path ` + -Path $driveInfo.Name ` + -ChildPath 'pagefile.sys' - <# - New-CimInstance does not support properties InitialSize and MaximumSize. Therefore, create - a New-CimInstance with the page file name only if it does not exist and Set-CimInstance on the instance - #> - $setParams = @{ - Namespace = 'root\cimv2' - Query = "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" - Property = @{ - InitialSize = $InitialSize - MaximumSize = $MaximumSize - } + New-PageFile -PageFileName $pageFileName } - Write-Verbose -Message ("Setting page file to {0}. Initial size {1}MB, maximum size {2}MB" -f $pageFileName, $InitialSize, $MaximumSize) + Set-PageFileSetting ` + -Drive $driveInfo.Name.Substring(0,2) ` + -InitialSize $InitialSize ` + -MaximumSize $MaximumSize - $null = Set-CimInstance @setParams $global:DSCMachineStatus = 1 + + Write-Verbose -Message ($script:localizedData.EnabledCustomSizeMessage -f $Drive) + break } 'SystemManagedSize' { - if ($SystemInfo.AutomaticManagedPageFile) - { - $setParams = @{ - Namespace = 'root\cimv2' - Query = 'Select * from Win32_ComputerSystem' - Property = @{AutomaticManagedPageFile = $false} - } - - Write-Verbose -Message 'Disabling AutoManagePagingFile' - - $null = Set-CimInstance @setParams - } - - $driveInfo = [System.IO.DriveInfo] $Drive - - if (-not $driveInfo.IsReady) + if ($systemInfo.AutomaticManagedPageFile) { - throw "Drive $($driveInfo.Name) is not ready. Please ensure that the drive exists and is available" + # First Disable Automatic Managed Page File + Set-AutoManagePaging -State Disable } - $pageFileName = Join-Path -Path $driveInfo.Name -ChildPath 'pagefile.sys' - - Write-Verbose -Message ('Checking if a paging file already exists at {0}' -f $pageFileName) + $driveInfo = Get-DriveInfo -Drive $Drive - $existingPageFileSetting = Get-CimInstance ` - -Namespace root\cimv2 ` - -Query "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) if (-not $existingPageFileSetting) { - $null = New-CimInstance -Namespace 'root\cimv2' -ClassName 'Win32_PageFileSetting' -Property @{Name = $pageFileName} - } + $pageFileName = Join-Path ` + -Path $driveInfo.Name ` + -ChildPath 'pagefile.sys' - $setParams = @{ - Namespace = 'root\cimv2' - Query = "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" - Property = @{ - InitialSize = 0 - MaximumSize = 0 - } + New-PageFile -PageFileName $pageFileName } - Write-Verbose -Message "Enabling system-managed page file on $pageFileName" + Set-PageFileSetting ` + -Drive $driveInfo.Name.Substring(0,2) - $null = Set-CimInstance @setParams $global:DSCMachineStatus = 1 + + Write-Verbose -Message ($script:localizedData.EnabledSystemManagedSizeMessage -f $Drive) + break } 'NoPagingFile' { - if ($SystemInfo.AutomaticManagedPageFile) + if ($systemInfo.AutomaticManagedPageFile) { - $setParams = @{ - Namespace = 'root\cimv2' - Query = 'Select * from Win32_ComputerSystem' - Property = @{AutomaticManagedPageFile = $false} - } - - $null = Set-CimInstance @setParams + # First Disable Automatic Managed Page File + Set-AutoManagePaging -State Disable } - $driveInfo = [System.IO.DriveInfo] $Drive - - if (-not $driveInfo.IsReady) - { - throw "Drive $($driveInfo.Name) is not ready. Please ensure that the drive exists and is available" - } + $driveInfo = Get-DriveInfo -Drive $Drive - $existingPageFileSetting = Get-CimInstance ` - -Namespace root\cimv2 ` - -Query "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) if ($existingPageFileSetting) { - Write-Verbose -Message "Removing existing page file $($existingPageFileSetting.Name)" - $null = Remove-CimInstance -InputObject $existingPageFileSetting + Write-Verbose -Message ($script:localizedData.RemovePageFileMessage -f $existingPageFileSetting.Name) + + $null = Remove-CimInstance ` + -InputObject $existingPageFileSetting + $global:DSCMachineStatus = 1 } - Write-Verbose -Message "Disabled page file for drive $Drive" + Write-Verbose -Message ($script:localizedData.DisabledPageFileMessage -f $Drive) break } - - default - { - throw "A wrong type '$Type' has been selected." - } } } @@ -325,16 +268,16 @@ function Test-TargetResource $MaximumSize ) - Write-Verbose -Message 'Testing page file' + Write-Verbose -Message ($script:localizedData.TestingVirtualMemoryMessage) - $systemInfo = Get-CimInstance -Class Win32_ComputerSystem - $result = $false + $systemInfo = Get-CimInstance -ClassName 'Win32_ComputerSystem' + $inDesiredState = $false switch ($Type) { 'AutoManagePagingFile' { - $result = $systemInfo.AutomaticManagedPagefile + $inDesiredState = $systemInfo.AutomaticManagedPagefile break } @@ -342,87 +285,239 @@ function Test-TargetResource { if ($systemInfo.AutomaticManagedPageFile) { - $result = $false break } $driveInfo = [System.IO.DriveInfo] $Drive - $pageFile = Get-CimInstance -Class Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) - if (-not $pageFile) + if (-not $existingPageFileSetting) { - $result = $false break } - if (-not ($pageFile.InitialSize -eq $InitialSize -and $pageFile.MaximumSize -eq $MaximumSize)) + if (-not ($existingPageFileSetting.InitialSize -eq $InitialSize -and $existingPageFileSetting.MaximumSize -eq $MaximumSize)) { - $result = $false break } - $result = $true + $inDesiredState = $true break } 'SystemManagedSize' { - if ($SystemInfo.AutomaticManagedPageFile) + if ($systemInfo.AutomaticManagedPageFile) { - $result = $false break } $driveInfo = [System.IO.DriveInfo] $Drive - $pageFile = Get-CimInstance -Class Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) - if (-not $pageFile) + if (-not $existingPageFileSetting) { - $result = $false break } - if (-not ($pageFile.InitialSize -eq 0 -and $pageFile.MaximumSize -eq 0)) + if (-not ($existingPageFileSetting.InitialSize -eq 0 -and $existingPageFileSetting.MaximumSize -eq 0)) { - $result = $false break } - $result = $true + $inDesiredState = $true break } 'NoPagingFile' { - if ($SystemInfo.AutomaticManagedPageFile) + if ($systemInfo.AutomaticManagedPageFile) { - $result = $false break } $driveInfo = [System.IO.DriveInfo] $Drive - $pageFile = Get-CimInstance -Class Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveInfo.Name.Substring(0,2))'" + $existingPageFileSetting = Get-PageFileSetting ` + -Drive $($driveInfo.Name.Substring(0,2)) - if ($pageFile) + if ($existingPageFileSetting) { - $result = $false break } - $result = $true + $inDesiredState = $true break } + } - default - { - break + return $inDesiredState +} + +<# + .SYNOPSIS + Gets the settings for a page file assigned to a Drive. + + .PARAMETER State + The drive letter for the page file to return the settings of. +#> +function Get-PageFileSetting +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Drive + ) + + Write-Verbose -Message ($script:localizedData.GettingPageFileSettingsMessage -f $Drive) + + # Find existing page file settings by drive letter + return Get-CimInstance ` + -ClassName 'Win32_PageFileSetting' ` + -Filter "SettingID='pagefile.sys @ $Drive'" +} + +<# + .SYNOPSIS + Sets a new page file name. + + .PARAMETER Drive + The letter of the drive containing the page file + to change the settings of. + + .PARAMETER InitialSize + The initial size to set the page file to. + + .PARAMETER MaximumSize + The maximum size to set the page file to. +#> +function Set-PageFileSetting +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Drive, + + [Parameter()] + [System.Int64] + $InitialSize = 0, + + [Parameter()] + [System.Int64] + $MaximumSize = 0 + ) + + $setParams = @{ + Namespace = 'root\cimv2' + Query = "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $Drive'" + Property = @{ + InitialSize = $InitialSize + MaximumSize = $MaximumSize } } - return $result + Write-Verbose -Message ($script:localizedData.SettingPageFileSettingsMessage -f $Drive, $InitialSize, $MaximumSize) + + $null = Set-CimInstance @setParams +} + +<# + .SYNOPSIS + Enables or Disables Automatically Managed Paging. + + .PARAMETER State + Specifies if Automatically Managed Paging is enabled + or disabled. +#> +function Set-AutoManagePaging +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('Enable', 'Disable')] + [System.String] + $State + ) + + $setParams = @{ + Namespace = 'root\cimv2' + Query = 'Select * from Win32_ComputerSystem' + Property = @{ + AutomaticManagedPageFile = ($State -eq 'Enable') + } + } + + Write-Verbose -Message ($script:localizedData.SetAutoManagePagingMessage -f $State) + + $null = Set-CimInstance @setParams +} + +<# + .SYNOPSIS + Sets a new page file name. + + .PARAMETER PageFileName + The name of the new page file. +#> +function New-PageFile +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $PageFileName + ) + + Write-Verbose -Message ($script:localizedData.NewPageFileMessage -f $State) + + $null = New-CimInstance ` + -Namespace 'root\cimv2' ` + -ClassName 'Win32_PageFileSetting' ` + -Property @{ + Name = $PageFileName + } +} + +<# + .SYNOPSIS + Gets the Drive info object for a specified + Drive. It will throw an exception if the drive + is invalid or does not exist. + + .PARAMETER Drive + The letter of the drive to get the drive info + for. +#> +function Get-DriveInfo +{ + [CmdletBinding()] + [OutputType([System.IO.DriveInfo])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Drive + ) + + $driveInfo = [System.IO.DriveInfo] $Drive + + if (-not $driveInfo.IsReady) + { + New-InvalidOperationException ` + -Message ($script:localizedData.DriveNotReadyError -f $driveInfo.Name) + } + + return $driveInfo } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xVirtualMemory/en-US/MSFT_xVirtualMemory.strings.psd1 b/DSCResources/MSFT_xVirtualMemory/en-US/MSFT_xVirtualMemory.strings.psd1 new file mode 100644 index 00000000..46fad838 --- /dev/null +++ b/DSCResources/MSFT_xVirtualMemory/en-US/MSFT_xVirtualMemory.strings.psd1 @@ -0,0 +1,14 @@ +ConvertFrom-StringData @' + GettingVirtualMemoryMessage = Getting Virtual Memory. + SettingVirtualMemoryMessage = Setting Virtual Memory. + SetAutoManagePagingMessage = {0} automatically managed page file. + GettingPageFileSettingsMessage = Getting page file settings for drive {0}. + SettingPageFileSettingsMessage = Setting page file settings for drive {0} with initial size of {1}MB and maximum size {2}MB. + NewPageFileMessage = Creating new page file '{0}'. + RemovePageFileMessage = Removing existing page file '{0}'. + DisabledPageFileMessage = Disabled page file for drive {0}. + EnabledSystemManagedSizeMessage = Enabled system managed page file for drive {0}. + EnabledCustomSizeMessage = Enabled custom size page file for drive {0}. + DriveNotReadyError = Drive {0} is not ready. Please ensure that the drive exists and is available. + TestingVirtualMemoryMessage = Testing Virtual Memory. +'@ diff --git a/Examples/xComputer/6-SetComputerDescriptionInWorkgroup.ps1 b/Examples/xComputer/6-SetComputerDescriptionInWorkgroup.ps1 new file mode 100644 index 00000000..ef1de48c --- /dev/null +++ b/Examples/xComputer/6-SetComputerDescriptionInWorkgroup.ps1 @@ -0,0 +1,24 @@ +<# + .EXAMPLE + This example will set the computer description +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -Module xComputerManagement + + Node $NodeName + { + xComputer NewDescription + { + Name = 'localhost' + Description = 'This is my computer.' + } + } +} diff --git a/Examples/xScheduledTask/1-CreateScheduledTaskOnce.ps1 b/Examples/xScheduledTask/1-CreateScheduledTaskOnce.ps1 new file mode 100644 index 00000000..83ba84a8 --- /dev/null +++ b/Examples/xScheduledTask/1-CreateScheduledTaskOnce.ps1 @@ -0,0 +1,38 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Once' in the folder + task folder 'MyTasks' that starts a new powershell process once at 00:00 repeating + every 15 minutes for 8 hours. The task is delayed by a random amount up to 1 hour + each time. The task will run even if the previous task is still running and it + will prevent hard termintaing of the previously running task instance. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask xScheduledTaskOnceAdd + { + TaskName = 'Test task Once' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' + ActionWorkingPath = (Get-Location).Path + Enable = $true + RandomDelay = '01:00:00' + DisallowHardTerminate = $true + RunOnlyIfIdle = $false + Priority = 9 + } + } +} diff --git a/Examples/xScheduledTask/1-CreateScheduledTasks.ps1 b/Examples/xScheduledTask/1-CreateScheduledTasks.ps1 deleted file mode 100644 index 512130a0..00000000 --- a/Examples/xScheduledTask/1-CreateScheduledTasks.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -<# - .EXAMPLE - This example creates five tasks with the following schedules that start a new powershell process - - Once at 00:00 repeating every 15 minutes for 8 hours - - Daily at 00:00 repeating every 15 minutes for 8 hours - - Weekly at 00:00 repeating every 15 minutes for 8 hours on Mon, Wed, Sat - - At logon repeating every 15 minutes for 8 hours - - At startup repeating every 15 minutes for 8 hours -#> -Configuration Example -{ - param - ( - [Parameter()] - [System.String[]] - $NodeName = 'localhost' - ) - - Import-DscResource -ModuleName xComputerManagement - - Node $NodeName - { - xScheduledTask xScheduledTaskOnceAdd - { - TaskName = 'Test task once' - TaskPath = '\MyTasks' - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - ScheduleType = 'Once' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) - ActionWorkingPath = (Get-Location).Path - Enable = $true - RandomDelay = [datetime]::Today.AddMinutes(60) - DisallowHardTerminate = $true - RunOnlyIfIdle = $false - Priority = 9 - } - - xScheduledTask xScheduledTaskDailyAdd - { - TaskName = 'Test task Daily' - TaskPath = '\MyTasks' - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - ScheduleType = 'Daily' - DaysInterval = 1 - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) - RestartCount = 2 - RestartInterval = [datetime]::Today.AddMinutes(5) - RunOnlyIfNetworkAvailable = $true - WakeToRun = $true - } - - xScheduledTask xScheduledTaskWeeklyAdd - { - TaskName = 'Test task Weekly' - TaskPath = '\MyTasks' - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - ScheduleType = 'Weekly' - WeeksInterval = 1 - DaysOfWeek = 'Monday','Wednesday','Saturday' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) - AllowStartIfOnBatteries = $true - Compatibility = 'Win8' - Hidden = $true - } - - xScheduledTask xScheduledTaskLogonAdd - { - TaskName = 'Test task Logon' - TaskPath = '\MyTasks' - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - ScheduleType = 'AtLogOn' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) - } - - xScheduledTask xScheduledTaskStartupAdd - { - TaskName = 'Test task Startup' - TaskPath = '\MyTasks' - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - ScheduleType = 'AtStartup' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) - } - } -} diff --git a/Examples/xScheduledTask/2-CreateScheduledTaskDaily.ps1 b/Examples/xScheduledTask/2-CreateScheduledTaskDaily.ps1 new file mode 100644 index 00000000..90e104b4 --- /dev/null +++ b/Examples/xScheduledTask/2-CreateScheduledTaskDaily.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Daily' in the folder + task folder 'MyTasks' that starts a new powershell process every day at 00:00 repeating + every 15 minutes for 8 hours. If the task fails it will be restarted after 5 minutes + and it will be restarted a maximum of two times. It will only run if the network + is connected and will wake the machine up to execute the task. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask xScheduledTaskDailyAdd + { + TaskName = 'Test task Daily' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Daily' + DaysInterval = 1 + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' + RestartCount = 2 + RestartInterval = '00:05:00' + RunOnlyIfNetworkAvailable = $true + WakeToRun = $true + } + } +} diff --git a/Examples/xScheduledTask/3-CreateScheduledTasksDailyIndefinitely.ps1 b/Examples/xScheduledTask/3-CreateScheduledTasksDailyIndefinitely.ps1 new file mode 100644 index 00000000..3364ef1e --- /dev/null +++ b/Examples/xScheduledTask/3-CreateScheduledTasksDailyIndefinitely.ps1 @@ -0,0 +1,31 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Daily Indefinitely' in the folder + task folder 'MyTasks' that starts a new powershell process every day at 00:00 repeating + every 15 minutes indefinitely. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask xScheduledTaskDailyIndefinitelyAdd + { + TaskName = 'Test task Daily Indefinitely' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Daily' + DaysInterval = 1 + RepeatInterval = '00:15:00' + RepetitionDuration = 'Indefinitely' + } + } +} diff --git a/Examples/xScheduledTask/4-CreateScheduledTasksWeekly.ps1 b/Examples/xScheduledTask/4-CreateScheduledTasksWeekly.ps1 new file mode 100644 index 00000000..8e56670a --- /dev/null +++ b/Examples/xScheduledTask/4-CreateScheduledTasksWeekly.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Weekly' in the folder + task folder 'MyTasks' that starts a new powershell process every week on + Monday, Wednesday and Saturday at 00:00 repeating every 15 minutes for 8 hours. + The task will be hidden and will be allowed to start if the machine is running + on batteries. The task will be compatible with Windows 8. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask xScheduledTaskWeeklyAdd + { + TaskName = 'Test task Weekly' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Weekly' + WeeksInterval = 1 + DaysOfWeek = 'Monday','Wednesday','Saturday' + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' + AllowStartIfOnBatteries = $true + Compatibility = 'Win8' + Hidden = $true + } + } +} diff --git a/Examples/xScheduledTask/5-CreateScheduledTasksAtLogon.ps1 b/Examples/xScheduledTask/5-CreateScheduledTasksAtLogon.ps1 new file mode 100644 index 00000000..534d8fd3 --- /dev/null +++ b/Examples/xScheduledTask/5-CreateScheduledTasksAtLogon.ps1 @@ -0,0 +1,30 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Logon' in the folder + task folder 'MyTasks' that starts a new powershell process when the machine + is logged on repeating every 15 minutes for 8 hours. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask xScheduledTaskLogonAdd + { + TaskName = 'Test task Logon' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'AtLogOn' + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' + } + } +} diff --git a/Examples/xScheduledTask/6-CreateScheduledTasksAtStartup.ps1 b/Examples/xScheduledTask/6-CreateScheduledTasksAtStartup.ps1 new file mode 100644 index 00000000..98a2fec6 --- /dev/null +++ b/Examples/xScheduledTask/6-CreateScheduledTasksAtStartup.ps1 @@ -0,0 +1,31 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'Test task Startup' in the folder + task folder 'MyTasks' that starts a new powershell process when the machine + is started up repeating every 15 minutes for 8 hours. + +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask xScheduledTaskStartupAdd + { + TaskName = 'Test task Startup' + TaskPath = '\MyTasks' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'AtStartup' + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' + } + } +} diff --git a/Examples/xScheduledTask/2-RunPowerShellTaskEvery15Minutes.ps1 b/Examples/xScheduledTask/7-RunPowerShellTaskEvery15Minutes.ps1 similarity index 73% rename from Examples/xScheduledTask/2-RunPowerShellTaskEvery15Minutes.ps1 rename to Examples/xScheduledTask/7-RunPowerShellTaskEvery15Minutes.ps1 index d21685e4..7c55da7e 100644 --- a/Examples/xScheduledTask/2-RunPowerShellTaskEvery15Minutes.ps1 +++ b/Examples/xScheduledTask/7-RunPowerShellTaskEvery15Minutes.ps1 @@ -1,8 +1,8 @@ <# .EXAMPLE This example will create a scheduled task that will call PowerShell.exe every 15 - minutes to run a script saved locally. - The script will be called as the local system account + minutes for 4 days to run a script saved locally. The task will start immediately. + The script will be called as the local system account. #> Configuration Example { @@ -23,8 +23,8 @@ Configuration Example ActionExecutable = "C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe" ActionArguments = "-File `"C:\scripts\my custom script.ps1`"" ScheduleType = 'Once' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(10) + RepeatInterval = '00:15:00' + RepetitionDuration = '4.00:00:00' } } } diff --git a/Examples/xScheduledTask/8-RunPowerShellTaskEvery15MinutesIndefinitely.ps1 b/Examples/xScheduledTask/8-RunPowerShellTaskEvery15MinutesIndefinitely.ps1 new file mode 100644 index 00000000..9d771d04 --- /dev/null +++ b/Examples/xScheduledTask/8-RunPowerShellTaskEvery15MinutesIndefinitely.ps1 @@ -0,0 +1,30 @@ +<# + .EXAMPLE + This example will create a scheduled task that will call PowerShell.exe every 15 + minutes indefinitely to run a script saved locally. The task will start immediately. + The script will be called as the local system account. +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName xComputerManagement + + Node $NodeName + { + xScheduledTask MaintenanceScriptExample + { + TaskName = "Custom maintenance tasks" + ActionExecutable = "C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe" + ActionArguments = "-File `"C:\scripts\my custom script.ps1`"" + ScheduleType = 'Once' + RepeatInterval = '00:15:00' + RepetitionDuration = 'Indefinitely' + } + } +} diff --git a/README.md b/README.md index 6876d65b..b77c1be0 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,16 @@ Please check out common DSC Resources [contributing guidelines](https://github.c xComputer resource has following properties: -* Name: The desired computer name -* DomainName: The name of the domain to join +* Name: The desired computer name. +* DomainName: The name of the domain to join. * JoinOU: The distinguished name of the organizational unit that the computer - account will be created in -* WorkGroupName: The name of the workgroup -* Credential: Credential to be used to join or leave domain + account will be created in. +* WorkGroupName: The name of the workgroup. +* Credential: Credential to be used to join a domain. +* UnjoinCredential: Credential to be used to leave a domain. * CurrentOU: A read-only property that specifies the organizational unit that - the computer account is currently in + the computer account is currently in. +* Description: The value assigned here will be set as the local computer description. ### xComputer Examples @@ -61,6 +63,7 @@ xComputer resource has following properties: * [Set the Name while staying on the Domain](/Examples/xComputer/3-RenameComputerInDomain.ps1) * [Set the Name while staying on the Workgroup](/Examples/xComputer/4-RenameComputerInWorkgroup.ps1) * [Switch from a Domain to a Workgroup](/Examples/xComputer/5-UnjoinDomainAndJoinWorkgroup.ps1) +* [Set a Description for the Workstation](/Examples/xComputer/6-SetComputerDescriptionInWorkgroup.ps1) ## xOfflineDomainJoin @@ -78,7 +81,7 @@ resource that can only be used once in a configuration and has following propert xScheduledTask resource is used to define basic recurring scheduled tasks on the local computer. -Tasks are created to run indefinitely based on the schedule defined. +Tasks are created to run based on the schedule defined. xScheduledTask has the following properties: * TaskName: The name of the task @@ -106,7 +109,7 @@ xScheduledTask has the following properties: trigger. The delay time is a random time between the time the task triggers and the time that you specify in this setting. * RepetitionDuration: Specifies how long the repetition pattern repeats after - the task starts. + the task starts. May be set to `Indefinitely` to specify an indefinite duration. * DaysOfWeek: Specifies an array of the days of the week on which Task Scheduler runs the task. * WeeksInterval: Specifies the interval between the weeks in the schedule. An @@ -165,8 +168,14 @@ xScheduledTask has the following properties: ### xScheduledTask Examples -* [Create five different scheduled tasks that run PowerShell](/Examples/xScheduledTask/1-CreateScheduledTasks.ps1) -* [Run a PowerShell script every 15 minutes on a server](/Examples/xScheduledTask/2-RunPowerShellTaskEvery15Minutes.ps1) +* [Create a task that starts PowerShell once every 15 minutes from 00:00 for 8 hours](/Examples/xScheduledTask/1-CreateScheduledTaskOnce.ps1) +* [Create a task that starts PowerShell daily every 15 minutes from 00:00 for 8 hours](/Examples/xScheduledTask/2-CreateScheduledTaskDaily.ps1) +* [Create a task that starts PowerShell daily every 15 minutes from 00:00 indefinitely](/Examples/xScheduledTask/3-CreateScheduledTasksDailyIndefinitely.ps1) +* [Create a task that starts PowerShell weekly on Monday, Wednesday and Saturday every 15 minutes from 00:00 for 8 hours](/Examples/xScheduledTask/4-CreateScheduledTasksWeekly.ps1) +* [Create a task that starts PowerShell at logon and runs every 15 minutes from 00:00 for 8 hours](/Examples/xScheduledTask/5-CreateScheduledTasksAtLogon.ps1) +* [Create a task that starts PowerShell at startup and runs every 15 minutes from 00:00 for 8 hours](/Examples/xScheduledTask/6-CreateScheduledTasksAtStartup.ps1) +* [Run a PowerShell script every 15 minutes for 4 days on a server](/Examples/xScheduledTask/7-RunPowerShellTaskEvery15Minutes.ps1) +* [Run a PowerShell script every 15 minutes indefinitely on a server](/Examples/xScheduledTask/8-RunPowerShellTaskEvery15MinutesIndefinitely.ps1) ## xPowerPlan @@ -202,6 +211,30 @@ xVirtualMemory has the following properties: ### Unreleased +### 3.0.0.0 + +* xComputer: Added parameter to set the local computer description along with documentation + and unit tests for this change. +* BREAKING CHANGE: xScheduledTask: + * Converted all Interval/Duration type parameters over to be string format + to prevent the Timezone the MOF file was created in from being stored. + This is to fix problems where MOF files are created in one timezone but + deployed nodes to a different timezone - See [Issue #85](https://github.com/PowerShell/xComputerManagement/issues/85) + * Added ConvertTo-TimeSpanFromScheduledTaskString function and refactored + to reduce code duplication. + * Added support for setting repetition duration to `Indefinitely`. +* xComputer: + * Moved strings to localization file. + * Updated to meet HQRM guidelines. +* xVirtualMemory: + * Refactored shared common code into new utility functions to + reduce code duplication and improve testability. + * Moved strings into localizable strings file. + * Converted calls to `throw` to use `New-InvalidOperationException` + in CommonResourceHelper. + * Improved unit test coverage. + * Updated to meet HQRM guidelines. + ### 2.1.0.0 * xComputer: Changed comparison that validates if we are in the correct AD diff --git a/Tests/Integration/MSFT_xScheduledTask.Config.ps1 b/Tests/Integration/MSFT_xScheduledTask.Config.ps1 index 875aba91..864e1291 100644 --- a/Tests/Integration/MSFT_xScheduledTask.Config.ps1 +++ b/Tests/Integration/MSFT_xScheduledTask.Config.ps1 @@ -1,3 +1,26 @@ +Configuration xScheduledTaskOnceCrossTimezone +{ + Import-DscResource -ModuleName xComputerManagement + node 'localhost' + { + xScheduledTask xScheduledTaskOnceAdd + { + TaskName = 'Test task once cross timezone' + TaskPath = '\xComputerManagement\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = '00:15:00' + RepetitionDuration = '23:00:00' + ActionWorkingPath = (Get-Location).Path + Enable = $true + RandomDelay = '01:00:00' + DisallowHardTerminate = $true + RunOnlyIfIdle = $false + Priority = 9 + } + } +} + Configuration xScheduledTaskOnceAdd { Import-DscResource -ModuleName xComputerManagement @@ -9,11 +32,11 @@ Configuration xScheduledTaskOnceAdd TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' ActionWorkingPath = (Get-Location).Path Enable = $true - RandomDelay = [datetime]::Today.AddMinutes(60) + RandomDelay = '01:00:00' DisallowHardTerminate = $true RunOnlyIfIdle = $false Priority = 9 @@ -33,10 +56,32 @@ Configuration xScheduledTaskDailyAdd ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 1 - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' + RestartCount = 2 + RestartInterval = '00:05:00' + RunOnlyIfNetworkAvailable = $true + WakeToRun = $true + } + } +} + +Configuration xScheduledTaskDailyIndefinitelyAdd +{ + Import-DscResource -ModuleName xComputerManagement + node 'localhost' + { + xScheduledTask xScheduledTaskDailyAdd + { + TaskName = 'Test task Daily Indefinitely' + TaskPath = '\xComputerManagement\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Daily' + DaysInterval = 1 + RepeatInterval = '00:15:00' + RepetitionDuration = 'Indefinitely' RestartCount = 2 - RestartInterval = [datetime]::Today.AddMinutes(5) + RestartInterval = '00:05:00' RunOnlyIfNetworkAvailable = $true WakeToRun = $true } @@ -56,8 +101,8 @@ Configuration xScheduledTaskWeeklyAdd ScheduleType = 'Weekly' WeeksInterval = 1 DaysOfWeek = 'Monday', 'Wednesday', 'Saturday' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' AllowStartIfOnBatteries = $true Compatibility = 'Win8' Hidden = $true @@ -76,8 +121,8 @@ Configuration xScheduledTaskLogonAdd TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'AtLogOn' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' } } } @@ -93,8 +138,8 @@ Configuration xScheduledTaskStartupAdd TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'AtStartup' - RepeatInterval = [datetime]::Today.AddMinutes(15) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:15:00' + RepetitionDuration = '08:00:00' } } } @@ -110,8 +155,8 @@ Configuration xScheduledTaskOnceMod TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today.AddMinutes(20) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:20:00' + RepetitionDuration = '08:00:00' DisallowDemandStart = $true } } @@ -129,8 +174,27 @@ Configuration xScheduledTaskDailyMod ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 2 - RepeatInterval = [datetime]::Today.AddMinutes(30) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:30:00' + RepetitionDuration = '08:00:00' + Enable = $false + } + } +} + +Configuration xScheduledTaskDailyIndefinitelyMod +{ + Import-DscResource -ModuleName xComputerManagement + node 'localhost' + { + xScheduledTask xScheduledTaskDailyMod + { + TaskName = 'Test task Daily Indefinitely' + TaskPath = '\xComputerManagement\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Daily' + DaysInterval = 2 + RepeatInterval = '00:30:00' + RepetitionDuration = '10.00:00:00' Enable = $false } } @@ -149,8 +213,8 @@ Configuration xScheduledTaskWeeklyMod ScheduleType = 'Weekly' WeeksInterval = 1 DaysOfWeek = 'Monday', 'Thursday', 'Saturday' - RepeatInterval = [datetime]::Today.AddMinutes(40) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:40:00' + RepetitionDuration = '08:00:00' } } } @@ -166,8 +230,8 @@ Configuration xScheduledTaskLogonMod TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'AtStartup' - RepeatInterval = [datetime]::Today.AddMinutes(12) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:12:00' + RepetitionDuration = '08:00:00' } } } @@ -183,8 +247,8 @@ Configuration xScheduledTaskStartupMod TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'AtLogOn' - RepeatInterval = [datetime]::Today.AddMinutes(10) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:10:00' + RepetitionDuration = '08:00:00' } } } @@ -200,8 +264,8 @@ Configuration xScheduledTaskOnceDel TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today.AddMinutes(20) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:20:00' + RepetitionDuration = '08:00:00' DisallowDemandStart = $true Ensure = 'Absent' } @@ -220,8 +284,28 @@ Configuration xScheduledTaskDailyDel ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 2 - RepeatInterval = [datetime]::Today.AddMinutes(30) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:30:00' + RepetitionDuration = '08:00:00' + Enable = $false + Ensure = 'Absent' + } + } +} + +Configuration xScheduledTaskDailyIndefinitelyDel +{ + Import-DscResource -ModuleName xComputerManagement + node 'localhost' + { + xScheduledTask xScheduledTaskDailyDel + { + TaskName = 'Test task Daily Indefinitely' + TaskPath = '\xComputerManagement\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Daily' + DaysInterval = 2 + RepeatInterval = '00:30:00' + RepetitionDuration = '08:00:00' Enable = $false Ensure = 'Absent' } @@ -241,8 +325,8 @@ Configuration xScheduledTaskWeeklyDel ScheduleType = 'Weekly' WeeksInterval = 1 DaysOfWeek = 'Monday', 'Thursday', 'Saturday' - RepeatInterval = [datetime]::Today.AddMinutes(40) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:40:00' + RepetitionDuration = '08:00:00' Ensure = 'Absent' } } @@ -259,8 +343,8 @@ Configuration xScheduledTaskLogonDel TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'AtStartup' - RepeatInterval = [datetime]::Today.AddMinutes(12) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:12:00' + RepetitionDuration = '08:00:00' Ensure = 'Absent' } } @@ -277,8 +361,8 @@ Configuration xScheduledTaskStartupDel TaskPath = '\xComputerManagement\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'AtLogOn' - RepeatInterval = [datetime]::Today.AddMinutes(10) - RepetitionDuration = [datetime]::Today.AddHours(8) + RepeatInterval = '00:10:00' + RepetitionDuration = '08:00:00' Ensure = 'Absent' } } diff --git a/Tests/Integration/MSFT_xScheduledTask.Integration.Tests.ps1 b/Tests/Integration/MSFT_xScheduledTask.Integration.Tests.ps1 index f3e75ff8..81df50c5 100644 --- a/Tests/Integration/MSFT_xScheduledTask.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_xScheduledTask.Integration.Tests.ps1 @@ -15,46 +15,49 @@ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $Global:DSCModuleName ` -DSCResourceName $Global:DSCResourceName ` -TestType Integration - #endregion + +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global + # Begin Testing try { $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($Global:DSCResourceName).config.ps1" . $ConfigFile - + #region Pester Tests Describe $Global:DSCResourceName { $contexts = @{ - Once = 'xScheduledTaskOnce' - Daily = 'xScheduledTaskDaily' - Weekly = 'xScheduledTaskWeekly' - AtLogon = 'xScheduledTaskLogon' - AtStartup = 'xScheduledTaskStartup' + Once = 'xScheduledTaskOnce' + Daily = 'xScheduledTaskDaily' + DailyIndefinitely = 'xScheduledTaskDailyIndefinitely' + Weekly = 'xScheduledTaskWeekly' + AtLogon = 'xScheduledTaskLogon' + AtStartup = 'xScheduledTaskStartup' } - + foreach ($contextInfo in $contexts.GetEnumerator()) { Context "[$($contextInfo.Key)] No scheduled task exists but it should" { $CurrentConfig = '{0}Add' -f $contextInfo.Value $ConfigDir = (Join-Path -Path $TestDrive -ChildPath $CurrentConfig) $ConfigMof = (Join-Path -Path $ConfigDir -ChildPath 'localhost.mof') - - It 'should compile and apply the MOF without throwing' { + + It 'Should compile the MOF without throwing' { { . $CurrentConfig -OutputPath $ConfigDir } | Should Not Throw } - - It 'should apply the MOF correctly' { + + It 'Should apply the MOF correctly' { { - Start-DscConfiguration -Path $ConfigDir -Wait -Force + Start-DscConfiguration -Path $ConfigDir -Wait -Force -Verbose } | Should Not Throw } - - It 'should return a compliant state after being applied' { - (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true + + It 'Should return a compliant state after being applied' { + (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true } } @@ -62,21 +65,21 @@ try $CurrentConfig = '{0}Mod' -f $contextInfo.Value $ConfigDir = (Join-Path -Path $TestDrive -ChildPath $CurrentConfig) $ConfigMof = (Join-Path -Path $ConfigDir -ChildPath 'localhost.mof') - - It 'should compile and apply the MOF without throwing' { + + It 'Should compile the MOF without throwing' { { . $CurrentConfig -OutputPath $ConfigDir } | Should Not Throw } - - It 'should apply the MOF correctly' { + + It 'Should apply the MOF correctly' { { - Start-DscConfiguration -Path $ConfigDir -Wait -Force + Start-DscConfiguration -Path $ConfigDir -Wait -Force -Verbose } | Should Not Throw } - - It 'should return a compliant state after being applied' { - (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true + + It 'Should return a compliant state after being applied' { + (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true } } @@ -84,30 +87,79 @@ try $CurrentConfig = '{0}Del' -f $contextInfo.Value $ConfigDir = (Join-Path -Path $TestDrive -ChildPath $CurrentConfig) $ConfigMof = (Join-Path -Path $ConfigDir -ChildPath 'localhost.mof') - - It 'should compile and apply the MOF without throwing' { + + It 'Should compile the MOF without throwing' { { . $CurrentConfig -OutputPath $ConfigDir } | Should Not Throw } - - It 'should apply the MOF correctly' { + + It 'Should apply the MOF correctly' { { - Start-DscConfiguration -Path $ConfigDir -Wait -Force + Start-DscConfiguration -Path $ConfigDir -Wait -Force -Verbose } | Should Not Throw } - - It 'should return a compliant state after being applied' { - (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true + + It 'Should return a compliant state after being applied' { + (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true } } } + + Context "MOF is created in a different timezone to node MOF being applied to" { + BeforeAll { + $currentTimeZoneId = Get-TimeZoneId + } + + $CurrentConfig = 'xScheduledTaskOnceCrossTimezone' + $ConfigDir = (Join-Path -Path $TestDrive -ChildPath $CurrentConfig) + $ConfigMof = (Join-Path -Path $ConfigDir -ChildPath 'localhost.mof') + + It 'Should compile the MOF without throwing in W. Australia Standard Time Timezone' { + { + + Set-TimeZoneId -Id 'W. Australia Standard Time' + . $CurrentConfig -OutputPath $ConfigDir + } | Should Not Throw + } + + It 'Should apply the MOF correctly in New Zealand Standard Time Timezone' { + { + Set-TimeZoneId -Id 'New Zealand Standard Time' + Start-DscConfiguration -Path $ConfigDir -Wait -Force -Verbose + } | Should Not Throw + } + + It 'Should return a compliant state after being applied' { + (Test-DscConfiguration -ReferenceConfiguration $ConfigMof -Verbose).InDesiredState | Should be $true + } + + It 'Should have set the resource and all the parameters should match' { + $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $CurrentConfig} + $current.TaskName | Should Be 'Test task once cross timezone' + $current.TaskPath | Should Be '\xComputerManagement\' + $current.ActionExecutable | Should Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + $current.ScheduleType | Should Be 'Once' + $current.RepeatInterval | Should Be '00:15:00' + $current.RepetitionDuration | Should Be '23:00:00' + $current.ActionWorkingPath | Should Be (Get-Location).Path + $current.Enable | Should Be $true + $current.RandomDelay | Should Be '01:00:00' + $current.DisallowHardTerminate | Should Be $true + $current.RunOnlyIfIdle | Should Be $false + $current.Priority | Should Be 9 + } + + AfterAll { + Set-TimeZoneId -Id $currentTimeZoneId + } + } } } finally { #region FOOTER - + # Remove any traces of the created tasks Get-ScheduledTask -TaskPath '\xComputerManagement\' -ErrorAction SilentlyContinue | Unregister-ScheduledTask -ErrorAction SilentlyContinue -Confirm:$false @@ -115,7 +167,7 @@ finally $scheduler.Connect() $rootFolder = $scheduler.GetFolder('\') $rootFolder.DeleteFolder('xComputerManagement', 0) - + Restore-TestEnvironment -TestEnvironment $TestEnvironment #endregion } diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 new file mode 100644 index 00000000..8f261600 --- /dev/null +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -0,0 +1,138 @@ +<# + .SYNOPSIS + Returns an invalid argument exception object + + .PARAMETER Message + The message explaining why this error is being thrown + + .PARAMETER ArgumentName + The name of the invalid argument that is causing this error to be thrown +#> +function Get-InvalidArgumentRecord +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $ArgumentName + ) + + $argumentException = New-Object -TypeName 'ArgumentException' -ArgumentList @( $Message, + $ArgumentName ) + $newObjectParams = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( $argumentException, $ArgumentName, 'InvalidArgument', $null ) + } + return New-Object @newObjectParams +} + +<# + .SYNOPSIS + Returns an invalid operation exception object + + .PARAMETER Message + The message explaining why this error is being thrown + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error +#> +function Get-InvalidOperationRecord +{ + [CmdletBinding()] + param + ( + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $Message) + { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' + } + elseif ($null -eq $ErrorRecord) + { + $invalidOperationException = + New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message ) + } + else + { + $invalidOperationException = + New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message, + $ErrorRecord.Exception ) + } + + $newObjectParams = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', + 'InvalidOperation', $null ) + } + return New-Object @newObjectParams +} + +<# + .SYNOPSIS + Returns the current Time Zone. This method is used because the + Get-Timezone cmdlet is not available on OS versions prior to + Windows 10/Windows Server 2016. +#> +function Get-TimeZoneId +{ + [CmdletBinding()] + param() + + $TimeZone = (Get-CimInstance ` + -ClassName WIN32_Timezone ` + -Namespace root\cimv2).StandardName + + $timeZoneInfo = [System.TimeZoneInfo]::GetSystemTimeZones() | + Where-Object StandardName -eq $TimeZone + + return $timeZoneInfo.Id +} # function Get-TimeZoneId + +<# + .SYNOPSIS + Set the current Time Zone. This method is used because the + Set-Timezone cmdlet is not available on OS versions prior to + Windows 10/Windows Server 2016. + + .PARAMETER Id + The Id of the Timezone to set the system to. +#> +function Set-TimeZoneId +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.String] + $Id + ) + + try + { + & tzutil.exe @('/s',$Id) + } + catch + { + $ErrorMsg = $_.Exception.Message + Write-Verbose -Message $ErrorMsg + } # try +} # function Set-TimeZoneId + +Export-ModuleMember -Function ` + Get-TimeZoneId, ` + Set-TimeZoneId, ` + Get-InvalidArgumentRecord, ` + Get-InvalidOperationRecord diff --git a/Tests/Unit/MSFT_xComputer.Tests.ps1 b/Tests/Unit/MSFT_xComputer.Tests.ps1 index 0b81a1aa..9dc7c848 100644 --- a/Tests/Unit/MSFT_xComputer.Tests.ps1 +++ b/Tests/Unit/MSFT_xComputer.Tests.ps1 @@ -1,278 +1,929 @@ -$Global:DSCModuleName = 'xComputerManagement' -$Global:DSCResourceName = 'MSFT_xComputer' +$script:DSCModuleName = 'xComputerManagement' +$script:DSCResourceName = 'MSFT_xComputer' -#region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) -} -else +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) { - & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull') + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` -TestType Unit -#endregion +#endregion HEADER # Begin Testing try { #region Pester Tests - InModuleScope $Global:DSCResourceName { + InModuleScope $script:DSCResourceName { + $script:DSCResourceName = 'MSFT_xComputer' - Describe $Global:DSCResourceName { + Describe $script:DSCResourceName { # A real password isn't needed here - use this next line to avoid triggering PSSA rule - $SecPassword = New-Object -Type SecureString - $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'USER',$SecPassword - $NotComputerName = if($env:COMPUTERNAME -ne 'othername'){'othername'}else{'name'} + $securePassword = New-Object -Type SecureString + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'USER', $securePassword + $notComputerName = if ($env:COMPUTERNAME -ne 'othername') + { + 'othername' + } + else + { + 'name' + } + + Context "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + DomainName = 'ContosoLtd' + } + } -ParameterFilter { + $Class -eq 'Win32_NTDomain' + } - Context "$($Global:DSCResourceName)\Test-TargetResource" { - Mock Get-WMIObject {[PSCustomObject]@{DomainName = 'ContosoLtd'}} -ParameterFilter {$Class -eq 'Win32_NTDomain'} It 'Throws if both DomainName and WorkGroupName are specified' { - {Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -WorkGroupName 'workgroup'} | Should Throw + $errorRecord = Get-InvalidOperationRecord ` + -Message ($LocalizedData.DomainNameAndWorkgroupNameError) + + { + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'contoso.com' ` + -WorkGroupName 'workgroup' ` + -Verbose + } | Should Throw $errorRecord } + It 'Throws if Domain is specified without Credentials' { - {Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com'} | Should Throw + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($LocalizedData.CredentialsNotSpecifiedError) ` + -ArgumentName 'Credentials' + + { + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'contoso.com' ` + -Verbose + } | Should Throw $errorRecord } + It 'Should return True if Domain name is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -DomainName 'Contoso.com' -Credential $Credential | Should Be $true + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'Contoso.com' ` + -Credential $credential ` + -Verbose | Should Be $true } + It 'Should return True if Workgroup name is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'workgroup' | Should Be $true + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Workgroup'; + Workgroup = 'Workgroup'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -WorkGroupName 'workgroup' ` + -Verbose | Should Be $true } + It 'Should return True if ComputerName and Domain name is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $true - Test-TargetResource -Name 'localhost' -DomainName 'contoso.com' -Credential $Credential | Should Be $true + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'contoso.com' ` + -Credential $credential ` + -Verbose | Should Be $true + + Test-TargetResource ` + -Name 'localhost' ` + -DomainName 'contoso.com' ` + -Credential $credential ` + -Verbose | Should Be $true } + It 'Should return True if ComputerName and Workgroup is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'workgroup' | Should Be $true - Test-TargetResource -Name 'localhost' -WorkGroupName 'workgroup' | Should Be $true + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Workgroup'; + Workgroup = 'Workgroup'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -WorkGroupName 'workgroup' ` + -Verbose | Should Be $true + + Test-TargetResource ` + -Name 'localhost' ` + -WorkGroupName 'workgroup' ` + -Verbose | Should Be $true } + It 'Should return True if ComputerName is same and no Domain or Workgroup specified' { - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName | Should Be $true - Test-TargetResource -Name 'localhost' | Should Be $true - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName | Should Be $true - Test-TargetResource -Name 'localhost' | Should Be $true + Mock -CommandName Get-WmiObject -MockWith { + [PSCustomObject] @{ + Domain = 'Workgroup'; + Workgroup = 'Workgroup'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -Verbose | Should Be $true + + Test-TargetResource ` + -Name 'localhost' ` + -Verbose | Should Be $true + + Mock -CommandName Get-WmiObject { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -Verbose | Should Be $true + + Test-TargetResource ` + -Name 'localhost' ` + -Verbose | Should Be $true } + It 'Should return False if ComputerName is not same and no Domain or Workgroup specified' { - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $NotComputerName | Should Be $false - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $NotComputerName | Should Be $false + Mock -CommandName Get-WmiObject -MockWith { + [PSCustomObject] @{ + Domain = 'Workgroup'; + Workgroup = 'Workgroup'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $notComputerName ` + -Verbose | Should Be $false + + Mock -CommandName Get-WmiObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $notComputerName ` + -Verbose | Should Be $false } + It 'Should return False if Domain name is not same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -Credential $Credential | Should Be $false - Test-TargetResource -Name 'localhost' -DomainName 'adventure-works.com' -Credential $Credential | Should Be $false + Mock -CommandName Get-WMIObject { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'adventure-works.com' ` + -Credential $credential ` + -Verbose | Should Be $false + + Test-TargetResource ` + -Name 'localhost' ` + -DomainName 'adventure-works.com' ` + -Credential $credential ` + -Verbose | Should Be $false } + It 'Should return False if Workgroup name is not same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'NOTworkgroup' | Should Be $false - Test-TargetResource -Name 'localhost' -WorkGroupName 'NOTworkgroup' | Should Be $false + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Workgroup'; + Workgroup = 'Workgroup'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -WorkGroupName 'NOTworkgroup' ` + -Verbose | Should Be $false + + Test-TargetResource ` + -Name 'localhost' ` + -WorkGroupName 'NOTworkgroup' ` + -Verbose | Should Be $false } + It 'Should return False if ComputerName is not same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $NotComputerName -WorkGroupName 'workgroup' | Should Be $false - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $NotComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $false + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Workgroup'; + Workgroup = 'Workgroup'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $notComputerName ` + -WorkGroupName 'workgroup' ` + -Verbose | Should Be $false + + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $notComputerName ` + -DomainName 'contoso.com' ` + -Credential $credential ` + -Verbose | Should Be $false } + It 'Should return False if Computer is in Workgroup and Domain is specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $false - Test-TargetResource -Name 'localhost' -DomainName 'contoso.com' -Credential $Credential | Should Be $false + Mock -CommandName Get-WMIObject { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'contoso.com' ` + -Credential $credential ` + -Verbose | Should Be $false + + Test-TargetResource ` + -Name 'localhost' ` + -DomainName 'contoso.com' ` + -Credential $credential ` + -Verbose | Should Be $false } + It 'Should return False if ComputerName is in Domain and Workgroup is specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'Contoso' -Credential $Credential -UnjoinCredential $Credential | Should Be $false - Test-TargetResource -Name 'localhost' -WorkGroupName 'Contoso' -Credential $Credential -UnjoinCredential $Credential | Should Be $false + Mock -CommandName Get-WMIObject { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -WorkGroupName 'Contoso' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should Be $false + + Test-TargetResource ` + -Name 'localhost' ` + -WorkGroupName 'Contoso' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should Be $false } + It 'Throws if name is to long' { - {Test-TargetResource -Name "ThisNameIsTooLong"} | Should Throw + { + Test-TargetResource ` + -Name 'ThisNameIsTooLong' ` + -Verbose + } | Should Throw } - It 'Throws if name contains illigal characters' { - {Test-TargetResource -Name "ThisIsBad<>"} | Should Throw + + It 'Throws if name contains illegal characters' { + { + Test-TargetResource ` + -Name 'ThisIsBad<>' ` + -Verbose + } | Should Throw } + It 'Should not Throw if name is localhost' { - {Test-TargetResource -Name "localhost"} | Should Not Throw + { + Test-TargetResource ` + -Name 'localhost' ` + -Verbose + } | Should Not Throw } + It 'Should return true if description is same as specified' { + Mock -CommandName Get-CimInstance -MockWith { + [PSCustomObject] @{ + Description = 'This is my computer' + } + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -Description 'This is my computer' ` + -Verbose | Should Be $true + + Test-TargetResource ` + -Name 'localhost' ` + -Description 'This is my computer' ` + -Verbose | Should Be $true + } + + It 'Should return false if description is same as specified' { + Mock -CommandName Get-CimInstance -MockWith { + [PSCustomObject] @{ + Description = 'This is not my computer' + } + } + + Test-TargetResource ` + -Name $env:COMPUTERNAME ` + -Description 'This is my computer' ` + -Verbose | Should Be $false + + Test-TargetResource ` + -Name 'localhost' ` + -Description 'This is my computer' ` + -Verbose | Should Be $false + } } - Context "$($Global:DSCResourceName)\Get-TargetResource" { + + Context "$($script:DSCResourceName)\Get-TargetResource" { It 'should not throw' { - {Get-TargetResource -Name $env:COMPUTERNAME} | Should Not Throw + { + Get-TargetResource ` + -Name $env:COMPUTERNAME ` + -Verbose + } | Should Not Throw } - It 'Should return a hashtable containing Name, DomainName, JoinOU, CurrentOU, Credential, UnjoinCredential and WorkGroupName' { - $Result = Get-TargetResource -Name $env:COMPUTERNAME + + It 'Should return a hashtable containing Name, DomainName, JoinOU, CurrentOU, Credential, UnjoinCredential, WorkGroupName and Description' { + $Result = Get-TargetResource ` + -Name $env:COMPUTERNAME ` + -Verbose + $Result.GetType().Fullname | Should Be 'System.Collections.Hashtable' - $Result.Keys | Sort-Object | Should Be @('Credential', 'CurrentOU', 'DomainName', 'JoinOU', 'Name', 'UnjoinCredential', 'WorkGroupName') + $Result.Keys | Sort-Object | Should Be @('Credential', 'CurrentOU', 'Description', 'DomainName', 'JoinOU', 'Name', 'UnjoinCredential', 'WorkGroupName') } + It 'Throws if name is to long' { - {Get-TargetResource -Name "ThisNameIsTooLong"} | Should Throw + { + Get-TargetResource ` + -Name 'ThisNameIsTooLong' ` + -Verbose + } | Should Throw } - It 'Throws if name contains illigal characters' { - {Get-TargetResource -Name "ThisIsBad<>"} | Should Throw + + It 'Throws if name contains illegal characters' { + { + Get-TargetResource ` + -Name 'ThisIsBad<>' ` + -Verbose + } | Should Throw } } - Context "$($Global:DSCResourceName)\Set-TargetResource" { - Mock Rename-Computer {} - Mock Add-Computer {} + + Context "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Rename-Computer + Mock -CommandName Add-Computer + Mock -CommandName Set-CimInstance + It 'Throws if both DomainName and WorkGroupName are specified' { - {Set-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -WorkGroupName 'workgroup'} | Should Throw - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + $errorRecord = Get-InvalidOperationRecord ` + -Message ($LocalizedData.DomainNameAndWorkgroupNameError) + + { + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'contoso.com' ` + -WorkGroupName 'workgroup' ` + -Verbose + } | Should Throw $errorRecord + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It } + It 'Throws if Domain is specified without Credentials' { - {Set-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com'} | Should Throw - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($LocalizedData.CredentialsNotSpecifiedError) ` + -ArgumentName 'Credentials' + + { + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'contoso.com' ` + -Verbose + } | Should Throw $errorRecord + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It } + It 'Changes ComputerName and changes Domain to new Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'adventure-works.com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes ComputerName and changes Domain to new Domain with specified OU' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'adventure-works.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes ComputerName and changes Domain to Workgroup' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -WorkGroupName 'contoso' -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName -and $NewName -and $Credential} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName -or $UnjoinCredential} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $notComputerName ` + -WorkGroupName 'contoso' ` + -Credential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $WorkGroupName -and $NewName -and $credential } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $DomainName -or $UnjoinCredential } } + It 'Changes ComputerName and changes Workgroup to Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Set-TargetResource -Name $NotComputerName -DomainName 'Contoso.com' -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso'; + Workgroup = 'Contoso'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'Contoso.com' ` + -Credential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes ComputerName and changes Workgroup to Domain with specified OU' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Set-TargetResource -Name $NotComputerName -DomainName 'Contoso.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso'; + Workgroup = 'Contoso'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'Contoso.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes ComputerName and changes Workgroup to new Workgroup' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Mock Get-ComputerDomain {''} - Set-TargetResource -Name $NotComputerName -WorkGroupName 'adventure-works' | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso'; + Workgroup = 'Contoso'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Set-TargetResource ` + -Name $notComputerName ` + -WorkGroupName 'adventure-works' ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $WorkGroupName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $DomainName } } + It 'Changes only the Domain to new Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'adventure-works.com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes only the Domain to new Domain when name is [localhost]' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name 'localhost' -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name 'localhost' ` + -DomainName 'adventure-works.com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes only the Domain to new Domain with specified OU' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'adventure-works.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes only the Domain to new Domain with specified OU when Name is [localhost]' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name 'localhost' -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name 'localhost' ` + -DomainName 'adventure-works.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Changes only Domain to Workgroup' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {''} - Set-TargetResource -Name $Env:ComputerName -WorkGroupName 'Contoso' -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -WorkGroupName 'Contoso' ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $WorkGroupName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $DomainName } } + It 'Changes only Domain to Workgroup when Name is [localhost]' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {''} - Set-TargetResource -Name 'localhost' -WorkGroupName 'Contoso' -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Set-TargetResource ` + -Name 'localhost' ` + -WorkGroupName 'Contoso' ` + -UnjoinCredential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $WorkGroupName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $DomainName } } + It 'Changes only ComputerName in Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock Get-ComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 1 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $notComputerName ` + -Credential $credential ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It } + It 'Changes only ComputerName in Workgroup' { - Mock Get-ComputerDomain {''} - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Set-TargetResource -Name $NotComputerName | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 1 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso'; + Workgroup = 'Contoso'; + PartOfDomain = $false + } + } + + Set-TargetResource ` + -Name $notComputerName ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It } + It 'Throws if name is to long' { - {Set-TargetResource -Name "ThisNameIsTooLong"} | Should Throw + { + Set-TargetResource ` + -Name 'ThisNameIsTooLong' ` + -Verbose + } | Should Throw } - It 'Throws if name contains illigal characters' { - {Set-TargetResource -Name "ThisIsBad<>"} | Should Throw + + It 'Throws if name contains illegal characters' { + { + Set-TargetResource ` + -Name 'ThisIsBad<>' ` + -Verbose + } | Should Throw + } + + It 'Changes computer description in a workgroup' { + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Mock -CommandName Get-WMIObject { + [PSCustomObject] @{ + Domain = 'Contoso'; + Workgroup = 'Contoso'; + PartOfDomain = $false + } + } + + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -Description 'This is my computer' ` + -DomainName '' ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Set-CimInstance -Exactly -Times 1 -Scope It + } + + It 'Changes computer description in a domain' { + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso.com'; + Workgroup = 'Contoso.com'; + PartOfDomain = $true + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + 'contoso.com' + } + + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -Verbose | Should BeNullOrEmpty + + Set-TargetResource ` + -Name $env:COMPUTERNAME ` + -DomainName 'Contoso.com' ` + -Credential $credential ` + -UnjoinCredential $credential ` + -Description 'This is my computer' ` + -Verbose | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Set-CimInstance -Exactly -Times 1 -Scope It } } } diff --git a/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 b/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 index 1413925e..55c5b290 100644 --- a/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 +++ b/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 @@ -1,37 +1,38 @@ -$Global:DSCModuleName = 'xComputerManagement' -$Global:DSCResourceName = 'MSFT_xOfflineDomainJoin' +$script:DSCModuleName = 'xComputerManagement' +$script:DSCResourceName = 'MSFT_xOfflineDomainJoin' -#region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) -} -else +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) { - & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull') + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` - -TestType Unit -#endregion + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER # Begin Testing try { #region Pester Tests - InModuleScope $Global:DSCResourceName { + InModuleScope $script:DSCResourceName { + $script:DSCResourceName = 'MSFT_xOfflineDomainJoin' $TestOfflineDomainJoin = @{ IsSingleInstance = 'Yes' RequestFile = 'C:\ODJRequest.txt' } - Describe "$($Global:DSCResourceName)\Get-TargetResource" { + Describe "$($script:DSCResourceName)\Get-TargetResource" { It 'should return the correct values' { $Result = Get-TargetResource ` @@ -42,7 +43,7 @@ try } } - Describe "$($Global:DSCResourceName)\Set-TargetResource" { + Describe "$($script:DSCResourceName)\Set-TargetResource" { Mock Test-Path -MockWith { return $True } Mock Join-Domain @@ -77,8 +78,8 @@ try } } } - - Describe "$($Global:DSCResourceName)\Test-TargetResource" { + + Describe "$($script:DSCResourceName)\Test-TargetResource" { Mock Test-Path -MockWith { return $True } Mock Get-DomainName -MockWith { return $null } @@ -126,8 +127,8 @@ try } } - Describe "$($Global:DSCResourceName)\Join-Domain" { - Mock djoin.exe -MockWith { $Global:LASTEXITCODE = 0; return "OK" } + Describe "$($script:DSCResourceName)\Join-Domain" { + Mock djoin.exe -MockWith { $script:LASTEXITCODE = 0; return "OK" } Context 'Domain Join successful' { It 'should not throw' { @@ -138,7 +139,7 @@ try } } - Mock djoin.exe -MockWith { $Global:LASTEXITCODE = 99; return "ERROR" } + Mock djoin.exe -MockWith { $script:LASTEXITCODE = 99; return "ERROR" } Context 'Domain Join successful' { $errorId = 'DjoinError' diff --git a/Tests/Unit/MSFT_xPowerPlan.Tests.ps1 b/Tests/Unit/MSFT_xPowerPlan.Tests.ps1 index e177f69b..4bf90388 100644 --- a/Tests/Unit/MSFT_xPowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_xPowerPlan.Tests.ps1 @@ -1,7 +1,7 @@ -$script:DSCModuleName = 'xComputerManagement' -$script:DSCResourceName = 'MSFT_xPowerPlan' +$script:DSCModuleName = 'xComputerManagement' +$script:DSCResourceName = 'MSFT_xPowerPlan' -#region HEADER +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global # Unit Test Template Version: 1.2.0 $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) @@ -16,8 +16,7 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit - + -TestType Unit #endregion HEADER function Invoke-TestCleanup { @@ -34,11 +33,11 @@ try Name = 'High performance' } } - + Context 'When the system is in the desired present state' { BeforeEach { Mock -CommandName Get-CimInstance -MockWith { - return New-Object Object | + return New-Object Object | Add-Member -MemberType NoteProperty -Name IsActive -Value $true -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable } @@ -53,7 +52,7 @@ try Context 'When the system is not in the desired present state' { BeforeEach { Mock -CommandName Get-CimInstance -MockWith { - return New-Object Object | + return New-Object Object | Add-Member -MemberType NoteProperty -Name IsActive -Value $false -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable } @@ -64,7 +63,7 @@ try $result.Name | Should Be $null } } - + Context 'When the Get-CimInstance cannot retrive information about power plans' { BeforeEach { Mock -CommandName Get-CimInstance -MockWith { @@ -152,7 +151,7 @@ try Context 'When the system is in the desired present state' { BeforeEach { Mock -CommandName Get-CimInstance -MockWith { - return New-Object Object | + return New-Object Object | Add-Member -MemberType NoteProperty -Name IsActive -Value $true -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable } @@ -165,7 +164,7 @@ try Context 'When the system is not in the desired state' { BeforeEach { Mock -CommandName Get-CimInstance -MockWith { - return New-Object Object | + return New-Object Object | Add-Member -MemberType NoteProperty -Name IsActive -Value $false -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable } diff --git a/Tests/Unit/MSFT_xScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_xScheduledTask.Tests.ps1 index c2a735eb..b7b6e896 100644 --- a/Tests/Unit/MSFT_xScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_xScheduledTask.Tests.ps1 @@ -2,25 +2,28 @@ param( ) -$Global:DSCModuleName = 'xComputerManagement' -$Global:DSCResourceName = 'MSFT_xScheduledTask' +$script:DSCModuleName = 'xComputerManagement' +$script:DSCResourceName = 'MSFT_xScheduledTask' +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global - -#region HEADER -# Unit Test Template Version: 1.1.0 -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` - -TestType Unit + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER + +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global $VerbosePreference = 'Continue' # Begin Testing @@ -28,51 +31,56 @@ try { #region Pester Tests - InModuleScope $Global:DSCResourceName { + InModuleScope $script:DSCResourceName { + $script:DSCResourceName = 'MSFT_xScheduledTask' + + Describe $script:DSCResourceName { + BeforeAll { + Mock -CommandName Register-ScheduledTask + Mock -CommandName Set-ScheduledTask + Mock -CommandName Unregister-ScheduledTask + } - Describe $Global:DSCResourceName { - - Mock Register-ScheduledTask { } - Mock Set-ScheduledTask { } - Mock Unregister-ScheduledTask { } - Context 'No scheduled task exists, but it should' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Minutes 150) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Minutes 150).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return $null } - It 'should return absent from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Absent' + Mock -CommandName Get-ScheduledTask { return $null } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Absent' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should create the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should create the scheduled task in the set method' { + Set-TargetResource @testParams } } - + Context 'A scheduled task exists, but it should not' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Minutes 15) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Minutes 15).ToString() Ensure = 'Absent' + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -80,8 +88,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalMinutes)M" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalMinutes)M" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -91,21 +99,22 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should remove the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should remove the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled Unregister-ScheduledTask } } - + Context 'A scheduled task doesnt exist, and it should not' { $testParams = @{ TaskName = 'Test task' @@ -113,30 +122,33 @@ try ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' Ensure = 'Absent' + Verbose = $True } - - Mock Get-ScheduledTask { return $null } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Absent' + + Mock -CommandName Get-ScheduledTask { return $null } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Absent' } - - It 'should return true from the test method' { + + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task with Once based repetition exists, but has the wrong settings' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Minutes 150) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Minutes 150).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -144,8 +156,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = $null - Interval = "PT$(($testParams.RepeatInterval.TimeOfDay.TotalMinutes) + 1)M" + Duration = '' + Interval = "PT$(([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes) + 1)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -155,33 +167,35 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A scheduled task with minutes based repetition exists and has the correct settings' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Minutes 30) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Minutes 30).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -189,8 +203,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalMinutes)M" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalMinutes)M" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -200,27 +214,29 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return true from the test method' { + + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task with hourly based repetition exists, but has the wrong settings' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Hours 4) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Hours 4).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -228,8 +244,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$(($testParams.RepetitionDuration.TimeOfDay.TotalHours))H" - Interval = "PT$(($testParams.RepeatInterval.TimeOfDay.TotalHours) + 1)H" + Duration = "PT$(([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours))H" + Interval = "PT$(([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalHours) + 1)H" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -239,33 +255,35 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A scheduled task with hourly based repetition exists and has the correct settings' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Hours 4) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Hours 4).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -273,8 +291,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalHours)H" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalHours)H" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -284,16 +302,17 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return true from the test method' { + + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task with daily based repetition exists, but has the wrong settings' { $testParams = @{ TaskName = 'Test task' @@ -301,9 +320,10 @@ try ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 3 + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -322,22 +342,23 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A scheduled task with daily based repetition exists and has the correct settings' { $testParams = @{ TaskName = 'Test task' @@ -345,9 +366,10 @@ try ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Daily' DaysInterval = 3 + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -363,28 +385,30 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return true from the test method' { + + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task exists and is configured with the wrong execution account' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [DateTime]::Today.Add((New-TimeSpan -Minutes 15)) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() ExecuteAsCredential = New-Object System.Management.Automation.PSCredential ('DEMO\RightUser', (ConvertTo-SecureString 'ExamplePassword' -AsPlainText -Force)) + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -392,8 +416,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -403,22 +427,23 @@ try UserId = 'WrongUser' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A scheduled task exists and is configured with the wrong working directory' { $testParams = @{ TaskName = 'Test task' @@ -426,11 +451,12 @@ try ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ActionWorkingPath = 'C:\Example' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -440,7 +466,7 @@ try Triggers = @(@{ Repetition = @{ Duration = $null - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -450,22 +476,23 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A scheduled task exists and is configured with the wrong executable arguments' { $testParams = @{ TaskName = 'Test task' @@ -473,11 +500,12 @@ try ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ActionArguments = '-File "C:\something\right.ps1"' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -486,8 +514,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -497,34 +525,36 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A scheduled task is enabled and should be disabled' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() Enable = $false + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -533,8 +563,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -547,35 +577,42 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } - + } - + Context 'A scheduled task is enabled and has the correct settings' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + RandomDelay = (New-TimeSpan -Minutes 4).ToString() + IdleWaitTimeout = (New-TimeSpan -Minutes 5).ToString() + IdleDuration = (New-TimeSpan -Minutes 6).ToString() + ExecutionTimeLimit = (New-TimeSpan -Minutes 7).ToString() + RestartInterval = (New-TimeSpan -Minutes 8).ToString() Enable = $true + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -584,42 +621,51 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } + RandomDelay = "PT$([System.TimeSpan]::Parse($testParams.RandomDelay).TotalMinutes)M" CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' } }) Settings = @(@{ Enabled = $true + IdleSettings = @{ + IdleWaitTimeout = "PT$([System.TimeSpan]::Parse($testParams.IdleWaitTimeout).TotalMinutes)M" + IdleDuration = "PT$([System.TimeSpan]::Parse($testParams.IdleDuration).TotalMinutes)M" + } + ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParams.ExecutionTimeLimit).TotalMinutes)M" + RestartInterval = "PT$([System.TimeSpan]::Parse($testParams.RestartInterval).TotalMinutes)M" }) Principal = @{ UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return true from the test method' { + + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task is disabled and has the correct settings' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() Enable = $false + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -628,8 +674,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -642,28 +688,30 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return true from the test method' { + + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task is disabled but should be enabled' { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() Enable = $true + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -672,8 +720,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -686,33 +734,35 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - - It 'should return false from the test method' { + + It 'Should return false from the test method' { Test-TargetResource @testParams | Should Be $false } - - It 'should update the scheduled task in the set method' { - Set-TargetResource @testParams -Verbose + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 } } - + Context 'A Scheduled task exists, is disabled, and the optional parameter enable is not specified' -Fixture { $testParams = @{ TaskName = 'Test task' TaskPath = '\Test\' ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' ScheduleType = 'Once' - RepeatInterval = [datetime]::Today + (New-TimeSpan -Minutes 15) - RepetitionDuration = [datetime]::Today + (New-TimeSpan -Hours 8) + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + Verbose = $True } - - Mock Get-ScheduledTask { return @{ + + Mock -CommandName Get-ScheduledTask { return @{ TaskName = $testParams.TaskName TaskPath = $testParams.TaskPath Actions = @(@{ @@ -721,8 +771,8 @@ try }) Triggers = @(@{ Repetition = @{ - Duration = "PT$($testParams.RepetitionDuration.TimeOfDay.TotalHours)H" - Interval = "PT$($testParams.RepeatInterval.TimeOfDay.TotalMinutes)M" + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" } CimClass = @{ CimClassName = 'MSFT_TaskTimeTrigger' @@ -735,37 +785,300 @@ try UserId = 'SYSTEM' } } } - - It 'should return present from the get method' { - (Get-TargetResource @testParams).Ensure | Should Be 'Present' + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' } - + It 'Should return true from the test method' { Test-TargetResource @testParams | Should Be $true } } - + Context 'A scheduled task path is root or custom' -Fixture { - It 'should return backslash' { + It 'Should return backslash' { ConvertTo-NormalizedTaskPath -TaskPath '\'| Should Be '\' } - It 'should add backslash at the end' { + It 'Should add backslash at the end' { ConvertTo-NormalizedTaskPath -TaskPath '\Test'| Should Be '\Test\' } - It 'should add backslash at the beginning' { + It 'Should add backslash at the beginning' { ConvertTo-NormalizedTaskPath -TaskPath 'Test\'| Should Be '\Test\' } - It 'should add backslash at the beginning and at the end' { + It 'Should add backslash at the beginning and at the end' { ConvertTo-NormalizedTaskPath -TaskPath 'Test'| Should Be '\Test\' } - It 'should not add backslash' { + It 'Should not add backslash' { ConvertTo-NormalizedTaskPath -TaskPath '\Test\'| Should Be '\Test\' } } + + Context 'A scheduled task exists and is configured with the wrong interval, duration & random delay parameters' { + $testParams = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 20).ToString() + RepetitionDuration = (New-TimeSpan -Hours 9).ToString() + RandomDelay = (New-TimeSpan -Minutes 4).ToString() + IdleWaitTimeout = (New-TimeSpan -Minutes 5).ToString() + IdleDuration = (New-TimeSpan -Minutes 6).ToString() + ExecutionTimeLimit = (New-TimeSpan -Minutes 7).ToString() + RestartInterval = (New-TimeSpan -Minutes 8).ToString() + Verbose = $True + } + + Mock -CommandName Get-ScheduledTask { return @{ + TaskName = $testParams.TaskName + TaskPath = $testParams.TaskPath + Actions = @(@{ + Execute = $testParams.ActionExecutable + Arguments = $testParams.Arguments + }) + Triggers = @(@{ + Repetition = @{ + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours + 1)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes + 1)M" + } + RandomDelay = "PT$([System.TimeSpan]::Parse($testParams.RandomDelay).TotalMinutes + 1)M" + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + }) + Settings = @{ + IdleSettings = @{ + IdleWaitTimeout = "PT$([System.TimeSpan]::Parse($testParams.IdleWaitTimeout).TotalMinutes)M" + IdleDuration = "PT$([System.TimeSpan]::Parse($testParams.IdleDuration).TotalMinutes)M" + } + ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParams.ExecutionTimeLimit).TotalMinutes)M" + RestartInterval = "PT$([System.TimeSpan]::Parse($testParams.RestartInterval).TotalMinutes)M" + } + Principal = @{ + UserId = 'SYSTEM' + } + } } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' + } + + It 'Should return false from the test method' { + Test-TargetResource @testParams | Should Be $false + } + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams + Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 + Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 + } + } + + Context 'A scheduled task exists and is configured with the wrong idle timeout & idle duration parameters' { + $testParams = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 20).ToString() + RepetitionDuration = (New-TimeSpan -Hours 9).ToString() + RandomDelay = (New-TimeSpan -Minutes 4).ToString() + IdleWaitTimeout = (New-TimeSpan -Minutes 5).ToString() + IdleDuration = (New-TimeSpan -Minutes 6).ToString() + ExecutionTimeLimit = (New-TimeSpan -Minutes 7).ToString() + RestartInterval = (New-TimeSpan -Minutes 8).ToString() + Verbose = $True + } + + Mock -CommandName Get-ScheduledTask { return @{ + TaskName = $testParams.TaskName + TaskPath = $testParams.TaskPath + Actions = @(@{ + Execute = $testParams.ActionExecutable + Arguments = $testParams.Arguments + }) + Triggers = @(@{ + Repetition = @{ + Duration = "PT$([System.TimeSpan]::Parse($testParams.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" + } + RandomDelay = "PT$([System.TimeSpan]::Parse($testParams.RandomDelay).TotalMinutes)M" + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + }) + Settings = @{ + IdleSettings = @{ + IdleWaitTimeout = "PT$([System.TimeSpan]::Parse($testParams.IdleWaitTimeout).TotalMinutes + 1)M" + IdleDuration = "PT$([System.TimeSpan]::Parse($testParams.IdleDuration).TotalMinutes + 1)M" + } + ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParams.ExecutionTimeLimit).TotalMinutes)M" + RestartInterval = "PT$([System.TimeSpan]::Parse($testParams.RestartInterval).TotalMinutes)M" + } + Principal = @{ + UserId = 'SYSTEM' + } + } } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' + } + + It 'Should return false from the test method' { + Test-TargetResource @testParams | Should Be $false + } + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams + Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 + Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 + } + } + + Context 'A scheduled task exists and is configured with the wrong duration parameter for an indefinite trigger' { + $testParams = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 20).ToString() + RepetitionDuration = 'Indefinitely' + Verbose = $True + } + + Mock -CommandName Get-ScheduledTask { return @{ + TaskName = $testParams.TaskName + TaskPath = $testParams.TaskPath + Actions = @(@{ + Execute = $testParams.ActionExecutable + Arguments = $testParams.Arguments + }) + Triggers = @(@{ + Repetition = @{ + Duration = "PT4H" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" + } + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + }) + Principal = @{ + UserId = 'SYSTEM' + } + } } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' + } + + It 'Should return false from the test method' { + Test-TargetResource @testParams | Should Be $false + } + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams + Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 + Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 + } + } + + Context 'A scheduled task exists and is configured with indefinite repetition duration for a trigger but should be fixed' { + $testParams = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 20).ToString() + RepetitionDuration = (New-TimeSpan -Hours 9).ToString() + Verbose = $True + } + + Mock -CommandName Get-ScheduledTask { return @{ + TaskName = $testParams.TaskName + TaskPath = $testParams.TaskPath + Actions = @(@{ + Execute = $testParams.ActionExecutable + Arguments = $testParams.Arguments + }) + Triggers = @(@{ + Repetition = @{ + Duration = "" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" + } + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + }) + Principal = @{ + UserId = 'SYSTEM' + } + } } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' + } + + It 'Should return false from the test method' { + Test-TargetResource @testParams | Should Be $false + } + + It 'Should update the scheduled task in the set method' { + Set-TargetResource @testParams + Assert-MockCalled -CommandName Unregister-ScheduledTask -Times 1 + Assert-Mockcalled -CommandName Register-ScheduledTask -Times 1 + } + } + + Context 'A scheduled task exists and is configured with correctly with an indefinite duration trigger' { + $testParams = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 20).ToString() + RepetitionDuration = 'Indefinitely' + Verbose = $True + } + + Mock -CommandName Get-ScheduledTask { return @{ + TaskName = $testParams.TaskName + TaskPath = $testParams.TaskPath + Actions = @(@{ + Execute = $testParams.ActionExecutable + Arguments = $testParams.Arguments + }) + Triggers = @(@{ + Repetition = @{ + Duration = "" + Interval = "PT$([System.TimeSpan]::Parse($testParams.RepeatInterval).TotalMinutes)M" + } + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + }) + Principal = @{ + UserId = 'SYSTEM' + } + } } + + It 'Should return the correct values from Get-TargetResource' { + $result = Get-TargetResource @testParams + $result.Ensure | Should Be 'Present' + } + + It 'Should return true from the test method' { + Test-TargetResource @testParams | Should Be $true + } + } } } #endregion diff --git a/Tests/Unit/MSFT_xVirtualMemory.Tests.ps1 b/Tests/Unit/MSFT_xVirtualMemory.Tests.ps1 index 1804ed90..76cff16e 100644 --- a/Tests/Unit/MSFT_xVirtualMemory.Tests.ps1 +++ b/Tests/Unit/MSFT_xVirtualMemory.Tests.ps1 @@ -1,6 +1,8 @@ #region HEADER -$script:DSCModuleName = 'xComputerManagement' -$script:DSCResourceName = 'MSFT_xVirtualMemory' +$script:DSCModuleName = 'xComputerManagement' +$script:DSCResourceName = 'MSFT_xVirtualMemory' + +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global # Unit Test Template Version: 1.2.0 $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) @@ -10,12 +12,11 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR } Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force - + $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` -TestType Unit - #endregion HEADER function Invoke-TestSetup { @@ -29,171 +30,1110 @@ function Invoke-TestCleanup { try { Invoke-TestSetup - InModuleScope 'MSFT_xVirtualMemory' { + InModuleScope 'MSFT_xVirtualMemory' { + <# + Remove-CimInstance overridden to enable PSObject + to be passed to mocked version. + #> + function Remove-CimInstance { + param + ( + $InputObject + ) + } + + $testDrive = 'K:' + $testInitialSize = 10 + $testMaximumSize = 20 + $testPageFileName = "$testDrive\pagefile.sys" - Describe "$($script:DSCResourceName)\Get-TargetResource" { + $mockGetDriveInfo = { + [PSObject] @{ + Name = "$testDrive\" + } + } + + $mockAutomaticPagefileEnabled = { + [PSObject] @{ + AutomaticManagedPageFile = $true + Name = $testPageFileName + } + } + $mockAutomaticPagefileDisabled = { + [PSObject] @{ + AutomaticManagedPageFile = $false + Name = $testPageFileName + } + } + + $mockPageFileSetting = { + [PSObject] @{ + Name = $testPageFileName + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + } + } + + $parameterFilterGetPageFileSetting = { + $Drive -eq $testDrive + } + + $parameterFilterSetPageFileSetting = { + $Namespace -eq 'root\cimv2' -and ` + $Query -eq "Select * from Win32_PageFileSetting where SettingID='pagefile.sys @ $testDrive'" -and ` + $Property.InitialSize -eq $testInitialSize -and ` + $Property.MaximumSize -eq $testMaximumSize + } + + $parameterFilterEnableAutoManagePaging = { + $Namespace -eq 'root\cimv2' -and ` + $Query -eq 'Select * from Win32_ComputerSystem' -and ` + $Property.AutomaticManagedPageFile -eq $True + } + + $parameterFilterDisableAutoManagePaging = { + $Namespace -eq 'root\cimv2' -and ` + $Query -eq 'Select * from Win32_ComputerSystem' -and ` + $Property.AutomaticManagedPageFile -eq $False + } + + $parameterFilterNewPageFileSetting = { + $Namespace -eq 'root\cimv2' -and ` + $ClassName -eq 'Win32_PageFileSetting' -and ` + $Property.Name -eq $testPageFileName + } + + $parameterFilterComputerSystem = { + $ClassName -eq 'Win32_ComputerSystem' + } + + $parameterFilterPageFileSetting = { + $ClassName -eq 'Win32_PageFileSetting' -and ` + $Filter -eq "SettingID='pagefile.sys @ $testDrive'" + } + + Describe 'MSFT_xVirtualMemory\Get-TargetResource' { BeforeEach { $testParameters = @{ - Drive = 'D:' - Type = 'CustomSize' + Drive = $testDrive + Type = 'CustomSize' + Verbose = $true } } - - Context 'When the system is in the desired present state' { - BeforeEach { - Mock -CommandName Get-CimInstance -MockWith { - [PSObject] @{ - AutomaticManagedPageFile = $false - Name = 'D:\pagefile.sys' - } - } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When automatic managed page file is enabled' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + It 'Should return type set to AutoManagePagingFile' { + $result = Get-TargetResource @testParameters + $result.Type | Should Be 'AutoManagePagingFile' + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 } + } + + Context 'When automatic managed page file is disabled and no page file set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting - It 'It should return the same values as passed as parameters' { + It 'Should return type set to NoPagingFile' { $result = Get-TargetResource @testParameters - $result.Type | Should Be $testParameters.Type - $result.Drive | Should Be ([System.IO.DriveInfo]$testParameters.Drive).Name + $result.Type | Should Be 'NoPagingFile' + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 } } - - Context 'When the system is not in the desired present state' { - BeforeEach { - Mock -CommandName Get-CimInstance -MockWith { + + Context 'When automatic managed page file is disabled and system managed size is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { [PSObject] @{ InitialSize = 0 MaximumSize = 0 - AutomaticManagedPageFile = $false - Name = "C:\pagefile.sys" + Name = "$testDrive\" } - } -ModuleName $script:DSCResourceName -Verifiable - } + } - It 'It should not return a valid type' { + It 'Should return a expected type and drive letter' { $result = Get-TargetResource @testParameters - $result.Type | Should Not Be $testParameters.Type + $result.Type | Should Be 'SystemManagedSize' + $result.Drive | Should Be ([System.IO.DriveInfo] $testParameters.Drive).Name } - - It 'It should not return a valid drive letter' { - $result = Get-TargetResource @testParameters - $result.Drive | Should Not Be ([System.IO.DriveInfo]$testParameters.Drive).Name + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 } } - Assert-VerifiableMocks + Context 'When automatic managed page file is disabled and custom size is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = 10 + MaximumSize = 20 + Name = "$testDrive\" + } + } + + It 'Should return expected type and drive letter' { + $result = Get-TargetResource @testParameters + $result.Type | Should Be 'CustomSize' + $result.Drive | Should Be ([System.IO.DriveInfo] $testParameters.Drive).Name + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } + } } - - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Context 'When the system is not in the desired state' { - BeforeEach { + Describe 'MSFT_xVirtualMemory\Set-TargetResource' { + BeforeEach { + <# + These mocks are to handle when disk drive + used for testing does not exist. + #> + Mock ` + -CommandName Get-DriveInfo ` + -ParameterFilter { $Drive -eq $testDrive } ` + -MockWith $mockGetDriveInfo + + Mock ` + -CommandName Join-Path ` + -ParameterFilter { + $Path -eq "$testDrive\" -and ` + $ChildPath -eq 'pagefile.sys' + } ` + -MockWith { "$testDrive\pagefile.sys"} + + } + + Context 'When automatic managed page file should be enabled' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Enable' } + + It 'Should not throw an exception' { $testParameters = @{ - Drive = 'C:' - Type = 'CustomSize' + Drive = $testDrive + Type = 'AutoManagePagingFile' InitialSize = 0 - MaximumSize = 1337 - } - } - Mock -CommandName Set-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName New-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Remove-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Get-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - It 'Should call the mocked function Set-CimInstance exactly once' { - Set-TargetResource @testParameters - - Assert-MockCalled -CommandName Set-CimInstance -Exactly -Times 1 -Scope It - } - } - - - context 'When an exception is expected' { - Mock -CommandName Set-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName New-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Remove-CimInstance -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Get-CimInstance -MockWith { - [PSObject] @{ - InitialSize = 0 - MaximumSize = 1338 - Name = "D:\pagefile.sys" - AutomaticManagedPageFile = $false + MaximumSize = 0 + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Enable' } ` + -Exactly -Times 1 + } + } + + Context 'CustomSize is required' { + Context 'When automatic managed page file is enabled and no page file set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } + + Mock ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } + + Mock ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive -and ` + $InitialSize -eq $testInitialSize -and ` + $MaximumSize -eq $testMaximumSize } + + It 'Should not throw an exception' { + $testParameters = @{ + Drive = $testDrive + Type = 'CustomSize' + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive -and ` + $InitialSize -eq $testInitialSize -and ` + $MaximumSize -eq $testMaximumSize + } ` + -Exactly -Times 1 + } } - $testParameters = @{ - Drive = 'abc' - Type = 'CustomSize' - InitialSize = 0 - MaximumSize = 1337 - } - It 'Should throw if no valid drive letter has been used' { - { Set-TargetResource @testParameters } | Should Throw + Context 'When automatic managed page file is enabled and page file is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -MockWith $mockPageFileSetting + + Mock ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } + + Mock ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive -and ` + $InitialSize -eq $testInitialSize -and ` + $MaximumSize -eq $testMaximumSize + } + + It 'Should not throw an exception' { + $testParameters = @{ + Drive = $testDrive + Type = 'CustomSize' + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive -and ` + $InitialSize -eq $testInitialSize -and ` + $MaximumSize -eq $testMaximumSize + } ` + -Exactly -Times 1 + } } - $testParameters = @{ - Drive = 'N:' - Type = 'CustomSize' - InitialSize = 0 - MaximumSize = 1337 - } - It 'Should throw if the drive is not ready' { - { Set-TargetResource @testParameters } | Should Throw + Context 'SystemManagedSize is required' { + Context 'When automatic managed page file is enabled and no page file set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } + + Mock ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } + + Mock ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive + } + + It 'Should not throw an exception' { + $testParameters = @{ + Drive = $testDrive + Type = 'SystemManagedSize' + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive + } ` + -Exactly -Times 1 + } + } } - } - Assert-VerifiableMocks + Context 'When automatic managed page file is enabled and page file is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -MockWith $mockPageFileSetting + + Mock ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } + + Mock ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive + } + + It 'Should not throw an exception' { + $testParameters = @{ + Drive = $testDrive + Type = 'SystemManagedSize' + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName New-PageFile ` + -ParameterFilter { $PageFileName -eq $testPageFileName } ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Set-PageFileSetting ` + -ParameterFilter { + $Drive -eq $testDrive + } ` + -Exactly -Times 1 + } + } + + Context 'NoPagingFile is required' { + Context 'When automatic managed page file is enabled and no page file set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } + + Mock ` + -CommandName Remove-CimInstance + + It 'Should not throw an exception' { + $testParameters = @{ + Drive = $testDrive + Type = 'NoPagingFile' + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Remove-CimInstance ` + -Exactly -Times 0 + } + } + } + + Context 'When automatic managed page file is enabled and page file is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + Mock ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -MockWith $mockPageFileSetting + + Mock ` + -CommandName Remove-CimInstance + + It 'Should not throw an exception' { + $testParameters = @{ + Drive = $testDrive + Type = 'NoPagingFile' + Verbose = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Set-AutoManagePaging ` + -ParameterFilter { $State -eq 'Disable' } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter { $Drive -eq $testDrive } ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Remove-CimInstance ` + -Exactly -Times 1 + } + } + } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When a True or False is expected' { - BeforeEach { - $testParameters = @{ - Drive = 'D:' - Type = 'CustomSize' - InitialSize = 0 - MaximumSize = 1337 - } + Describe 'MSFT_xVirtualMemory\Test-TargetResource' { + Context 'In desired state' { + Context 'When automatic managed page file is enabled' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileEnabled + + It 'Should return true' { + $testParameters = @{ + Drive = $testDrive + Type = 'AutoManagePagingFile' + InitialSize = 0 + MaximumSize = 0 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + } } - $pageFileObject = [PSObject] @{ - InitialSize = 0 - MaximumSize = 1338 - Name = "D:\pagefile.sys" - AutomaticManagedPageFile = $false + Context 'When automatic managed page file is disabled and no page file set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting + + It 'Should return true' { + $testParameters = @{ + Drive = $testDrive + Type = 'NoPagingFile' + InitialSize = 0 + MaximumSize = 0 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } + } + + Context 'When automatic managed page file is disabled and system managed size is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = 0 + MaximumSize = 0 + Name = "$testDrive\" + } + } + + It 'Should return true' { + $testParameters = @{ + Drive = $testDrive + Type = 'SystemManagedSize' + InitialSize = 0 + MaximumSize = 0 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } + } + + Context 'When automatic managed page file is disabled and custom size is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Name = "$testDrive\" + } + } + + It 'Should return true' { + $testParameters = @{ + Drive = $testDrive + Type = 'CustomSize' + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } + } + } + + Context 'Not in desired state' { + Context 'When automatic managed page file is enabled' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + It 'Should return false' { + $testParameters = @{ + Drive = $testDrive + Type = 'AutoManagePagingFile' + InitialSize = 0 + MaximumSize = 0 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 } - - Mock -CommandName Get-CimInstance -MockWith { - $pageFileObject.MaximumSize = 1337 - $pageFileObject } - It 'Should return True if the input matches the actual values' { - Test-TargetResource @testParameters | Should Be $true + + Context 'When automatic managed page file is disabled and no page file set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Name = "$testDrive\" + } + } + + It 'Should return false' { + $testParameters = @{ + Drive = $testDrive + Type = 'NoPagingFile' + InitialSize = 0 + MaximumSize = 0 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } } - Mock -CommandName Get-CimInstance -MockWith { - $pageFileObject.MaximumSize = 1337 - $pageFileObject.AutomaticManagedPageFile = $true - $pageFileObject + Context 'When automatic managed page file is disabled and system managed size is set' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Name = "$testDrive\" + } + } + + It 'Should return false' { + $testParameters = @{ + Drive = $testDrive + Type = 'SystemManagedSize' + InitialSize = 0 + MaximumSize = 0 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } + } + + Context 'When automatic managed page file is disabled and custom size is set and initial size differs' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Name = "$testDrive\" + } + } + + It 'Should return false' { + $testParameters = @{ + Drive = $testDrive + Type = 'CustomSize' + InitialSize = $testInitialSize + 10 + MaximumSize = $testMaximumSize + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } } - It 'Should return False if the type is wrong' { - Test-TargetResource @testParameters | Should Be $false + + Context 'When automatic managed page file is disabled and custom size is set and maximum size differs' { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -MockWith $mockAutomaticPagefileDisabled + + Mock ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Name = "$testDrive\" + } + } + + It 'Should return false' { + $testParameters = @{ + Drive = $testDrive + Type = 'CustomSize' + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + 10 + Verbose = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterComputerSystem ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-PageFileSetting ` + -ParameterFilter $parameterFilterGetPageFileSetting ` + -Exactly -Times 1 + } } + } - Mock -CommandName Get-CimInstance -MockWith { - $pageFileObject.MaximumSize = 1338 - $pageFileObject + Describe 'MSFT_xVirtualMemory\Get-PageFileSetting' { + Context "Page file defined on drive $testDrive" { + Mock ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterPageFileSetting ` + -MockWith { + [PSObject] @{ + InitialSize = $testInitialSize + MaximumSize = $testMaximumSize + Name = "$testDrive\" + } + } + + It 'Should return the expected object' { + $result = Get-PageFileSetting -Drive $testDrive -Verbose + $result.InitialSize | Should Be $testInitialSize + $result.MaximumSize | Should Be $testMaximumSize + $result.Name | Should Be "$testDrive\" + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Get-CimInstance ` + -ParameterFilter $parameterFilterPageFileSetting ` + -Exactly -Times 1 + } } - It 'Should return False if InitialSize and/or MaximumSize do not match' { - Test-TargetResource @testParameters | Should Be $false + } + + Describe 'MSFT_xVirtualMemory\Set-PageFileSetting' { + Context "Set page file settings on drive $testDrive" { + Mock ` + -CommandName Set-CimInstance ` + -ParameterFilter $parameterFilterSetPageFileSetting + + It 'Should not throw an exception' { + { + Set-PageFileSetting ` + -Drive $testDrive ` + -InitialSize $testInitialSize ` + -MaximumSize $testMaximumSize ` + -Verbose + } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Set-CimInstance ` + -ParameterFilter $parameterFilterSetPageFileSetting ` + -Exactly -Times 1 + } } + } + + Describe 'MSFT_xVirtualMemory\Set-AutoManagePaging' { + Context "Enable auto managed page file" { + Mock ` + -CommandName Set-CimInstance ` + -ParameterFilter $parameterFilterEnableAutoManagePaging + + It 'Should not throw an exception' { + { Set-AutoManagePaging -State Enable -Verbose } | Should Not Throw + } - Mock -CommandName Get-CimInstance -MockWith { - # In this case Get-CimInstance returns an empty object + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Set-CimInstance ` + -ParameterFilter $parameterFilterEnableAutoManagePaging ` + -Exactly -Times 1 + } } - It 'Should return False if Name does not match' { - Test-TargetResource @testParameters | Should Be $false + + Context "Disable auto managed page file" { + Mock ` + -CommandName Set-CimInstance ` + -ParameterFilter $parameterFilterDisableAutoManagePaging + + It 'Should not throw an exception' { + { Set-AutoManagePaging -State Disable -Verbose } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName Set-CimInstance ` + -ParameterFilter $parameterFilterDisableAutoManagePaging ` + -Exactly -Times 1 + } } + } + Describe 'MSFT_xVirtualMemory\New-PageFile' { + Context "Create a new page file" { + Mock ` + -CommandName New-CimInstance ` + -ParameterFilter $parameterFilterNewPageFileSetting + + It 'Should not throw an exception' { + { New-PageFile -PageFileName $testPageFileName -Verbose } | Should Not Throw + } + + It 'Should call the correct mocks' { + Assert-MockCalled ` + -CommandName New-CimInstance ` + -ParameterFilter $parameterFilterNewPageFileSetting ` + -Exactly -Times 1 + } + } } } } @@ -201,4 +1141,3 @@ try { finally { Invoke-TestCleanup } - diff --git a/xComputerManagement.psd1 b/xComputerManagement.psd1 index f6061013..086c0f91 100644 --- a/xComputerManagement.psd1 +++ b/xComputerManagement.psd1 @@ -1,6 +1,6 @@ @{ # Version number of this module. -ModuleVersion = '2.1.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = 'B5004952-489E-43EA-999C-F16A25355B89' @@ -49,38 +49,27 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '* xComputer: Changed comparison that validates if we are in the correct AD - Domain to work correctly if FQDN wasn"t used. -* Updated AppVeyor.yml to use AppVeyor.psm1 module in DSCResource.Tests. -* Removed Markdown.md errors. -* Added CodeCov.io support. -* xScheduledTask - * Fixed incorrect TaskPath handling - [Issue 45](https://github.com/PowerShell/xComputerManagement/issues/45) -* Change examples to meet HQRM standards and optin to Example validation - tests. -* Replaced examples in README.MD to links to Example files. -* Added the VS Code PowerShell extension formatting settings that cause PowerShell - files to be formatted as per the DSC Resource kit style guidelines - [Issue 91](https://github.com/PowerShell/xComputerManagement/issues/91). -* Opted into Common Tests "Validate Module Files" and "Validate Script Files". -* Converted files with UTF8 with BOM over to UTF8 - fixes [Issue 90](https://github.com/PowerShell/xComputerManagement/issues/90). -* Updated Year to 2017 in License and Manifest - fixes [Issue 87](https://github.com/PowerShell/xComputerManagement/issues/87). -* Added .github support files - fixes [Issue 88](https://github.com/PowerShell/xComputerManagement/issues/88): - * CONTRIBUTING.md - * ISSUE_TEMPLATE.md - * PULL_REQUEST_TEMPLATE.md -* Resolved all PSScriptAnalyzer warnings and style guide warnings. -* xOfflineDomainJoin: - * Changed to use CommonResourceHelper to load localization strings. - * Renamed en-US to be correct case so that localization strings can be loaded. - * Suppress PSScriptAnalyzer rule PSAvoidGlobalVars for - `$global:DSCMachineStatus = 1`. + ReleaseNotes = '* xComputer: Added parameter to set the local computer description along with documentation + and unit tests for this change. +* BREAKING CHANGE: xScheduledTask: + * Converted all Interval/Duration type parameters over to be string format + to prevent the Timezone the MOF file was created in from being stored. + This is to fix problems where MOF files are created in one timezone but + deployed nodes to a different timezone - See [Issue 85](https://github.com/PowerShell/xComputerManagement/issues/85) + * Added ConvertTo-TimeSpanFromScheduledTaskString function and refactored + to reduce code duplication. + * Added support for setting repetition duration to `Indefinitely`. * xComputer: - * Suppress PSScriptAnalyzer rule PSAvoidGlobalVars for - `$global:DSCMachineStatus = 1`. + * Moved strings to localization file. + * Updated to meet HQRM guidelines. * xVirtualMemory: - * Suppress PSScriptAnalyzer rule PSAvoidGlobalVars for - `$global:DSCMachineStatus = 1`. - + * Refactored shared common code into new utility functions to + reduce code duplication and improve testability. + * Moved strings into localizable strings file. + * Converted calls to `throw` to use `New-InvalidOperationException` + in CommonResourceHelper. + * Improved unit test coverage. + * Updated to meet HQRM guidelines. ' } # End of PSData hashtable @@ -93,3 +82,4 @@ PrivateData = @{ +