Accessing the status of Windows services from a scheduled task

23 November 2013

A client of mine is using a monitoring suite which checks for certain running Windows services once an hour. It uses the Windows scheduled tasks feature to run. When running as a normal user, it returns no services even though the user is able to list all services in services.msc or on the command line.

Lets demonstrate this:


returns 157 services on a Windows 8 machine.

Create a script to enumerate the services from task scheduler:

'get-service | out-file $env:temp\services.txt' | Set-Content $env:temp\test.ps1
schedule it:
schtasks /create /TN test /TR "powershell.exe %temp%\test.ps1" /SC monthly /NP /RU $env:username
run the task:
schtasks /run /tn test
check the content:
gc $env:temp\services.txt
it is empty. In the pre-Vista days any user would be able to enumerate services. Since Vista the security on what users can do with services has been tightened. While a logged on (interactive) user can still list all services. One that is just running as a task can not see any. Why is this?  Lets look at the permissions on the Service Control Manager, the process that is responsible in managing Windows services.
sc.exe sdshow scmanager
on Windows 8 we get:
this looks pretty cryptic, each set between the brackets describe the permissions for one user(group). The last two letters define the group, AU stands for authenticated users while IU stands for Interactive users. We see the IU has a permission of LC (each two letter between the semicolons define a permission). LC stands for the 'SERVICE_QUERY_STATUS' which is what we need. All we have to do is give this permission to authenticated users. To do this, we copy the existing security descriptor and add the LC in the first section:
sc.exe sdset scmanager D:(A;;LCCC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)
this command fails in a PowerShell because the brackets have a special meaning to the parser, we also need to run this command elevated.
Start-Process "$psHome\powershell.exe" -Verb Runas
and then use quotes around the parameter with brackets:
sc.exe sdset scmanager "D:(A;;LCCC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)"
lets run our test again:
schtasks /run /tn test; start-sleep 3;gc $env:temp\services.txt
we now get a list with 40 services, hmm why not all 157? Lets look at the permissions for a service we get and one we don't:
sc.exe sdshow Spooler
sc.exe sdshow themes
We see AC has LC access to the spooler service, while no access at all to the themes service. after adding this:
we can now see the themes service as well:
schtasks /run /tn test; start-sleep 3;gc $env:temp\services.txt
If we want to change these permissions for many or all services, a script would make sense:
    [string]$pattern = ".",
    [string]$principal = "AU",
    [string]$permission = "LC")

function FixIt ([string]$name,[string]$rawSD,[string]$newSD)
    Write-Host $name -ForegroundColor White
    if ($fix)
        sc.exe sdset $name $newSD 
        Write-Host $rawSD -ForegroundColor Yellow 
        Write-Host $newSD -ForegroundColor Cyan

function ProcessService([string]$name,[string]$permission,[string]$group)
  $rawSD = & sc.exe sdshow $name

  $match = ([regex]"\(A;;[;A-Z]+;;;$group\)").match($rawSD)


  if ($match.Success)
     if ($match.groups[0].value -notmatch $permission)
         # no, so add it
         $newtoken = $match.groups[0].value -replace "\(A;;","(A;;$permission"
         # remove the brackets:
         $newtoken = $newtoken -replace "[()]",""
         #write-host $newtoken  -ForegroundColor red
         $newSD = $rawSD -replace $match.groups[0].value,$newtoken
         FixIt $name $rawSD $newSD 
        Write-Host $name -ForegroundColor green
    # it has no AC token yet, lets add one
    if ($rawSD -match "D:\(")
        # starts with D:, just replace the beginning
        $newSD = $rawSD -replace "D:\(","D:(A;;$permission;;;$group)"
        FixIt $name $rawSD $newSD 
        Write-Host $_.Name -ForegroundColor Red 
        Write-Host $rawSD -ForegroundColor Yellow 

get-service | Where-Object {$_.Name -match $pattern}| ForEach-Object {
    ProcessService $_.Name $permission $principal

ProcessService "scmanager" $permission $principal

copy this to a file Set-ServiceSecurityDescriptors.ps1

When just running it, it shows the descriptors for all services and how it would change them. You can limit it to one or certain services

.\Set-ServiceSecurityDescriptors.ps1 -pattern spooler
.\Set-ServiceSecurityDescriptors.ps1 -pattern svc
To change the permission use the -fix switch:
.\Set-ServiceSecurityDescriptors.ps1 -pattern time -fix
or for all:
.\Set-ServiceSecurityDescriptors.ps1 -fix
You will get a few access denied errors, because for certain services even an elevated administrator has no permissions to change the permissions. Run our task again:
schtasks /run /tn test; start-sleep 3;gc $env:temp\services.txt

We now get a list of 152 services, the five we can't see are: DPS, gpsvc, msiserver, wdiservicehost and whisystemhost

To get to these, open a powershell as system user:

psexec.exe -i -s powershell.exe
.\Set-ServiceSecurityDescriptors.ps1 -fix
now we get all but msiserver. You should be able to set different permissions for different users as well, but I haven't tested this. For more information about the meaning of the various two letters codes check:

Pages in this section


ASP.Net | Community | Development | IIS | IT Pro | Security | SQL (Server) | Tools | Web | Work on the road | Windows