Press "Enter" to skip to content

Automate Windows service actions using non-admin service accounts, powershell, and task scheduler – Part 2

In the last section we looked at getting task scheduler configured and we received the following error: "Get-Service : Cannot open Service Control Manager on computer '.'. This operation might require other privileges." This error is telling us we don’t have appropriate permissions when running the stop services task from the task scheduler. When I discovered this error and researched how to set permissions I couldn’t find a great way to set these permissions in a transparent manner. Meaning, if a colleague jumped on this server a year from now and tried to troubleshoot permissions on the Service Control Manager, would they be able easily see the change I put in place? This should always be a consideration when implementing a change.

Modifying permissions on the Service Control Manager (SCM) is done via the set sdset scmanager command. I could not find a way to set this naively with GPO. You could probably write a script and execute that with GPO, or powershell (If you’ve done this, please comment below). I took a bit of a unique approach, especially when it comes to the Windows world. I chose to use Puppet. Puppet is a configuration manager, primarily associated with Linux configuration management, but has the ability to managed Windows hosts as well. This could be a bit foreign to Windows admins, but hear me out. It allows for configuration management via code which allows for comments, and thus is solves the transparency snafu I had mentioned earlier. I know, a powershell script could do the same, and well we are actually going to leverage powershell during this implementation as well. Even if you don’t have a puppet implementation in your environment the following code can be used and implemented in to your own solution. Ok, now onto the Puppet stuff…

First things first, we need two scripts. One which will get the current permissions of the SCM, and one that will set the permissions. Puppet will take the output of the check permissions script, and need run the permissions fix script. We start by looking at the current permissions using Test-SCManagerPermissions.ps1:

# Default Permissions in SDDL format
$DefaultPermissions = "D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)"

# Query service manager to get the current permissions in SDDL format.
$command = @'
cmd.exe /C  sc sdshow scmanager
'@

$HostPermissions = Invoke-Expression -Command:$command

# Visually compare the two strings
Write-Host "Default: " $DefaultPermissions
Write-Host "  Host: " $HostPermissions

# Test if default permissions are in place.
If ($HostPermissions -eq $DefaultPermissions){
    Write-Host "Found Default Permissions. Exiting 0"
    exit 0
}

Write-Host "Did not match with default permissions. Exiting 1"
exit 1

This script takes the default permissions in Security Descriptor Definition Language (SDDL) format, and compares them to the current permissions. If they match, we return 0, if they don’t, we return 1. We’ll use this output to take action later. Now what is this nasty SDDL string? Well it’s text representation of permissions. I talk more about this in the next section, however we are going to have to get a bit dirty here. Unfortunately, there is no workaround that I’ve found to modify these permissions other than via the command line. To do that, we’ll use the following commands:

sc sdshow scmanager
sc sdset scmanager "your permission string here"

The first command (sdshow) shows the current permissions applied to the SCM in SDDL format. The second command (sdset) will set the permissions on the SCM by specifying permissions in SDDL format. If you run the first command, you should get an output similar to this:

Windows Server 2016

Please note your permission might be different, especially if running this command on a desktop OS. And now that we have a baseline permission set for the server, we can create our own permissions and apply them. The first thing we’ll need is the Security IID (SID) of your service account, or group.

Note: For illustration I’m assigning permissions directly to our service account. If you are in a domain environment, I would highly recommend assigning permissions to a domain group which contains your service account. This is the recommended best practice for assigning permissions!

Getting the SID of your user or group as simple as running a powershell command, or using Active Directory Users and Computers. To use powershell you’ll need to make sure you have the AD module installed and loaded (outside the scope of this guide), and then run the following:

get-aduser -Identity "your_service_account" | select SID

The SID is also available from Active Directory Users and Computer via the object properties, show below:

And now that we have the SID we can build our SDDL string to apply permissions. We start by taking the default string and modifying the block (A;;CCLCRPRC;;;IU) – (IU means Interactively logged-on user) More information on SDDL strings can be found here: https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings

Copy the entire string to a text editor, then copy the IU section. Then add the SID to your user or group to the UI section like so:

Before:
D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)

After:
D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)(A;;CCLCRPRC;;;S-1-5-21-2613503727-1553357937-2150718590-315158)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)

Alright now that we have the modified permissions string we’ll need to use that in our Set-SCManagerPermissions.ps1 script.

# Create custom EventLog source so we can write to it later
if ([System.Diagnostics.EventLog]::SourceExists("Demo Log") -eq $False) {
    Write-Host "Creating Event Log Source: 'Demo Log'"
    New-EventLog -LogName Application -Source "Demo Log"
}

# Group or user SID
$SID = "S-1-5-21-2613503727-1553357937-2150718590-315158"

# Default Permissions in SDDL format
$DefaultPermissions = "D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)"
$NewPermissions = "D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)(A;;CCLCRPRC;;;$($SID))S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)"

# Query service manager to get the current permissions in SDDL format.
$CheckPermissionsCommand = @"
cmd.exe /C  sc sdshow scmanager
"@

$SetPermissionsCommand = @"
cmd.exe /C  sc sdset scmanager `"$($NewPermissions)`"
"@

$HostPermissions = Invoke-Expression -Command:$CheckPermissionsCommand


