Report Scheduled Tasks on servers that have local or domain accounts configured

For one of our customers, I needed to create a report of all Scheduled Tasks on their servers with a local or domain account configured. They required this report because they are switching to stricter group policies and need to know what user accounts should have the “Log on as a batch job” right. In this blog post, I will show you how to create that report 🙂

Challenges

I thought creating a report should be easy. The Get-ScheduledTask should display the configured account. But it doesn’t, so Schtasks.exe should do that, right? I started creating a script using that, and it has issues connecting to servers, throwing ‘Can’t retrieve XML…’ errors, and I couldn’t get that to work reliably.

But… Scheduled Tasks are XML files that are stored in C:\Windows\System32\Tasks 🙂 So, I started creating a script that parsed those files and yielded the desired results. I could now retrieve the server, task name, and credentials when the task was configured to run, regardless of whether the user was logged on.

Running the script

The “Scheduled Tasks inventory.ps1” script will search for all Computer Accounts with a Windows Server operating system, parse all Task Schedules for credentials, and skip things like System, Local Service, etc.

In the example below, it runs against my Windows Server 2022 Domain Controller and my Windows Server 2022 Member server:

When done, it saves a CSV to the location specified at the top of the script. The report lists all Scheduled Tasks that are not running as a built-in/system account. If it didn’t find any, it would report that too.

The script

Below is the script that I made. You can change the $CSVlocation to the location and filename you prefer 🙂 And thanks, Brent Newland, for the additions to it!

$CSVlocation = 'C:\Temp\ScheduledTasks.csv'
$total = foreach ($server in Get-ADComputer -Filter 'OperatingSystem -like "Windows Server*" -and Enabled -ne $False' | Sort-Object Name) {
    try {
        $scheduledtasks = Get-ChildItem "\\$($Server.name)\c$\Windows\System32\Tasks" -Recurse -File -ErrorAction Stop
        Write-Host ("Retrieving Scheduled Tasks list for {0}" -f $server.Name) -ForegroundColor Green
    }
    catch {
        Write-Warning ("Unable to retrieve Scheduled Tasks list for {0}" -f $server.Name)
        $scheduledtasks = $null
    }
    foreach ($task in $scheduledtasks | Sort-Object Name) {
        try {
            $taskinfo = [xml](Get-Content -Path $task.FullName -ErrorAction stop)
        }
        catch {
            Write-Warning ("Could not read {0}" -f $task.FullName)
            $taskinfo = $null
        }
        
        if ($taskinfo.Task.Settings.Enabled -eq 'true' `
                -and $taskinfo.Task.Principals.Principal.GroupId -ne 'NT AUTHORITY\SYSTEM' `
                -and $taskinfo.Task.Principals.Principal.GroupId -ne 'S-1-5-32-544' `
                -and $taskinfo.Task.Principals.Principal.LogonType -ne 'InteractiveToken' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'Administrators' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'EVERYONE' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'INTERACTIVE' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'LOCAL SERVICE' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'NETWORK SERVICE' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'NT AUTHORITY\SYSTEM' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'SYSTEM' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'S-1-5-18' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'S-1-5-19' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'S-1-5-20' `
                -and $taskinfo.Task.Principals.Principal.UserId -ne 'USERS' `
                -and $taskinfo.Task.Triggers.LogonTrigger.Enabled -ne 'True' `
                -and $null -ne $taskinfo.Task.Principals.Principal.UserId
        ) {
            [PSCustomObject]@{
                Server    = $Server.name
                TaskName  = $task.Name
                RunAsUser = $taskinfo.Task.Principals.Principal.UserId
            }    
        }
    }
}
if ($total.count -gt 0) {
    $Total | Sort-Object Server, TaskName | Export-Csv -NoTypeInformation -Delimiter ';' -Encoding UTF8 -Path $CSVlocation
    Write-Host ("Saved results to {0}" -f $CSVlocation) -ForegroundColor Green
}
else {
    Write-Warning ("No Scheduled Tasks were found running on local or Domain accounts")
}

Download the script(s) from GitHub here.

15 thoughts on “Report Scheduled Tasks on servers that have local or domain accounts configured

  1. You can also use WMI, then you don’t need to parse xml files:

    get-ciminstance -ComputerName [put computer name here] -Namespace ‘ROOT\Microsoft\Windows\TaskScheduler’ -Query ‘SELECT * FROM MSFT_ScheduledTask’

    1. That’s also an option, it does retrieve the Principal security settings that I needed. I think I queried the Win32_ScheduledJob class which only returned the AT-like schedules and that I stopped there…

      It would be great if the Get-ScheduledTask cmdlet would show the credentials 🙁

  2. I had a similar need myself and I approached the problem from the schtasks.exe command line tool. I wrote a Powershell wrapper function called Get-ScheduledTaskUser which is part of my PoshFunctions module on the Powershell Gallery.

      1. Hello, I also do not get any output when I know there are Domain accounts running scheduled tasks.

        Processing Task User_Feed_Synchronization-{7AB69278-C1D9-49CA-80D0-5BE85FCB7769} on ECSTASKSPD02
        Processing Task UserTask on ECSTASKSPD02
        Processing Task UserTask-Roam on ECSTASKSPD02
        Processing Task USO_UxBroker on ECSTASKSPD02
        Processing Task VerifiedPublisherCertStoreCheck on ECSTASKSPD02
        Processing Task VerifyWinRE on ECSTASKSPD02
        Processing Task WakeUpAndContinueUpdates on ECSTASKSPD02
        Processing Task WakeUpAndScanForUpdates on ECSTASKSPD02
        Processing Task WindowsActionDialog on ECSTASKSPD02
        Processing Task WinSAT on ECSTASKSPD02
        Processing Task WSRefreshBannedAppsListTask on ECSTASKSPD02
        Processing Task WSTask on ECSTASKSPD02
        WARNING: No Scheduled Tasks were found running on local or Domain accounts

  3. Hello,
    I modified the script to point to a single server which I know has tasks using domain accounts, however I don’t get any output. This is the end results of running the script:

    Processing Task UpdateLibrary on Server1
    Processing Task UPNChange on Server1
    Processing Task UPnPHostConfig on Server1
    Processing Task UsbCeip on Server1
    Processing Task UserTask on Server1
    Processing Task UserTask-Roam on Server1
    Processing Task USO_UxBroker on Server1
    Processing Task VerifiedPublisherCertStoreCheck on Server1
    Processing Task VerifyWinRE on Server1
    Processing Task WakeUpAndContinueUpdates on Server1
    Processing Task WakeUpAndScanForUpdates on Server1
    Processing Task WindowsActionDialog on Server1
    Processing Task WinSAT on Server1
    Processing Task WSRefreshBannedAppsListTask on Server1
    Processing Task WSTask on Server1
    WARNING: No Scheduled Tasks were found running on local or Domain accounts

    1. I updated the script, could you test it? It did work on my test machines, not sure why it’s not working for me anymore too… This was a quick fix, I will put it on my To Do to change it and also add the option to scan for one or more specific servers instead of all 🙂

  4. Getting the initial list of AD computers might be slightly more efficient if you’d ‘filter left’, e.g. by using `… Get-ADComputer -Filter ‘OperatingSystem -like “Windows Server*”‘ …`

  5. I was getting groups with S-1-1-0 (all users), S-1-5-4 (interactive logons), etc.
    I ended up adding this:
    -and $taskinfo.Task.Principals.Principal.UserId -ne $null `
    All the ones that were only groups seemed to have no UserID.

    Also, I excluded all disabled servers.
    -Filter ‘OperatingSystem -like “Windows Server*” -and Enabled -ne $False’

    And I ended up commenting out the “Retrieving Scheduled Tasks”, made it difficult to see errors.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.