Working with scheduled tasks

When one deals with automation, a good IT pro should immediately think powershell and scheduled tasks. Scheduled tasks exist since a long time, Microsoft has made big improvements to the scheduler over the last 10 years. However, there isn’t an easy way to deal with scheduled tasks with Powershell V2. You could use a wrapper of the schtasks command or write custom functions handling COM objects. In powershell V3 Microsoft started to introduce cmdlets but they may not cover all the needs and have some limits. Here’s a non exhaustive list of these limitations:

  • You cannot easily enumerate all the tasks on the system with the Get-ScheduledJob command
  • Tasks created by the Register-ScheduledJob cmdlet are located in \Microsoft\Windows\PowerShell\ScheduledJobs
  • To create a task, you need to create first a trigger with the New-JobTrigger cmdlet
  • The new-jobtrigger cmdlet is limited compared to the GUI (taskschd.msc): only Once,Daily,Weekly,AtLogon and AtStartup are available compared to the following list on the technet page
    task triggers
    You can find other limitations presented by Jeffery Hicks on this page: http://jdhitsolutions.com/blog/2012/05/sql-saturday-129-session-material/

Let’s stop the criticism and have some hands on.
The excellent post of the HeyScripting Guy, Ed Wilson http://blogs.technet.com/b/heyscriptingguy/archive/2009/04/01/how-can-i-best-work-with-task-scheduler.aspx inspired me a lot.

After reading the above post, my idea was to be able to achieve the same result as the following commands:

# List all tasks from remote comupter            
schtasks  --% /s remotePC /query            
            
# Get the XML definition of the task            
schtasks  --%  /query /TN "\Microsoft\Windows\RAC\RacTask" /XML            
            
# Get all the task properties            
schtasks --%  /query /TN "\Microsoft\Windows\RAC\RacTask" /V /FO LIST

Sounds easy…
However, after digging into the XML schema of a task. My goal appeared to be a little bit ambitious 😦

Anyway, playing with XML tasks triggers could be the subject of another post. So here’s the code that could replace the the first two schtasks commands:

# Requires -Version 2.0

Function Get-AllScheduledTasks 
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$false,ValueFromPipeline=$true)]
        [System.String[]]$ComputerName = $env:COMPUTERNAME
    )
    Begin {
        

        Function Get-SubFolders ($folder,[switch]$recurse)
        {
            $folder
            if ($recurse)
            {
                $TaskService.GetFolder($folder).GetFolders(0) | ForEach-Object {
                Get-SubFolders $_.Path -Recurse
                }
            } else {
                $TaskService.GetFolder($folder).GetFolders(0)
            }
   
        }
    }
    Process {

        $ComputerName | ForEach-Object -Process {
            $alltasks = @()
            $Computer  = $_
            $TaskService = New-Object -com schedule.service
            try
            {
                $TaskService.Connect($Computer) | Out-Null

            } catch {
                Write-Warning "Cannot connect to $Computer because $($_.Exception.Message)"
                return
            }

            Get-SubFolders -folder "\" -recurse | ForEach-Object -Process {

                $TaskService.GetFolder($_).GetTasks(1) | ForEach-Object -Process {
                    $obj = New-Object -TypeName PSObject -Property @{
                        ComputerName = $Computer
                        Path = Split-Path $_.Path
                        Name = $_.Name
                    }
                    $alltasks += $obj
                }
            }
            Write-Verbose -Message "There's a total of $($alltasks.Count) tasks on $Computer"
            $alltasks
        }
    }
    End {}
}

