This week I received a ticket from a user requesting help automating a Windows service state via Powershell and Windows Task Scheduler. The user had wrote a basic script to start and stop selected application services and was attempting to schedule them using Task Scheduler.
Pre-requisites:
- Basic understanding of Powershell scripting
- Basic understanding of how to task scheduler works
- Know how to write/apply/update and test GPOs
Part 1 – The Task Scheduler
For anyone who has tried executing a Powershell script via task scheduler, it can be a bit tricky to figure out whats happening because not all actions are transparent to the user. In this case the task scheduler would run successfully but the services would not start/stop. However, if you ran the script manually via a Powershell console, everything works as expected. In this case I figured I should probably try to figure out if task scheduler was actually running the script.
Lets take a look at the stop_services.ps1 powershell script. Then we will look at the task scheduler configuration. And finally we’ll see what we can do for logging to ensure the script is running.
# $ServiceName accepts wildcards and will search the SERVICE DISPLAYNAME for a match! # Service displayname is what you see in the services mmc under the name collumn. $ServiceName = "*Spooler*" $servicearray = Get-Service | Where-Object { $_.DisplayName -like "$($ServiceName)" } for ($i = 0; $i -lt $servicearray.Length; $i++) { Write-host "Stopping $($servicearray[$i].DisplayName) Service" Stop-Service $servicearray[$i].Name }
Note: In this example, I am substituting our production services and using the print spooler service. If you’d like to use this code, substitute your service or services using a wildcard, as shown.






If you’d like to create this task scheduler task using Powershell see the following snippet:
# Configurable Items $ScriptPath = "C:\temp\demo\stop_services.ps1" # Full path to script you'd like to execute $UserName = "DOMAIN\your-service-account" # Service account name in which script should be executed as $TaskName = "Stop Services" # Task display name $Trigger = New-ScheduledTaskTrigger -At 10:00am –Daily # Specify when and how oftent the task should be ran # Scheduled task creation after this point $Action = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-NonInteractive -NoLogo -NoProfile -ExecutionPolicy Bypass -File ""$($ScriptPath)""" $Settings = New-ScheduledTaskSettingsSet -DontStopOnIdleEnd -RestartInterval (New-TimeSpan -Minutes 1) -RestartCount 10 -StartWhenAvailable $Settings.ExecutionTimeLimit = "PT0S" $Credentials = Get-Credential -UserName "$($UserName)" -Message "Enter password: " $Password = $Credentials.GetNetworkCredential().Password $Task = New-ScheduledTask -Action $Action -Trigger $Trigger -Settings $Settings $Task | Register-ScheduledTask -TaskName "$($TaskName)" -User "$($UserName)" -Password $Password
Once the task is configured, you may notice if you try to run the task it will error with “Task Scheduler failed to start “\Stop Services” task for user “DOMAIN\User”. Additional Data: Error Value: 2147943785.”

We receive this error because the service account does not have permission to run scripts on the host. To grant this privileged we need to give the service account the ability to “Logon as a batch job” by defining it in the local group policy.
Click Start, type “gpedit.msc”, configure the following policy:
[Computer Configuration\Windows Settings\Security Settings\Local Policies\User Rights Assignment]
Select “Logon as a batch job”, then add your service account.

Once you’ve granted the service account permission, re-run your scheduled task. You should see that it now runs, and if we look in the history we can see a successful execution.

This is great! Or is it? We now have the ability to run a script on a host with no admin privileges. We’re done right? Not so fast… Our goal was to stop the services. If we take a look at the Print Spooler service, we can see its still running. So what happened? Did the script not run?

As far as we know, the script ran. Task scheduler logged an event 201 “Action Completed” and the script returned code 0. Since there is no visibility into what the script is doing when it’s ran from Task Scheduler, we have a couple options. We can either write extensive logging into the script to write debug information to disk, or we can use start-transcript. By using this, we can log the entire powershell session to disk, quickly and easily. Lets modify our script to include start transcript (and stop-transcript). Wrap the script with start and stop transcript as shown on line 1 and 14. Modify the file path appropriately, then save.
Start-Transcript -Path "C:\temp\demo\transcript.log" # $ServiceName accepts wildcards and will search the SERVICE DISPLAYNAME for a match! # Service displayname is what you see in the services mmc under the name collumn. $ServiceName = "*Spooler*" $servicearray = Get-Service | Where-Object { $_.DisplayName -like "$($ServiceName)" } for ($i = 0; $i -lt $servicearray.Length; $i++) { Write-host "Stopping $($servicearray[$i].DisplayName) Service" Stop-Service $servicearray[$i].Name } Stop-Transcript
After modifying the script, run your Scheduled Task once more. Check the path you specified for the log file.
********************** Windows PowerShell transcript start Start time: 20191227100002 Username: AD\is-svc-serv-test RunAs User: AD\is-svc-serv-test Configuration Name: Machine: your_host (Microsoft Windows NT 10.0.17763.0) Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NonInteractive -NoLogo -NoProfile -ExecutionPolicy Bypass -File C:\temp\demo\stop_services.ps1 Process ID: 12492 PSVersion: 5.1.17763.771 PSEdition: Desktop PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.17763.771 BuildVersion: 10.0.17763.771 CLRVersion: 4.0.30319.42000 WSManStackVersion: 3.0 PSRemotingProtocolVersion: 2.3 SerializationVersion: 1.1.0.1 ********************** Transcript started, output file is C:\temp\demo\transcript.log PS>TerminatingError(Get-Service): "Cannot open Service Control Manager on computer '.'. This operation might require other privileges." Get-Service : Cannot open Service Control Manager on computer '.'. This operation might require other privileges. At C:\temp\demo\stop_services.ps1:7 char:17 + $servicearray = Get-Service | Where-Object { $_.DisplayName -like "$( ... + ~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Get-Service], InvalidOperationException + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.GetServiceCommand Get-Service : Cannot open Service Control Manager on computer '.'. This operation might require other privileges. At C:\temp\demo\stop_services.ps1:7 char:17 + $servicearray = Get-Service | Where-Object { $_.DisplayName -like "$( ... + ~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Get-Service], InvalidOperationException + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.GetSe rviceCommand ********************** Windows PowerShell transcript end End time: 20191227100002 **********************
Wow, helpful right? Now we can see what happened and why the service didn’t stop. The script is throwing and error on Get-Service. More specifically:"Get-Service : Cannot open Service Control Manager on computer '.'. This operation might require other privileges."
This is certainly not what we want, but at least it points us in a general direction. We know we are running this as a limited user, with no admin rights. It’s more than likely this service account doesn’t have permission to user the Service Control Manager. We’ll take a look at that in the next section.