9

In earlier windows server versions (prior to 2016) it was possible to grant non-admin users the permission to run a scheduled task by doing following steps:

  1. Scheduled Task: run under system, execute script
  2. Give user read and execute rights on specific task under C:\Windows\System32\Tasks\

Now in server 2016 this doesn't work anymore. Do you know how to do it?

Thank you

related post, which didn't get answered, neither helped: Allow non-admin user to run scheduled task in Windows Server 2016

n0rthclub
  • 91
  • 1
  • 1
  • 3
  • Are you getting an error? – spacenomyous Oct 31 '17 at 09:52
  • no, the task just doesn't show up in the task scheduler – n0rthclub Oct 31 '17 at 10:56
  • I was able to reproduce it. It only works if the assigned user is changed on the task or it is run from an admin CMD prompt. I however can see the task with my non-admin account using `schtasks.exe /query`. I haven't been able to find anything explicitly stating changes in the account permissions and/or session isolation. – spacenomyous Oct 31 '17 at 15:47
  • What's the end goal of providing access to the scheduled task? Is it to get around UAC and running an application as administrator? – Nixphoe Apr 16 '18 at 14:48
  • The goal is to let less priviledge user run admin tasks. _a Task runs a script (from a secure location and signed) which for example changes the primary dns server_ – n0rthclub Apr 24 '18 at 07:06
  • 1
    FYI: I opened a Microsoft Case and it looks like it's a global bug according to them. There is no solution yet. – n0rthclub Dec 10 '18 at 09:02
  • @n0rthclub could you post a link to that MS case? – tomRedox Oct 01 '19 at 21:32
  • @tomRedox Sorry I'm not able to post it as I changed employer and it was on their company microsoft account. – n0rthclub Oct 08 '19 at 19:56

1 Answers1

1

After digging into the topic for a while and also trying some suggestions I've came up with this script which requires the PsExec tool from PsTools/SysInternals from Microsoft:

$task = "Test-Script"

$secdesc = new-object system.management.ManagementClass Win32_SecurityDescriptorHelper 
$regkeys = Get-childitem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree"
foreach ($key in $regkeys) {
    if ($key.PSChildName -eq $task) {
        
        
        $t = Get-ItemProperty $($key.name).replace("HKEY_LOCAL_MACHINE","HKLM:")
        $sddl = $secdesc.BinarySDToSDDL( $t.SD ) 
        $newSDDL = $sddl['SDDL'] +  '(A;ID;0x1301bf;;;AU)'
        $binSDDL = $secdesc.SDDLToBinarySD( $newSDDL )
        [string]$binSDDLStr =  $([System.BitConverter]::ToString($binSDDL['BinarySD'])).replace('-','') 
        
        "reg add ""HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{0}"" /f /v SD /t REG_BINARY /d {1}" -f $key.PSChildName, $binSDDLStr | out-file -Encoding ascii $PWD\temp_setreg.bat
        start-sleep 2
        .\PsExec.exe -accepteula -nobanner -s "$PWD\temp_setreg.bat"
        break
    }
}

Background: Obviously since Windows 10 (and corresponding Windows Server 2016+) the security settings are not used from the task path (C:\Windows\system32\Tasks) anymore but are stored in the registry:

I've found a script which uses the registry values. It creates a task to run as SYSTEM so you are able to edit the permissions (since Administrators also only have read-only permission).
I've edited this script to shorten it and use PsExec to edit the registry permissions as SYSTEM user.
Place PsExec.exe in the same directory as the script, edit the first line to hold the name of the task. Run the script from that directory.

From the script author: (A;ID;0x1301bf;;;AU) means to add Authenticated Users with read and execute permission.
You can create your own permission entry by using Windows Explorer's security tab and read it from command line in SDDL format with this: Cacls . /S
Replace . with the path or file if it's not your current directory.

Here is the original script (archive): UnlockScheduledTask.ps1

<#

.SYNOPSIS
This Powershell script updates the security descriptor for scheduled tasks so that any user can run the task. 

Version 1.0 of this script only displays tasks in the root folder. I want to make sure that works first. 

.DESCRIPTION
Earlier versions of Windows apparently used file permissions on C:\Windows\System32\Tasks files to manage security.
Windows now uses the SD value on tasks under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree to accomplish that. 

By default, this script will display the SDDL on all tasks. If a taskname is passed as a parameter, this script will grant Authenticated users read and execute permissions to the task. 

This script accepts 1 parameters.
-taskname   The name of a scheduled task. 

.EXAMPLE
./UnlockScheduledTask.ps1 
./UnlockScheduledTask.ps1 -taskname "My task"  

.NOTES
Author: Dave K. aka MotoX80 on the MS Technet forums. (I do not profess to be an expert in anything. I do claim to be dangerous with everything.)



.LINK
http://www.google.com

#>

param (
    [string]$taskname = ""   
 )

 'UnlockScheduledTask.ps1  Version 1.0'
 if ($taskname -eq '') {
    ''
    'No task name specified.'
    'SDDL for all tasks will be displayed.'
    ''
 } else {
    $batFile = "$env:TEMP\Set-A-Task-Free.bat"           # if you don't like my names, you can change them here. 
    $updateTaskName = 'Set-A-Task-Free'
    ''
    "SDDL for $taskname will be updated via $batfile"
    ''
 }
 $wmisdh = new-object system.management.ManagementClass Win32_SecurityDescriptorHelper 
 $subkeys = Get-childitem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree"
 foreach ($key in $subkeys) {
    if ($taskname -eq '') {              # if blank, show SDDL for all tasks 
        ''
        $key.PSChildName
        $task = Get-ItemProperty $($key.name).replace("HKEY_LOCAL_MACHINE","HKLM:")
        $sddl = $wmisdh.BinarySDToSDDL( $task.SD ) 
        $sddl['SDDL']        
    
    } else {
        if ($key.PSChildName -eq $taskname) {
            ""
            $key.PSChildName
            $task = Get-ItemProperty $($key.name).replace("HKEY_LOCAL_MACHINE","HKLM:")
            $sddl = $wmisdh.BinarySDToSDDL( $task.SD ) 
            $sddl['SDDL']
            ''
            'New SDDL'
            $newSD = $sddl['SDDL'] +  '(A;ID;0x1301bf;;;AU)'          # add authenticated users read and execute
            $newSD                                                    # Note: cacls /s will display the SDDL for a file. 
            $newBin = $wmisdh.SDDLToBinarySD( $newsd )
            [string]$newBinStr =  $([System.BitConverter]::ToString($newBin['BinarySD'])).replace('-','') 
            
            # Administrators only have read permissions to the registry vlaue that needs to be updated.
            # We will create a bat file with a reg.exe command to set the new SD.
            # The bat file will be invoked by a scheduled task that runs as the system account.
            # The bat file can also be reused if the task is deployed to other machines. 
            ''
            "reg add ""HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{0}"" /f /v SD /t REG_BINARY /d {1}" -f $key.PSChildName, $newBinStr
            "reg add ""HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{0}"" /f /v SD /t REG_BINARY /d {1}" -f $key.PSChildName, $newBinStr  | out-file -Encoding ascii $batfile  
            ''

            SCHTASKS /Create /f /tn "$updateTaskName" /sc onstart  /tr "cmd.exe /c $batfile" /ru system 
            SCHTASKS /run /tn "$updateTaskName"
            $count = 0
            while ($count -lt 5) {
                start-sleep 5
                $count++
                $(Get-ScheduledTask -TaskName $updateTaskName).State
                if ($(Get-ScheduledTask -TaskName $updateTaskName).State -eq 'Ready') {
                    $count = 99            # it's ok to procees
                }
            }
            if ($count -ne 99) {
                "Error! The $updateTaskName task is still running. "
                'It should have ended by now.'
                'Please investigate.'
                return
            }
            SCHTASKS /delete /f /tn "$updateTaskName"
            ''
            'Security has been updated. Test it.'
        }
    }      
 }
unNamed
  • 523
  • 2
  • 10
  • This is a good answer, but **NOTE:** Even though the adjustment to the registry must be done as SYSTEM (using PSExec or a scheduled task), the script itself must be run as Admin. If it is run as a startup or SCCM script, it fails because SYSTEM cannot see/read the registry key it ultimately updates. – Teknowledgist Apr 09 '21 at 13:43