From 140a78296812a2455ac14d47b3012604b7bd45e7 Mon Sep 17 00:00:00 2001 From: "AEMO\\DWork" Date: Mon, 15 Oct 2018 07:51:59 +1000 Subject: [PATCH 1/5] ScheduledTask: Added BuiltInUser (Issue #130) & Fixed IdleWaitTimeout Null (Issue #186) --- CHANGELOG.md | 3 + .../MSFT_ScheduledTask.psm1 | 105 ++++++++++++++++-- .../MSFT_ScheduledTask.schema.mof | 35 +++--- .../en-US/MSFT_ScheduledTask.strings.psd1 | 2 +- ...eScheduledTasksAsBuiltinServiceAccount.ps1 | 37 ++++++ Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 91 ++++++++++++--- 6 files changed, 228 insertions(+), 45 deletions(-) create mode 100644 Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a064d980..ecd1ba08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Unreleased - ScheduledTask: + - IdleWaitTimeout returned from Get-TargetResource always null - Fixes [Issue #186](https://github.com/PowerShell/ComputerManagementDsc/issues/186). + - Added BuiltInAccount Property to allow running task as one of the build in + service accounts - Fixes [Issue #130](https://github.com/PowerShell/ComputerManagementDsc/issues/130). - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index 53f9d8c3..c180948e 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -76,6 +76,10 @@ $script:localizedData = Get-LocalizedData ` True if the task should be enabled, false if it should be disabled. Not used in Get-TargetResource. + .PARAMETER BuiltInAccount + Run the task as one of the built in service accounts. + When set ExecuteAsCredential will be ignored and LogonType will be set to 'ServiceAccount' + .PARAMETER ExecuteAsCredential The credential this task should execute as. If not specified defaults to running as the local system account. Cannot be used in combination with ExecuteAsGMSA. @@ -277,6 +281,10 @@ function Get-TargetResource [System.Boolean] $Enable = $true, + [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] + [System.String] + $BuiltInAccount, + [Parameter()] [System.Management.Automation.PSCredential] $ExecuteAsCredential, @@ -511,7 +519,7 @@ function Get-TargetResource $returnSynchronizeAcrossTimeZone = $false } - return @{ + $result = @{ TaskName = $task.TaskName TaskPath = $task.TaskPath StartTime = $startAt @@ -538,7 +546,7 @@ function Get-TargetResource AllowStartIfOnBatteries = -not $settings.DisallowStartIfOnBatteries Hidden = $settings.Hidden RunOnlyIfIdle = $settings.RunOnlyIfIdle - IdleWaitTimeout = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.IdleSettings.IdleWaitTimeout + IdleWaitTimeout = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $settings.IdleSettings.WaitTimeout NetworkName = $settings.NetworkSettings.Name DisallowStartOnRemoteAppSession = $settings.DisallowStartOnRemoteAppSession StartWhenAvailable = $settings.StartWhenAvailable @@ -558,6 +566,13 @@ function Get-TargetResource EventSubscription = $trigger.Subscription Delay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Delay } + + if (($result.ContainsKey('LogonType')) -and ($result['LogonType'] -ieq 'ServiceAccount')) + { + $result.Add('BuiltInAccount', $task.Principal.UserId) + } + + return $result } } @@ -604,6 +619,10 @@ function Get-TargetResource .PARAMETER Enable True if the task should be enabled, false if it should be disabled. + .PARAMETER BuiltInAccount + Run the task as one of the built in service accounts. + When set ExecuteAsCredential will be ignored and LogonType will be set to 'ServiceAccount' + .PARAMETER ExecuteAsCredential The credential this task should execute as. If not specified defaults to running as the local system account. Cannot be used in combination with ExecuteAsGMSA. @@ -786,6 +805,10 @@ function Set-TargetResource [System.Boolean] $Enable = $true, + [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] + [System.String] + $BuiltInAccount, + [Parameter()] [System.Management.Automation.PSCredential] $ExecuteAsCredential, @@ -990,14 +1013,15 @@ function Set-TargetResource -ArgumentName EventSubscription } - if ($ExecuteAsCredential -and $ExecuteAsGMSA) + if ($ExecuteAsGMSA -and ($ExecuteAsCredential -or $BuiltInAccount)) { New-InvalidArgumentException ` -Message ($script:localizedData.gMSAandCredentialError) ` -ArgumentName ExecuteAsGMSA } - if($SynchronizeAcrossTimeZone -and ($ScheduleType -notin @('Once', 'Daily', 'Weekly'))) { + if ($SynchronizeAcrossTimeZone -and ($ScheduleType -notin @('Once', 'Daily', 'Weekly'))) + { New-InvalidArgumentException ` -Message ($script:localizedData.SynchronizeAcrossTimeZoneInvalidScheduleType) ` -ArgumentName SynchronizeAcrossTimeZone @@ -1245,7 +1269,19 @@ function Set-TargetResource # Prepare the register arguments $registerArguments = @{} - if ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) + $username = $null + if ($PSBoundParameters.ContainsKey('BuiltInAccount')) + { + <# + The validateset on BuiltInAccount has already checked the + non-null value to be 'LOCAL SERVICE', 'NETWORK SERVICE' or + 'SYSTEM' + #> + $username = 'NT AUTHORITY\' + $BuiltInAccount + $registerArguments.Add('User', $username) + $LogonType = 'ServiceAccount' + } + elseif ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) { $username = $ExecuteAsGMSA $LogonType = 'Password' @@ -1269,6 +1305,11 @@ function Set-TargetResource } else { + <# + 'NT AUTHORITY\SYSTEM' basically gives the schedule task admin + privileges, should we default to 'NT AUTHORITY\LOCAL SERVICE' + instead? + #> $username = 'NT AUTHORITY\SYSTEM' $registerArguments.Add('User', $username) $LogonType = 'ServiceAccount' @@ -1331,7 +1372,7 @@ function Set-TargetResource $scheduledTask.Description = $Description } - if($scheduledTask.Triggers[0].StartBoundary) + if ($scheduledTask.Triggers[0].StartBoundary) { <# The way New-ScheduledTaskTrigger writes the StartBoundary has issues because it does not take @@ -1367,8 +1408,8 @@ function Set-TargetResource # Register the scheduled task - $registerArguments.Add('TaskName',$TaskName) - $registerArguments.Add('TaskPath',$TaskPath) + $registerArguments.Add('TaskName', $TaskName) + $registerArguments.Add('TaskPath', $TaskPath) $registerArguments.Add('InputObject', $scheduledTask) $null = Register-ScheduledTask @registerArguments @@ -1426,6 +1467,10 @@ function Set-TargetResource .PARAMETER Enable True if the task should be enabled, false if it should be disabled. + .PARAMETER BuiltInAccount + Run the task as one of the built in service accounts. + When set ExecuteAsCredential will be ignored and LogonType will be set to 'ServiceAccount' + .PARAMETER ExecuteAsCredential The credential this task should execute as. If not specified defaults to running as the local system account. Cannot be used in combination with ExecuteAsGMSA. @@ -1609,6 +1654,10 @@ function Test-TargetResource [System.Boolean] $Enable = $true, + [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] + [System.String] + $BuiltInAccount, + [Parameter()] [System.Management.Automation.PSCredential] $ExecuteAsCredential, @@ -1824,12 +1873,40 @@ function Test-TargetResource return $false } - if ($PSBoundParameters.ContainsKey('ExecuteAsCredential')) + if ($PSBoundParameters.ContainsKey('BuiltInAccount')) + { + $PSBoundParameters.User = $BuiltInAccount + $currentValues.User = $BuiltInAccount + + $PSBoundParameters.ExecuteAsCredential = $BuiltInAccount + $currentValues.ExecuteAsCredential = $BuiltInAccount + + $PSBoundParameters['LogonType'] = 'ServiceAccount' + $currentValues['LogonType'] = 'ServiceAccount' + } + elseif ($PSBoundParameters.ContainsKey('ExecuteAsCredential')) { # The password of the execution credential can not be compared $username = $ExecuteAsCredential.UserName $PSBoundParameters['ExecuteAsCredential'] = $username } + else + { + # Must be running as System, login type is ServiceAccount + $PSBoundParameters['LogonType'] = 'ServiceAccount' + $currentValues['LogonType'] = 'ServiceAccount' + } + + if ($PSBoundParameters.ContainsKey('WeeksInterval') -and ((-not $currentValues.ContainsKey('WeeksInterval')) -or ($null -eq $currentValues['WeeksInterval']))) + { + <# + The WeeksInterval parameter is defaulted to 1, even when the + property is unset/undefined for the current task returned from + Get-TargetResouce initialise a missing or null WeeksInterval to + spurious calls to Set-TargetResouce + #> + $currentValues.WeeksInterval = $PSBoundParameters['WeeksInterval'] + } if ($PSBoundParameters.ContainsKey('ExecuteAsGMSA')) { @@ -1848,6 +1925,14 @@ function Test-TargetResource $desiredValues = $PSBoundParameters $desiredValues.TaskPath = $TaskPath + if ($desiredValues.ContainsKey('Verbose')) + { + <# + Initialise a missing or null Verbose to avoid spurious + calls to Set-TargetResouce + #> + $currentValues.Add('Verbose', $desiredValues['Verbose']) + } Write-Verbose -Message ($script:localizedData.TestingDscParameterStateMessage) @@ -2036,7 +2121,7 @@ Function Get-DateTimeString $format = (Get-Culture).DateTimeFormat.SortableDateTimePattern - if($SynchronizeAcrossTimeZone) + if ($SynchronizeAcrossTimeZone) { $returnDate = (Get-Date -Date $Date -Format $format) + (Get-Date -Format 'zzz') } diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof index 65255166..1ae7465f 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.schema.mof @@ -1,30 +1,31 @@ [ClassVersion("1.0.0.0"), FriendlyName("ScheduledTask")] class MSFT_ScheduledTask : OMI_BaseResource { - [Key, Description("The name of the task")] string TaskName; - [Write, Description("The path to the task - defaults to the root directory")] string TaskPath; - [Write, Description("The task description")] string Description; - [Write, Description("The path to the .exe for this task")] string ActionExecutable; - [Write, Description("The arguments to pass the executable")] string ActionArguments; - [Write, Description("The working path to specify for the executable")] string ActionWorkingPath; - [Write, Description("When should the task be executed"), ValueMap{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}, Values{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}] string ScheduleType; + [Key, Description("The name of the task.")] string TaskName; + [Write, Description("The path to the task - defaults to the root directory.")] string TaskPath; + [Write, Description("The task description.")] string Description; + [Write, Description("The path to the .exe for this task.")] string ActionExecutable; + [Write, Description("The arguments to pass the executable.")] string ActionArguments; + [Write, Description("The working path to specify for the executable.")] string ActionWorkingPath; + [Write, Description("When should the task be executed."), ValueMap{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}, Values{"Once", "Daily", "Weekly", "AtStartup", "AtLogOn", "OnEvent"}] string ScheduleType; [Write, Description("How many units (minutes, hours, days) between each run of this task?")] String RepeatInterval; - [Write, Description("The time of day this task should start at - defaults to 12:00 AM. Not valid for AtLogon and AtStartup tasks")] DateTime StartTime; + [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("Enable the scheduled task option to synchronize across time zones. This is enabled by including the timezone offset in the scheduled task trigger. Defaults to false which does not include the timezone offset.")] boolean SynchronizeAcrossTimeZone; - [Write, Description("Present if the task should exist, Absent if it should be removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; - [Write, Description("True if the task should be enabled, false if it should be disabled")] boolean Enable; - [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("The gMSA (Group Managed Service Account) this task should execute as. Cannot be used in combination with ExecuteAsCredential.")] string ExecuteAsGMSA; + [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("Run the task as one of the built in service accounts. When set ExecuteAsCredential will be ignored and LogonType will be set to 'ServiceAccount'."), ValueMap{"SYSTEM", "LOCAL SERVICE", "NETWORK SERVICE"}, Values{"SYSTEM", "LOCAL SERVICE", "NETWORK SERVICE"}] string BuiltInAccount; + [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("The gMSA (Group Managed Service Account) this task should execute as. Cannot be used in combination with ExecuteAsCredential or BuiltInAccount.")] string ExecuteAsGMSA; [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.")] 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; - [Write, Description("Indicates whether the task is prohibited to run on demand or not. Defaults to $false")] Boolean DisallowDemandStart; - [Write, Description("Indicates whether the task is prohibited to be terminated or not. Defaults to $false")] Boolean DisallowHardTerminate; + [Write, Description("Indicates whether the task is prohibited to run on demand or not. Defaults to $false.")] Boolean DisallowDemandStart; + [Write, Description("Indicates whether the task is prohibited to be terminated or not. Defaults to $false.")] Boolean DisallowHardTerminate; [Write, Description("The task compatibility level. Defaults to Vista."), ValueMap{"AT","V1","Vista","Win7","Win8"}, Values{"AT","V1","Vista","Win7","Win8"}] String Compatibility; - [Write, Description("Indicates whether the task should start if the machine is on batteries or not. Defaults to $false")] Boolean AllowStartIfOnBatteries; + [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.")] String IdleWaitTimeout; @@ -44,6 +45,6 @@ class MSFT_ScheduledTask : OMI_BaseResource [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; [Write, Description("Specifies the level of user rights that Task Scheduler uses to run the tasks that are associated with the principal. Defaults to 'Limited'."), ValueMap{"Limited","Highest"}, Values{"Limited","Highest"}] String RunLevel; [Write, Description("Specifies the security logon method that Task Scheduler uses to run the tasks that are associated with the principal."), ValueMap{"Group","Interactive","InteractiveOrPassword","None","Password","S4U","ServiceAccount"}, Values{"Group","Interactive","InteractiveOrPassword","None","Password","S4U","ServiceAccount"}] String LogonType; - [Write, Description("Specifies the EventSubscription in XML. This can be easily generated using the Windows Eventlog Viewer. For the query schema please check: https://docs.microsoft.com/en-us/windows/desktop/WES/queryschema-schema. Can only be used in combination with ScheduleType OnEvent")] String EventSubscription; - [Write, Description("Specifies a delay to the start of the trigger. The delay is a static delay before the task is executed. Can only be used in combination with ScheduleType OnEvent")] String Delay; + [Write, Description("Specifies the EventSubscription in XML. This can be easily generated using the Windows Eventlog Viewer. For the query schema please check: https://docs.microsoft.com/en-us/windows/desktop/WES/queryschema-schema. Can only be used in combination with ScheduleType OnEvent.")] String EventSubscription; + [Write, Description("Specifies a delay to the start of the trigger. The delay is a static delay before the task is executed. Can only be used in combination with ScheduleType OnEvent.")] String Delay; }; diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 index 9d3b4309..5281c977 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/en-US/MSFT_ScheduledTask.strings.psd1 @@ -11,7 +11,7 @@ ConvertFrom-StringData @' WeeksIntervalError = WeeksInterval must be greater than zero (0) for Weekly schedules. WeeksInterval specified is '{0}'. WeekDayMissingError = At least one weekday must be selected for Weekly schedule. OnEventSubscriptionError = No (valid) XML Event Subscription was provided. This is required when the scheduletype is OnEvent. - gMSAandCredentialError = Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two. + gMSAandCredentialError = Both ExecuteAsGMSA and (ExecuteAsCredential or BuiltInAccount) parameters have been specified. A task can run as a gMSA (Group Managed Service Account), a builtin service account or as a custom credential. Please modify your configuration to include just one of the three options. SynchronizeAcrossTimeZoneInvalidScheduleType = Setting SynchronizeAcrossTimeZone to true when the ScheduleType is not Once, Daily or Weekly is not a valid configuration. Please keep the default value of false when using other schedule types. TriggerCreationError = Error creating new scheduled task trigger. ConfigureTriggerRepetitionMessage = Configuring trigger repetition. diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 new file mode 100644 index 00000000..0d33f198 --- /dev/null +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example creates a scheduled task called 'TaskRunAsNetworkService' in + the folder root folder. The task is set to run every 15 minutes. + When run the task will start a new PowerShell instance running as the + builtin user NETWORK SERVICE. + The PowerShell instance will write the value of $env:USERNAME to the + file c:\temp\seeme.txt. + The contents of c:\temp\seeme.txt should be "NETWORK SERVICE". +#> +Configuration Example +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost' + ) + + Import-DscResource -ModuleName ComputerManagementDsc + + Node $NodeName + { + ScheduledTask ServiceEventManager + { + TaskName = 'TaskRunAsNetworkService' + Ensure = 'Present' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value $env:USERNAME -Force' + ScheduleType = 'Once' + RepeatInterval = '00:15:00' + RepetitionDuration = '4.00:00:00' + BuiltInAccount = 'NETWORK SERVICE' + } + } +} + diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index 6622572e..7bdc3d75 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -1010,7 +1010,7 @@ try Settings = [pscustomobject] @{ Enabled = $true IdleSettings = @{ - IdleWaitTimeout = "PT$([System.TimeSpan]::Parse($testParameters.IdleWaitTimeout).TotalMinutes)M" + WaitTimeout = "PT$([System.TimeSpan]::Parse($testParameters.IdleWaitTimeout).TotalMinutes)M" IdleDuration = "PT$([System.TimeSpan]::Parse($testParameters.IdleDuration).TotalMinutes)M" } ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.ExecutionTimeLimit).TotalMinutes)M" @@ -1254,7 +1254,7 @@ try ) Settings = [pscustomobject] @{ IdleSettings = @{ - IdleWaitTimeout = "PT$([System.TimeSpan]::Parse($testParameters.IdleWaitTimeout).TotalMinutes)M" + WaitTimeout = "PT$([System.TimeSpan]::Parse($testParameters.IdleWaitTimeout).TotalMinutes)M" IdleDuration = "PT$([System.TimeSpan]::Parse($testParameters.IdleDuration).TotalMinutes)M" } ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.ExecutionTimeLimit).TotalMinutes)M" @@ -1320,7 +1320,7 @@ try ) Settings = [pscustomobject] @{ IdleSettings = @{ - IdleWaitTimeout = "PT$([System.TimeSpan]::Parse($testParameters.IdleWaitTimeout).TotalMinutes + 1)M" + WaitTimeout = "PT$([System.TimeSpan]::Parse($testParameters.IdleWaitTimeout).TotalMinutes + 1)M" IdleDuration = "PT$([System.TimeSpan]::Parse($testParameters.IdleDuration).TotalMinutes + 1)M" } ExecutionTimeLimit = "PT$([System.TimeSpan]::Parse($testParameters.ExecutionTimeLimit).TotalMinutes)M" @@ -1729,7 +1729,7 @@ try } } - Context 'When a scheduled task is created using a Group Managed Service Account' { + Context 'When a scheduled task is created using a Built In Service Account' { $testParameters = @{ TaskName = 'Test task' TaskPath = '\Test\' @@ -1737,27 +1737,84 @@ try ScheduleType = 'Once' RepeatInterval = (New-TimeSpan -Minutes 15).ToString() RepetitionDuration = (New-TimeSpan -Hours 8).ToString() - ExecuteAsGMSA = 'DOMAIN\gMSA$' - ExecuteAsCredential = [pscredential]::new('DEMO\RightUser', (ConvertTo-SecureString 'ExamplePassword' -AsPlainText -Force)) + BuiltInAccount = 'NETWORK SERVICE' + ExecuteAsCredential = [pscredential]::new('DEMO\WrongUser', (ConvertTo-SecureString 'ExamplePassword' -AsPlainText -Force)) Verbose = $true } - It 'Should return an error when both the ExecuteAsGMSA an ExecuteAsCredential ar specified' { - try - { - Set-TargetResource @testParameters -ErrorVariable duplicateCredential + It 'Should Disregard ExecuteAsCredential and Set User to the BuiltInAccount' { + Set-TargetResource @testParameters + Assert-MockCalled -CommandName Register-ScheduledTask -Times 1 -Scope It -ParameterFilter { + + $User -ieq ('NT AUTHORITY\' + $testParameters['BuiltInAccount']) } - catch - { - # Error from Set-TargetResource expected + } + + $testParameters.Add('LogonType', 'Password') + + It 'Should overwrite LogonType to "ServiceAccount"' { + Set-TargetResource @testParameters + Assert-MockCalled -CommandName Register-ScheduledTask -Times 1 -Scope It -ParameterFilter { + $Inputobject.Principal.LogonType -ieq 'ServiceAccount' } - finally - { - $duplicateCredential.Message | Should -Be "Both ExecuteAsGMSA and ExecuteAsCredential parameters have been specified. A task can either run as a gMSA (Group Managed Service Account) or as a custom credential, not both. Please modify your configuration to include just one of the two.`r`nParameter name: ExecuteAsGMSA" + } + + Mock -CommandName Get-ScheduledTask -MockWith { + @{ + TaskName = $testParameters.TaskName + TaskPath = $testParameters.TaskPath + Actions = @( + [pscustomobject] @{ + Execute = $testParameters.ActionExecutable + } + ) + Triggers = @( + [pscustomobject] @{ + Repetition = @{ + Duration = "PT$([System.TimeSpan]::Parse($testParameters.RepetitionDuration).TotalHours)H" + Interval = "PT$([System.TimeSpan]::Parse($testParameters.RepeatInterval).TotalMinutes)M" + } + CimClass = @{ + CimClassName = 'MSFT_TaskTimeTrigger' + } + } + ) + Principal = [pscustomobject] @{ + UserId = $testParameters.BuiltInAccount + LogonType = 'ServiceAccount' + } } } - $testParameters.Remove('ExecuteAsCredential') + $testParameters.LogonType = 'Password' + It 'Should return true when BuiltInAccount set even if LogonType parameter different' { + Test-TargetResource @testParameters | Should -Be $true + } + } + + Context 'When a scheduled task is created using a Group Managed Service Account' { + $testParameters = @{ + TaskName = 'Test task' + TaskPath = '\Test\' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ScheduleType = 'Once' + RepeatInterval = (New-TimeSpan -Minutes 15).ToString() + RepetitionDuration = (New-TimeSpan -Hours 8).ToString() + ExecuteAsGMSA = 'DOMAIN\gMSA$' + BuiltInAccount = 'NETWORK SERVICE' + ExecuteAsCredential = [pscredential]::new('DEMO\RightUser', (ConvertTo-SecureString 'ExamplePassword' -AsPlainText -Force)) + Verbose = $true + } + + It 'Should throw expected exception' { + $errorRecord = Get-InvalidArgumentRecord -Message $LocalizedData.gMSAandCredentialError -ArgumentName 'ExecuteAsGMSA' + + { Set-TargetResource @testParameters -ErrorVariable duplicateCredential } | Should -Throw $errorRecord + $testParameters.Remove('ExecuteAsCredential') + { Set-TargetResource @testParameters -ErrorVariable duplicateCredential } | Should -Throw $errorRecord + } + + $testParameters.Remove('BuiltInAccount') It 'Should call Register-ScheduledTask with the name of the Group Managed Service Account' { Set-TargetResource @testParameters From f17b753f1f68d9afe1c0301547437562ad7116c2 Mon Sep 17 00:00:00 2001 From: "AEMO\\DWork" Date: Mon, 1 Apr 2019 08:23:22 +1000 Subject: [PATCH 2/5] Changes request by @PlagueHO --- .../MSFT_ScheduledTask.psm1 | 20 +++++--- ...eScheduledTasksAsBuiltinServiceAccount.ps1 | 37 -------------- ...ledTasksAsBuiltInServiceAccount_Config.ps1 | 49 +++++++++++++++++++ Tests/Unit/MSFT_ScheduledTask.Tests.ps1 | 2 +- 4 files changed, 64 insertions(+), 44 deletions(-) delete mode 100644 Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 create mode 100644 Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 index c180948e..69cf0a46 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_ScheduledTask/MSFT_ScheduledTask.psm1 @@ -281,6 +281,7 @@ function Get-TargetResource [System.Boolean] $Enable = $true, + [Parameter()] [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] [System.String] $BuiltInAccount, @@ -805,6 +806,7 @@ function Set-TargetResource [System.Boolean] $Enable = $true, + [Parameter()] [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] [System.String] $BuiltInAccount, @@ -1268,8 +1270,8 @@ function Set-TargetResource # Prepare the register arguments $registerArguments = @{} - $username = $null + if ($PSBoundParameters.ContainsKey('BuiltInAccount')) { <# @@ -1654,6 +1656,7 @@ function Test-TargetResource [System.Boolean] $Enable = $true, + [Parameter()] [ValidateSet('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE')] [System.String] $BuiltInAccount, @@ -1897,13 +1900,17 @@ function Test-TargetResource $currentValues['LogonType'] = 'ServiceAccount' } - if ($PSBoundParameters.ContainsKey('WeeksInterval') -and ((-not $currentValues.ContainsKey('WeeksInterval')) -or ($null -eq $currentValues['WeeksInterval']))) + if ($PSBoundParameters.ContainsKey('WeeksInterval') ` + -and ((-not $currentValues.ContainsKey('WeeksInterval')) -or ($null -eq $currentValues['WeeksInterval']))) { <# - The WeeksInterval parameter is defaulted to 1, even when the - property is unset/undefined for the current task returned from - Get-TargetResouce initialise a missing or null WeeksInterval to - spurious calls to Set-TargetResouce + The WeeksInterval parameter of this function defaults to 1, + even though the value of the WeeksInterval property maybe + unset/undefined in the object $currentValues returned from + Get-TargetResouce. To avoid Test-TargetResouce returning false + and generating spurious calls to Set-TargetResouce, default + an undefined $currentValues.WeeksInterval to the value of + $WeeksInterval. #> $currentValues.WeeksInterval = $PSBoundParameters['WeeksInterval'] } @@ -1925,6 +1932,7 @@ function Test-TargetResource $desiredValues = $PSBoundParameters $desiredValues.TaskPath = $TaskPath + if ($desiredValues.ContainsKey('Verbose')) { <# diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 deleted file mode 100644 index 0d33f198..00000000 --- a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-CreateScheduledTasksAsBuiltinServiceAccount.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -<# - .EXAMPLE - This example creates a scheduled task called 'TaskRunAsNetworkService' in - the folder root folder. The task is set to run every 15 minutes. - When run the task will start a new PowerShell instance running as the - builtin user NETWORK SERVICE. - The PowerShell instance will write the value of $env:USERNAME to the - file c:\temp\seeme.txt. - The contents of c:\temp\seeme.txt should be "NETWORK SERVICE". -#> -Configuration Example -{ - param - ( - [Parameter()] - [System.String[]] - $NodeName = 'localhost' - ) - - Import-DscResource -ModuleName ComputerManagementDsc - - Node $NodeName - { - ScheduledTask ServiceEventManager - { - TaskName = 'TaskRunAsNetworkService' - Ensure = 'Present' - ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' - ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value $env:USERNAME -Force' - ScheduleType = 'Once' - RepeatInterval = '00:15:00' - RepetitionDuration = '4.00:00:00' - BuiltInAccount = 'NETWORK SERVICE' - } - } -} - diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 new file mode 100644 index 00000000..fc48c7e0 --- /dev/null +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 @@ -0,0 +1,49 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID a2984c97-f4fc-4936-b5d7-f8ccf726744f +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT (c) Microsoft Corporation. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/ComputerManagementDsc/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/ComputerManagementDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This example creates a scheduled task called 'Test As NetworkService' in + the folder root folder. The task is set to run every 15 minutes. + When run the task will start a new PowerShell instance running as the + builtin user NETWORK SERVICE. + The PowerShell instance will write the value of $env:USERNAME to the + file c:\temp\seeme.txt. + The contents of c:\temp\seeme.txt should be "NETWORK SERVICE". +#> +Configuration Example +{ + Import-DscResource -ModuleName ComputerManagementDsc + + Node localhost + { + ScheduledTask ScheduledTaskAsNetworkService + { + TaskName = 'Test As NetworkService' + Ensure = 'Present' + ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' + ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value $env:USERNAME -Force' + ScheduleType = 'Once' + RepeatInterval = '00:15:00' + RepetitionDuration = '4.00:00:00' + BuiltInAccount = 'NETWORK SERVICE' + } + } +} + diff --git a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 index 7bdc3d75..92f59bd6 100644 --- a/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 +++ b/Tests/Unit/MSFT_ScheduledTask.Tests.ps1 @@ -1745,7 +1745,6 @@ try It 'Should Disregard ExecuteAsCredential and Set User to the BuiltInAccount' { Set-TargetResource @testParameters Assert-MockCalled -CommandName Register-ScheduledTask -Times 1 -Scope It -ParameterFilter { - $User -ieq ('NT AUTHORITY\' + $testParameters['BuiltInAccount']) } } @@ -1787,6 +1786,7 @@ try } $testParameters.LogonType = 'Password' + It 'Should return true when BuiltInAccount set even if LogonType parameter different' { Test-TargetResource @testParameters | Should -Be $true } From 5e6a3020d41446e3da6eecf6f67f8e0973f96daa Mon Sep 17 00:00:00 2001 From: "AEMO\\DWork" Date: Tue, 2 Apr 2019 07:14:39 +1000 Subject: [PATCH 3/5] renamed 16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config -> ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config as requested --- ...edTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/{16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 => ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1} (100%) diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 similarity index 100% rename from Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 rename to Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 From 70e8f1f2bf3d17fd14ad2cfd8784601e5755a566 Mon Sep 17 00:00:00 2001 From: "AEMO\\DWork" Date: Fri, 5 Apr 2019 06:05:41 +1000 Subject: [PATCH 4/5] Moved Schedule Task BuiltInAccount & IdleWaitTimeout to unreleased section --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e4df9c..bb512d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- ScheduledTask: + - IdleWaitTimeout returned from Get-TargetResource always null - Fixes [Issue #186](https://github.com/PowerShell/ComputerManagementDsc/issues/186). + - Added BuiltInAccount Property to allow running task as one of the build in + service accounts - Fixes [Issue #130](https://github.com/PowerShell/ComputerManagementDsc/issues/130). + ## 6.3.0.0 - Correct PSSA custom rule violations - fixes [Issue #209](https://github.com/PowerShell/ComputerManagementDsc/issues/209). @@ -40,9 +45,6 @@ ## 6.0.0.0 - ScheduledTask: - - IdleWaitTimeout returned from Get-TargetResource always null - Fixes [Issue #186](https://github.com/PowerShell/ComputerManagementDsc/issues/186). - - Added BuiltInAccount Property to allow running task as one of the build in - service accounts - Fixes [Issue #130](https://github.com/PowerShell/ComputerManagementDsc/issues/130). - Added support for Group Managed Service Accounts, implemented using the ExecuteAsGMSA parameter. Fixes [Issue #111](https://github.com/PowerShell/ComputerManagementDsc/issues/111) - Added support to set the Synchronize Across Time Zone option. Fixes [Issue #109](https://github.com/PowerShell/ComputerManagementDsc/issues/109) From a996329aa15655805f6ebc4b7ed76ee561af581e Mon Sep 17 00:00:00 2001 From: "AEMO\\DWork" Date: Fri, 5 Apr 2019 20:04:50 +1000 Subject: [PATCH 5/5] Fixed Configuration name and file name --- ...Task_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/{ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 => 16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1} (95%) diff --git a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 similarity index 95% rename from Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 rename to Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 index fc48c7e0..627a1e43 100644 --- a/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 +++ b/Modules/ComputerManagementDsc/Examples/Resources/ScheduledTask/16-ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config.ps1 @@ -27,7 +27,7 @@ file c:\temp\seeme.txt. The contents of c:\temp\seeme.txt should be "NETWORK SERVICE". #> -Configuration Example +Configuration ScheduledTask_CreateScheduledTasksAsBuiltInServiceAccount_Config { Import-DscResource -ModuleName ComputerManagementDsc