Windows Task Scheduler Chart

scheduled-taskswindowswindows server 2008windows server 2012

Does anyone know if there is any utility/tool that can make a chart of all tasks in Windows Task Scheduler based on time and date, so I will know if there is any overlap at certain point of time causing server overloaded?

We have a server for all scheduled tasks and it is running slow lately due to poor design made by ex-admin, now I need to find out running window for each tasks, I can make the chart manually in excel but it is just way too much to go through one by one.

Hopefully there will be some utilities can do this.

Best Answer

This Powershell script will read Task Scheduler's Event Log and export to CSV Task Name, Start Date, Finish Date and Duration for all tasks that were launched. Then, you can feed this data to your spreadsheet of choice and build a GANTT diagram.

Requirements:

  • PowerShell 2.0
  • Windows Server 2008\Vista

Script accepts following arguments:

  • Computers: array of computer names to query. If not specified, it will query local computer.
  • MaxEvents: maximum amount of events to read from the event log. Default is 100.
  • Path: existing folder on disk, where CSV's will be saved. If not specified, script folder will be used. CSV's are named like this: COMPUTERNAME_TaskScheduler.csv.
  • User: username for remote authentication.
  • Password: password for user. If not specified, it will be requested by script.
  • Verbose: script will tell you what's going on via Write-Verbose messages.

Examples (run from the PowerShell console):

Get data from local computer, process last 100 events, save CSV to the script folder:

.\TS_Gantt.ps1

Get data from remote computers, process last 200 events, save CSV's to the c:\ts_gantt folder:

.\TS_Gantt.ps1 -Computers Sun, Earth, Moon -MaxEvents 200 -Path 'c:\ts_gantt'

Script (TS_Gantt.ps1):

