6

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.

3
  • any idea guys? looks like no such utility hum....
    – Root Loop
    Dec 24, 2014 at 13:08
  • To create such a (GANTT) chart, you would probably need not only the start time but also the end time or duration for every task. This information is not available up-front under Windows. So, if you know the durations from experience, you'ld have to assemble the data for an external charting tool like sourceforge.net/projects/ganttproject Dec 24, 2014 at 15:07
  • Thanks, tried something similar to this, it is not exactly what i m looking for. I think its better do it myself in excel.
    – Root Loop
    Dec 24, 2014 at 15:58

1 Answer 1

3

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.

10
  • i will try this for sure even i made the chart manually already.
    – Root Loop
    Mar 4, 2015 at 2:11
  • Keep me posted, I'm interested in feedback! Mar 4, 2015 at 2:31
  • doesn't work for me under Windows 8. It crashes here: "$Events = Get-WinEvent -ComputerName $PC -FilterHashtable $Filter -MaxEvents $MaxEvents" Mar 4, 2015 at 5:42
  • Strange, I'm running 8.1 and it worked just fine for local PC and 3 remote servers. Can you post exception details? I wonder, could this be an auth issue, because I've run this script it as domain admin. Does it crashes even if you try to run it against localhost (no params at all)? Mar 4, 2015 at 7:03
  • I tried this on my local box, it worked, but the result was not accurate, all startDate were the same, the finisdate were empty, and all duration were the same....For remote PC, error message "Can not validate argumetn on parameter "Path""
    – Root Loop
    Mar 4, 2015 at 16:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .