12

I want to write a PowerShell script that lists all Scheduled Tasks on remote systems, and includes the user account which will be used to run each task.

The local system is running Windows 7, with PowerShell 3.0. The remote systems range from Server 2003 to 2008 R2, with PowerShell versions from 2.0 to 3.0.

What PowerShell commands or functions can I use for this task?

Iszi
  • 13,585
  • 44
  • 113
  • 181
Ob1lan
  • 1,906
  • 3
  • 18
  • 35
  • What's the local system using? (OS & PowerShell version) – Iszi Sep 15 '14 at 12:29
  • @root : As Iszi said, this Cmdlet is supported on Windows Server 2012 or Windows 8. Please read carefully the question before commenting/answering. – Ob1lan Sep 15 '14 at 13:01
  • 1
    @Iszi The local system is Windows 7 with PowerShell v3 – Ob1lan Sep 15 '14 at 13:01
  • @Ob1lan I've removed my comment for being inaccurate/unhelpful – root Sep 15 '14 at 13:02
  • @root If you're running Win7 SP1, you may want to consider upgrading to PowerShell 4.0. That may help expand your options a bit, although it still won't bring you up to the level of a Win 8.1 system. I'm also not 100% sure how PowerShell works when the version on the local system is different than the one on the remote system. I've posted a question for that [here](http://superuser.com/questions/811971/how-do-version-differences-affect-powershell-commands-on-remote-computers). – Iszi Sep 15 '14 at 13:12
  • @Iszi I'm not sure upgrading to PowerShell v4 on the local machine will do the trick in this situation. Indeed, it will not make the Get-Scheduled task usable of remote systems, as far as I understand. I think both systems should recognize it. I'll give a try asap. – Ob1lan Sep 15 '14 at 13:18
  • Ack! @root - pardon the mis-directed mention above. – Iszi Sep 15 '14 at 13:18
  • @Ob1lan It won't bring you `Get-ScheduledTasks`, but it may have other features and functionality which could be useful - if not for this particular script, then possibly for others. Again though, I'm not sure how PowerShell handles things when the remote version is different from the local one. Depending on the commands and features you end up needing, you may or may not be able to use them against your Server 2003 systems which only support up to PowerShell 2.0. – Iszi Sep 15 '14 at 13:21
  • @Ob1lan By the way, if you're not already familiar with `ForEach-Object`, you'll want to start learning about that. Whatever ends up being the answer, it will probably have something along the lines of: 1. Make a list of server names in a text file (e.g.: Servers.txt), one name/ip per line. 2. `Get-Content Servers.txt | ForEach-Object { ... }` – Iszi Sep 15 '14 at 14:19

5 Answers5

9

I finally wrote a script that suits my needs. This script will 'scan' all the servers listed in AD, searching in the c:\Windows\System32\tasks folder for xml files. Then it will write the value of the UserID xml node of each file, in the final CSV file.

Not yet perfect but totally working to list all tasks of all servers, and log which user account is used to run them.

<#
.Synopsis
   PowerShell script to list all Scheduled Tasks and the User ID
.DESCRIPTION
   This script scan the content of the c:\Windows\System32\tasks and search the UserID XML value. 
   The output of the script is a comma-separated log file containing the Computername, Task name, UserID.
#>

Import-Module ActiveDirectory
$VerbosePreference = "continue"
$list = (Get-ADComputer -LDAPFilter "(&(objectcategory=computer)(OperatingSystem=*server*))").Name
Write-Verbose  -Message "Trying to query $($list.count) servers found in AD"
$logfilepath = "$home\Desktop\TasksLog.csv"
$ErrorActionPreference = "SilentlyContinue"

foreach ($computername in $list)
{
    $path = "\\" + $computername + "\c$\Windows\System32\Tasks"
    $tasks = Get-ChildItem -Path $path -File

    if ($tasks)
    {
        Write-Verbose -Message "I found $($tasks.count) tasks for $computername"
    }

    foreach ($item in $tasks)
    {
        $AbsolutePath = $path + "\" + $item.Name
        $task = [xml] (Get-Content $AbsolutePath)
        [STRING]$check = $task.Task.Principals.Principal.UserId

        if ($task.Task.Principals.Principal.UserId)
        {
          Write-Verbose -Message "Writing the log file with values for $computername"           
          Add-content -path $logfilepath -Value "$computername,$item,$check"
        }

    }
}

The output is a comma-separated file generated on your desktop, like this one :

> SRV028,CCleanerSkipUAC,administrator
> SRV029,GoogleUpdateTaskMachineCore,System
> SRV030,GoogleUpdateTaskMachineUA,System
> SRV021,BackupMailboxes,DOMAIN\administrator
> SRV021,Compress&Archive,DOMAIN\sysScheduler
Ob1lan
  • 1,906
  • 3
  • 18
  • 35
  • here a PS version without read XML: https://stackoverflow.com/a/53123962/1747983 – Tilo Nov 02 '18 at 18:59
  • Note: Patrick E has posted a comment (as an Answer: https://superuser.com/a/1368312/19792), suggesting to use `-LiteralPath` in the `Get-Content` command. I can't comment on its validity though. – mwfearnley Dec 14 '18 at 10:28
6

Hey thought I'd share a modified version of the script posted by Ob1lan. The modification helps find tasks in nested folders and lists the status of the task as well as details included in the original.

$Computers = (get-adcomputer -filter {operatingsystem -like "*server*"}).name
$ErrorActionPreference = "SilentlyContinue"
$Report = @()
foreach ($Computer in $Computers)
{
    if (test-connection $Computer -quiet -count 1)
    {
        #Computer is online
        $path = "\\" + $Computer + "\c$\Windows\System32\Tasks"
        $tasks = Get-ChildItem -recurse -Path $path -File
        foreach ($task in $tasks)
        {
            $Details = "" | select ComputerName, Task, User, Enabled, Application
            $AbsolutePath = $task.directory.fullname + "\" + $task.Name
            $TaskInfo = [xml](Get-Content $AbsolutePath)
            $Details.ComputerName = $Computer
            $Details.Task = $task.name
            $Details.User = $TaskInfo.task.principals.principal.userid
            $Details.Enabled = $TaskInfo.task.settings.enabled
            $Details.Application = $TaskInfo.task.actions.exec.command
            $Details
            $Report += $Details
        }
    }
    else
    {
        #Computer is offline
    }
}
$Report | ft

NOTE: If you have many servers, this will take a long time to run. If you'd like to run it in parallel, you can use the invoke-parallel script (Google it), which is significantly faster:

. \\server\path\to\invoke-parallel.ps1
$Computers = (get-adcomputer -filter {operatingsystem -like "*server*"}).name
$ErrorActionPreference = "SilentlyContinue"
$Scriptblock =
{
    $path = "\\" + $_ + "\c$\Windows\System32\Tasks"
    $tasks = Get-ChildItem -recurse -Path $path -File
    foreach ($task in $tasks)
    {
        $Details = "" | select ComputerName, Task, User, Enabled, Application
        $AbsolutePath = $task.directory.fullname + "\" + $task.Name
        $TaskInfo = [xml](Get-Content $AbsolutePath)
        $Details.ComputerName = $_
        $Details.Task = $task.name
        $Details.User = $TaskInfo.task.principals.principal.userid
        $Details.Enabled = $TaskInfo.task.settings.enabled
        $Details.Application = $TaskInfo.task.actions.exec.command
        $Details
    }
}
$Report = invoke-parallel -input $Computers -scriptblock $Scriptblock -throttle 400 -runspacetimeout 30 -nocloseontimeout
$Report | ft 

Example output:

Screenshot

Donald Duck
  • 2,473
  • 10
  • 29
  • 45
laoist
  • 99
  • 1
  • 3
2

Watch out: caveat: better use -literalpath, like:

$TaskInfo = [xml](Get-Content -literalpath $AbsolutePath)

or get-content will return nothing if your tasknames or foldernames happen to contain any wildcards or regex chars, like in my case "[' and "]"...

Yes, Powershell does seem to not only interpret literal strings, but also the contents of string type variables.

Mureinik
  • 3,974
  • 11
  • 28
  • 32
Patrick E
  • 21
  • 1
  • I'm presuming this was downvoted because it should be a comment on the above answer(s) that use just `Get-Content $AbsolutePath`? I'm not good with PowerShell, but I'd like to cancel the downvote for you, if you can give a citation for the above. – mwfearnley Dec 14 '18 at 10:33
2

I used this command to list all tasks:

        Invoke-Command -ComputerName "computername" -Credential Get-Credential {schtasks.exe}
  • 1
    Thanks, but I already know this command. The problem is PowerShell remoting can't be performed on all the computers in the network due to our security policy. I'm on another script way more simple. – Ob1lan Sep 16 '14 at 11:59
1

I wanted to get more (as LastRunTime and LastTaskResult) so I modified DD's script to:

$Date = Get-Date -f "yyyy-MM-dd"
$Time = Get-Date -f "HH-mm-ss"

$Computers = (get-adcomputer -filter {operatingsystem -like "*server*"}).name
$Computers >C:\Temp\servers_"$Date"_"$Time".txt
$ErrorActionPreference = "SilentlyContinue"
$Report = @()
foreach ($Computer in $Computers)
{
    if (test-connection $Computer -quiet -count 1)
    {   
        
    
        #Computer is online
        $path = "\\" + $Computer + "\c$\Windows\System32\Tasks"
        $tasks = Get-ChildItem -recurse -Path $path -File
        foreach ($task in $tasks)
        {
            $Info = Get-ScheduledTask -CimSession $Computer -TaskName $task | Get-ScheduledTaskInfo | Select LastRunTime, LastTaskResult
    
            $Details = "" | select ComputerName, Task, User, Enabled, Application, LastRunTime, LastTaskResult
            $AbsolutePath = $task.directory.fullname + "\" + $task.Name
            $TaskInfo = [xml](Get-Content $AbsolutePath)
            $Details.ComputerName = $Computer
            $Details.Task = $task.name
            $Details.User = $TaskInfo.task.principals.principal.userid
            $Details.Enabled = $TaskInfo.task.settings.enabled
            $Details.Application = $TaskInfo.task.actions.exec.command
            $Details.LastRunTime = $Info.LastRunTime
            $Details.LastTaskResult = $Info.LastTaskResult
            $Details
            $Report += $Details
        }
    }
    else
    {
        #Computer is offline
    }
}
$Report | Export-csv C:\Temp\Tasks_"$Date"_"$Time".csv -NoTypeInformation

It needs more time to execute but I think you have all the information you need.

Ramhound
  • 41,734
  • 35
  • 103
  • 130