Param
(
    [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [ValidateNotNullOrEmpty()]
    [string[]]$Computers = $env:COMPUTERNAME,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [ValidateRange(1, 2147483647)]
    [int]$MaxEvents = 100,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [ValidateScript({
        if(!(Test-Path -LiteralPath $_ -PathType Container))
        {
            throw "Folder doesn't exist: $_"
        }
        $true
    })]
    [ValidateNotNullOrEmpty()]
    [string]$Path,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$User,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$Password

)

# Get script path, to save CSV's, if not specified
if(!$Path)
{
    if($psISE.CurrentFile.FullPath)
    {
        $Path = $psISE.CurrentFile.FullPath | Split-Path
    }
    elseif($script:MyInvocation.MyCommand.Path)
    {
        $Path = $script:MyInvocation.MyCommand.Path | Split-Path
    }
    else
    {
        $Path = $PWD.Path
    }

    Write-Verbose "No Path specified, defaulting to: $Path"
}

# Get user credentials, if needed
if($User)
{
    Write-Verbose "User specified: $User"
    if($Password)
    {
        Write-Verbose 'Password specified, converting to credentials object'
        $SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
        $Credentials =  New-Object System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword
    }
    else
    {
        Write-Verbose 'Password not specified, requesting from user.'
        $Credentials = Get-Credential -UserName $User -Message "Enter password for user: $User" -ErrorAction Stop
        if(!$Credentials)
        {
            Write-Verbose 'User cancelled password request'
        }
    }
}

# https://mnaoumov.wordpress.com/2014/05/15/task-scheduler-event-ids/
$TaskStartId = 100
$TaskFinishId = 102
$FilterXml = @"
<QueryList>
  <Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
    <Select Path="Microsoft-Windows-TaskScheduler/Operational">*[System[(EventID=$TaskStartId or EventID=$TaskFinishId)]]</Select>
  </Query>
</QueryList>
"@

# Hashtable to hold results
$Result = @{}

# Loop through computers
foreach ($PC in $Computers){

    # Grab the events from a PC
    $Params = @{
        ComputerName = $PC
        FilterXml = $FilterXml
        MaxEvents = $MaxEvents
    }

    if($Credentials)
    {
        $Params += @{Credential = $Credentials}
    }

    Write-Verbose "Trying to get Task Scheduler's event log. Computer: $PC"
    try
    {
        $Events = Get-WinEvent @Params -ErrorAction Stop
        Write-Verbose "Success"
    }
    catch
    {
        Write-Error "Can't access Task Scheduler's event log. Computer: $PC"
        continue
    }

    if(!$Events)
    {
        Write-Error "Task Scheduler's event log is empty! Computer: $PC"
        continue
    }

    Write-Verbose 'Extracting additional data from events'
    $Events |
        ForEach-Object {
            # Hashtable for new properties
            $Properties = @{}

            # Convert the event to XML and iterate through each one       
            # of the XML message properties to extract additional data  
            ([xml]$_.ToXml()).Event.EventData.Data |
                ForEach-Object {
                    $Properties.Add($_.name, $_.'#text')
                }

            # Add extracted properties to the event object
            $_ | Add-Member -NotePropertyMembers $Properties
        }

    # Set default start\finish date for event in case
    # it's still running or was started before $MaxEvents
    $DefaultStartDate = $Events[-1].TimeCreated
    $DefaultFinishDate = Get-Date
    Write-Verbose "Default task start date: $DefaultStartDate"
    Write-Verbose "Default task finish date: $DefaultFinishDate"

    Write-Verbose 'Processing events...'
    # Group events by ID and process them
    $PcEvents = $Events |
        Group-Object -Property InstanceId |
            ForEach-Object {
                # Get Name and start\finish Date
                $TaskName = $_.Group[0].TaskName
                $StartDate = ($_.Group | Where-Object {$_.OpcodeDisplayName -eq 'Start'}).TimeCreated
                $FinishDate = ($_.Group | Where-Object {$_.OpcodeDisplayName -eq 'Stop'}).TimeCreated

                # If we can't get dates, set them to defaults
                if(!$StartDate)
                {
                    $StartDate = $DefaultStartDate
                }
                elseif(!$FinishDate)
                {
                    $FinishDate = $DefaultFinishDate
                }

                # Hashtable holding object's properties
                $ItemProp = @{
                    Name = $TaskName
                    StartDate = $StartDate
                    FinishDate = $FinishDate
                    Duration = $FinishDate - $StartDate
                }

                # Output new object to the pipeline
                New-Object psobject -Property $ItemProp |
                    Select-Object Name, StartDate, FinishDate, Duration
        }

    # Add data to results
    $Result += @{$PC = $PcEvents}
}


# Loop through results
$Result.GetEnumerator() |
    ForEach-Object {
        # Export results to CSV, one file per computer
        $CsvPath = Join-Path -Path $Path -ChildPath ($_.Key + '_TaskScheduler.csv')
        Write-Verbose "Saving data to CSV: $CsvPath"
        $_.Value | Export-Csv -LiteralPath $CsvPath -Force -NoTypeInformation
    }

Update (1): I've added ability to auth as different user (Username\Password parameters) and switched to filtering using XML, which is faster and should allow to run this script against Vista\Server 2008 PCs (avoids this bug). Also, PowerShell 2.0 compatible now.

Update (2): I've tweaked script's path detection, so now it shouldn't break in Powershell ISE. Also, I've found, that on some PCs, Task Scheduler log is disabled. Here is what to do to verify that logging is enabled:

  1. Check, if you have All Tasks History enabled. It should read Disable All Tasks History (ugh):

All Tasks History

  1. Check if Task Scheduler's Operational Event Log is enabled. Open:

    Event ViewerApplications and Services LogMicrosoftWindowsTask SchedulerOperational → right-click it, (or go to the right pane) Properties

Task Scheduler Operational Event Log

Task Scheduler Operational Event Log Properies

Update (3): Fixed handling of missing or unavailable event logs, added a bunch of Verbose messages.