# Test if default permissions are in place.
If ($HostPermissions -eq $DefaultPermissions){
    Write-EventLog -LogName "Application" -Source "Puppet: Script" -EventID 3001 -EntryType Information -Message "Found default scmanager permissions. Adding $($SID)" -Category 1 -RawData 10,20
    Write-Host -ForegroundColor Yellow "Found default scmanager permissions. Adding $($SID)"
    Invoke-Expression -Command:$SetPermissionsCommand

    Write-Host "New permission set: $(Invoke-Expression -Command:$CheckPermissionsCommand)"
    Write-EventLog -LogName "Application" -Source "Puppet: Script" -EventID 3001 -EntryType Information -Message "Previous permission set: $($DefaultPermissions)" -Category 1 -RawData 10,20
    Write-EventLog -LogName "Application" -Source "Puppet: Script" -EventID 3001 -EntryType Information -Message "New permission set: $($NewPermissions)" -Category 1 -RawData 10,20
}

This script compares the default permissions set with the current permissions, if they are they match, then it modifies them by adding permissions for your SID. Pretty simple! Now at this point you could just run the script and the permissions would be set and you’d be done, right? Well, if you aren’t concerned about transparency going forward, then sure. But like I said before, I want this change to be easy to find for the next guy troubleshooting or rebuilding this server. So lets take a look at the puppet profile config for our test node. Feel free to skip this section if you are not a puppet user.

Puppet Configuration

As of this writing we are running puppet 6.4. It’s possible you may need to adapt this code to reflect your version of puppet, but for the most part this should be sufficient.

  # Declare a file resource and source files from git
  file { "c:\\scripts": ensure  => 'directory' }
  file { "c:\\scripts\\demo":
    ensure  => 'directory',
    recurse => true,
    force   => true,
    source  => 'puppet:///modules/test/demo',
    require => File["c:\\scripts"],
  }

  exec { 'sc manager permissions':
    command  => "c:\\scripts\\demo\\Set-SCManagerPermissions.ps1",
    path     => $::path,
    onlyif   => "c:\\scripts\\demo\\Test-SCManagerPermissions.ps1",
    provider => powershell,
  }

Line 2, we create the C:\scripts directory, if not already there.
Line 3, we create the C:\scripts\demo directory, if not already there, then we copy the files from puppet. Location of files within puppet will differ depending on your environment. For us they are stored in %test_repo%/files/demo. This will need to be modified to fit your environment. line 8 says C:\Scripts is a dependency and must exists before the code will run.

The magic happens on lines 11-15. This is the logic for testing and executing the permission change. It works by saying run Set-SCManagerPErmissions, ‘only if’ the Test-SCManagerPermissions.ps1 script exits with exit code 0. If you remember back, the output from this script is either 0, if default permissions are found, or 1, if not using default permissions. So in this case, if the script exists with 0, go ahead and run the Set-SCManagerPermissions.ps1, otherwise do nothing.

It’s also worth noting that Set-SCManagerPermissions.ps1 also checks to see if the permissions are set to the default value before making the change. This would prevent the permission from being changed if someone has already set custom permissions on the SCM.

I added the code snippet above to my a profile assigned to my test server. Because puppet environments can differ, I won’t go into my specific implementation. Once the code has been added, I’ll have the puppet agent checking and pull any new changes, shown below.

This puppet run has detected two new files, and placed moved them to the required location. It didn’t have to create the directories since they were already present, otherwise it would note that during the run. You can also see that it ran the ‘SC Manager Permissions’ code block. Lets have a look at those on disk.

Scripts have been copied to disk.

Now that the script has ran, lets take a look at the permissions. We can see the SID has been added to the SDDL string. Perfect!

And lastly since we logged to the Event Log, lets take a look and make sure we see the log entries. And we do! (Note: Screen capture shows a different Log Source, I changed the code after this screen shot was taken.)

This is great! Now if the SDDL string gets changed, puppet will correct it and set it back to our string. All there is left to do is check if the service account has sufficient privileges to run the Scheduled Task. Lets kick off the task created in part 1 and check the transcript log.

**********************
Windows PowerShell transcript start
Start time: 20200117154720
Username: AD\is-svc-serv-test
RunAs User: AD\is-svc-serv-test
Machine: IS-CRAIGS-DEV2 (Microsoft Windows NT 10.0.14393.0)
Host Application: powershell.exe -NonInteractive -NoLogo -NoProfile -ExecutionPolicy Bypass -File C:\temp\demo\stop_services.ps1
Process ID: 6236
PSVersion: 5.1.14393.3053
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.14393.3053
BuildVersion: 10.0.14393.3053
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
Stopping Print Spooler Service
Stop-Service : Service 'Print Spooler (Spooler)' cannot be stopped due to the following error: Cannot open Spooler 
service on computer '.'.
At C:\temp\demo\stop_services.ps1:12 char:5
+     Stop-Service $servicearray[$i].Name
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (System.ServiceProcess.ServiceController:ServiceController) [Stop-Service], 
ServiceCommandException
    + FullyQualifiedErrorId : CouldNotStopService,Microsoft.PowerShell.Commands.StopServiceCommand
Stop-Service : Service 'Print Spooler (Spooler)' cannot be stopped due to the following error:
Cannot open Spooler service on computer '.'.
At C:\temp\demo\stop_services.ps1:12 char:5
+     Stop-Service $servicearray[$i].Name
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (System.ServiceProcess.ServiceController:ServiceControl
   ler) [Stop-Service], ServiceCommandException
    + FullyQualifiedErrorId : CouldNotStopService,Microsoft.PowerShell.Commands.StopServiceCommand

 

**********************
Windows PowerShell transcript end
End time: 20200117154721
**********************
Stop-Service : Service 'Print Spooler (Spooler)' cannot be stopped due to the following error: Cannot open Spooler service on computer '.'.

Good news and bad news. It’s no longer complaining about permissions on the SCM, great! But it still can’t stop the service. That’s because there are individual permissions assigned at the service level. The good news is they are a bit easier to configure. More on that in the next post!

Sharing is caring!

Leave a Reply

Your email address will not be published. Required fields are marked *

thirty − twenty six =