Function Get-Task
{
[CmdletBinding()]
    param (
    [parameter(ValueFromPipeline=$false,ValueFromPipelineByPropertyName=$true,Mandatory=$false)]
    [system.string[]] ${ComputerName} = $env:computername,

    [parameter(ValueFromPipeline=$false,ValueFromPipelineByPropertyName=$true,Mandatory=$false,
               HelpMessage="The task folder string must begin by '\'")]
    [ValidatePattern('^\\')]
    [system.string[]] ${Path} = "\",

    [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [system.string[]] ${Name} = $null
    )
    Begin {}
    Process
    {
        $resultsar = @()
        $ComputerName | ForEach-Object -Process {
            $Computer = $_
            $TaskService = New-Object -com schedule.service
            try
            {
                $TaskService.Connect($Computer) | Out-Null
            } catch {
                Write-Warning "Failed to connect to $Computer"
            }
            if ($TaskService.Connected)
            {
                Write-Verbose -Message "Connected to the scheduler service of computer $Computer"
                    Foreach ($Folder in $Path)
                    {
                        Write-Verbose -Message "Dealing with folder task $Folder"
                        $RootFolder = $null
                        try
                        {
                            $RootFolder = $TaskService.GetFolder($Folder)
                        } catch {
                            Write-Warning -Message "The folder task $Folder cannot be found"
                        }
                        if ($RootFolder)
                        {
                            Foreach ($Task in $Name)
                            {
                                $TaskObject = $null
                                try
                                {
                                    Write-Verbose -Message "Dealing with task name $Task"
                                    $TaskObject = $RootFolder.GetTask($Task)
                                } catch {
                                    Write-Warning -Message "The task $Task cannot be found under $Folder"
                                }
                                if ($TaskObject)
                                {
                                    switch ($TaskObject.NextRunTime) {
                                        (Get-Date -Year 1899 -Month 12 -Day 30 -Minute 00 -Hour 00 -Second 00) {$NextRunTime = "None"}
                                        default {$NextRunTime = $TaskObject.NextRunTime}
                                    }
                                    
                                    switch ($TaskObject.LastRunTime) {
                                        (Get-Date -Year 1899 -Month 12 -Day 30 -Minute 00 -Hour 00 -Second 00) {$LastRunTime = "Never"}
                                        default {$LastRunTime = $TaskObject.LastRunTime}
                                    } 
                                                                       
                                    # Author
                                    switch (([xml]$TaskObject.XML).Task.RegistrationInfo.Author)
                                    {
                                        '$(@%ProgramFiles%\Windows Media Player\wmpnscfg.exe,-1001)'   { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\acproxy.dll,-101)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\aepdu.dll,-701)'                     { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\aitagent.exe,-701)'                  { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\appidsvc.dll,-201)'                  { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\appidsvc.dll,-301)'                  { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\System32\AuxiliaryDisplayServices.dll,-1001)' { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\bfe.dll,-2001)'                      { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\BthUdTask.exe,-1002)'                { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\cscui.dll,-5001)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\System32\DFDTS.dll,-101)'                     { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\dimsjob.dll,-101)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\dps.dll,-600)'                       { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\drivers\tcpip.sys,-10000)'           { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\defragsvc.dll,-801)'                 { $Author = 'Microsoft Corporation'}
                                        '$(@%systemRoot%\system32\energy.dll,-103)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\HotStartUserAgent.dll,-502)'         { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\kernelceip.dll,-600)'                { $Author = 'Microsoft Corporation'}
                                        '$(@%systemRoot%\System32\lpremove.exe,-100)'                  { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\memdiag.dll,-230)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\mscms.dll,-201)'                     { $Author = 'Microsoft Corporation'}
                                        '$(@%systemRoot%\System32\msdrm.dll,-6001)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\msra.exe,-686)'                      { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\nettrace.dll,-6911)'                 { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\osppc.dll,-200)'                     { $Author = 'Microsoft Corporation'}
                                        '$(@%systemRoot%\System32\perftrack.dll,-2003)'                { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\PortableDeviceApi.dll,-102)'         { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\profsvc,-500)'                       { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\RacEngn.dll,-501)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\rasmbmgr.dll,-201)'                  { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\regidle.dll,-600)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\sdclt.exe,-2193)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\sdiagschd.dll,-101)'                 { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\sppc.dll,-200)'                      { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\srrstr.dll,-321)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\upnphost.dll,-215)'                  { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\usbceip.dll,-600)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\w32time.dll,-202)'                   { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\wdc.dll,-10041)'                     { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\wer.dll,-293)'                       { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\System32\wpcmig.dll,-301)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\System32\wpcumi.dll,-301)'                    { $Author = 'Microsoft Corporation'}
                                        '$(@%systemroot%\system32\winsatapi.dll,-112)'                 { $Author = 'Microsoft Corporation'}
                                        '$(@%SystemRoot%\system32\wat\WatUX.exe,-702)'                 { $Author = 'Microsoft Corporation'}
                                        default {$Author = $_ }                                   
                                    }
                                    # Created
                                    switch (([xml]$TaskObject.XML).Task.RegistrationInfo.Date)
                                    {
                                        ''      {$Created = 'Unknown'}
                                        default {$Created = Get-Date -Date ([xml]$TaskObject.XML).Task.RegistrationInfo.Date }
                                    }
                                    
                                    # Triggers
                                    # ([xml]$TaskObject.XML).Task.Triggers.Count
                                    # Inject here dev. about triggers

                                    # Status
                                    # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383617%28v=vs.85%29.aspx
                                    switch ($TaskObject.State)
                                    {
                                        0 { $State = 'Unknown'}
                                        1 { $State = 'Disabled'}
                                        2 { $State = 'Queued'}
                                        3 { $State = 'Ready'}
                                        4 { $State = 'Running'}
                                        default {$State = $_ }
                                    }

                                    Switch (([xml]$TaskObject.XML).Task.Settings.Hidden)
                                    {
                                        false { $Hidden = $false}
                                        true  { $Hidden = $true }
                                        default { $Hidden = $false}
                                    }
                                    $resultsar += New-Object -TypeName PSObject -Property @{
                                        Created = $Created
                                        ComputerName = $Computer
                                        Author = $Author
                                        Name = $TaskObject.Name
                                        Path = $Folder
                                        State = $State
                                        Enabled = $TaskObject.Enabled
                                        LastRunTime = $LastRunTime
                                        LastTaskResult = $TaskObject.LastTaskResult
                                        # NumberOfMissedRuns = $TaskObject.NumberOfMissedRuns
                                        NextRunTime = $NextRunTime
                                        # Definition = $TaskObject.Definition
                                        Xml = $TaskObject.XML
                                        Hidden = $Hidden
                                    }
                                }
                            }
                        }
                    }
            }
        }
        return $resultsar
    } 
    End {}
}

On line 44, you can see that I’m using GetTasks(1) to enumerate all tasks including hidden ones.

Let’s demonstrate quickly what can be done with the above 2 functions.

# Get all the tasks from 2 computers,            
# get all their properties and select hidden one            
# and show only a subset of selected properties            
'192.168.0.100',$env:computername | Get-AllScheduledTasks |             
Get-Task | Where { ($_.Hidden)} |            
Select-Object -Property Computername,Name,Path,Hidden            
            
# List all the tasks, where their name match 'monitor'            
# get all their properties and display a subset of properties            
Get-AllScheduledTasks | Where { $_.Name -match 'Monitor' } |             
Get-Task | Format-Table -Property Path,Name,State,Hidden

monitor tasks

# Get the task from Adobe at the root folder            
# and show all its properties except its XML definition            
Get-Task -Path "\" -Name "Adobe Flash Player Updater" |             
Select-Object -ExcludeProperty XML -Property *            

Adobe FlashPlayer udpate task

# Get all the tasks and their properties            
# where the author isn't either Microsoft nor empty            
# and display a subset of their properties            
Get-AllScheduledTasks |  Get-Task |             
Where { ($_.Author -notmatch 'Microsoft') -and $_.Author } |            
 ft -Property Path,Name,State,Hidden,Author            

Non MSFT tasks

# Get the tasks created by Register-ScheduledJob            
# Just extract the command it launches from their XML definition            
Get-AllScheduledTasks |             
Where { $_.Path -eq '\Microsoft\Windows\PowerShell\ScheduledJobs'} |            
Get-Task | ForEach-Object -Process {            
    'TaskName : {0}' -f $_.Name            
    'Command  : {0}' -f ([xml]$_.XML).Task.Actions.Exec.Command            
    'Arguments: {0}' -f (([xml]$_.XML).Task.Actions.Exec | Select-Object -ExpandProperty Arguments)            
}

Scheduled jobs tasks

Advertisements

2 thoughts on “Working with scheduled tasks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s