6

We have a couple of servers with hundred of scheduled tasks on them... and it is becoming problematic to find a right maintenance window. Is there some tool which allows for a graphical representation (like in a Gantt graph) of the Windows Task scheduler events?

Barring this, I've been fiddling with Powershell to implement the tool myself, using get-scheduledtask and get-scheduledtaskinfo, but while they do provide the properties LastRunTime and NextRunTime , I cannot find info about the duration of a task. I mean, If I started a task at 9AM, and the thread returned at 9.10, I do see in the history gui it ran for 10 minutes.., but I cannot get the same info using Powershell. Any hint? Thanks!

Aldo
  • 303
  • 1
  • 4
  • 14

2 Answers2

7

The information you're looking for is not persisted as a property of a task. You need to extract it from the task history. Beginning and end of an action are logged with ID 200 and 201 respectively.

Get-WinEvent -FilterHashtable @{
    'LogName' = 'Microsoft-Windows-TaskScheduler/Operational'
    'ID'      = 200, 201
} | Group-Object ActivityID | ForEach-Object {
    $start = $_.Group |
             Where-Object { $_.Id -eq 200 } |
             Select-Object -Expand TimeCreated -First 1
    $end   = $_.Group |
             Where-Object { $_.Id -eq 201 } |
             Select-Object -Expand TimeCreated -First 1

    New-Object -Type PSObject -Property @{
        'TaskName'  = $_.Group[0].Properties[0].Value
        'StartTime' = $start
        'Duration'  = ($end - $start).TotalSeconds
    }
}
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • This is gold. Please tell me you at least made *one* mistake in constructing this... – Lieven Keersmaekers Nov 05 '18 at 13:14
  • 1
    Don't know about mistakes, but I pieced it together bit by bit (identify where the history is stored -> identify the IDs to query for -> identify a property for grouping the results ...). – Ansgar Wiechers Nov 05 '18 at 13:24
  • really, it is gold. The only issue I can think of is that it is excruciantly slow.. on one of our production servers (16 core Xeon v5, 128Gb of RAM and enterprise grade solid state storage) with some 1hundred schedules and 6 months of history, it took around 15 minutes of execution – Aldo Nov 05 '18 at 14:06
  • but the code behind is very solid.. and I think there's space for optimizations on my side (like searching just the latest week of events and so on). Some of the tasks have a negative duration tho, I think there's a case that is not properly handled – Aldo Nov 05 '18 at 14:07
  • 1
    on a first analysis (unexpectedly), the slowest part is the extraction of the events. About the XML conversion.. is it necessary? $_.Group.properties[0].value seem to hold the name of the task already.. or I'm missing something? – Aldo Nov 05 '18 at 14:22
  • 1
    @Aldo `$_.Group.Properties[0]` should indeed suffice. I had overlooked that. Fixed in my answer. As for the negative durations: my sample code just demonstrates the general principle. It doesn't handle any edge cases. – Ansgar Wiechers Nov 05 '18 at 14:54
  • @AnsgarWiechers : indeed you are right. Thanks again for the great help! (now going to plot chart all this stuff :) – Aldo Nov 05 '18 at 15:50
  • @AnsgarWiechers Ah, you having to piece it together gives me peace of mind. That's the workflow I have for most if not any problems I try to solve. You got me worried that I was falling too far behind without ever being able to catch up ;) – Lieven Keersmaekers Nov 06 '18 at 06:17
2

Here are some tips to use with querying the events with Get-WinEvent comandlet. To search for desired Task name, construct a XPath xml and pass it via -FilterXml param To limit the Event count use -MaxEvents

    #Timeframe to look 
# Last 24H -> 24*60*60*1000 = 86400000
$TimeInterval = 24 * 60 * 60 * 1000

# paths for scheduled tasks in Task Scheduler namespace:
$TaskName = '\Microsoft\Windows\.NET Framework\.NET Framework NGEN v4.0.30319'

# must be even as we need a minimum of two events - 200 + 201
$EventsToGet = 2

$XMLQuery_TaskScheduler = @"
<QueryList>
  <Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
    <Select Path="Microsoft-Windows-TaskScheduler/Operational">*[System[(EventID=200 or EventID=201) and TimeCreated[timediff(@SystemTime) &lt;= $($TimeInterval)]] and EventData/Data[@Name='TaskName']='$($TaskName)']</Select>
  </Query>
</QueryList>
"@


$TestEvents = Get-WinEvent -FilterXml $XMLQuery_TaskScheduler -MaxEvents $EventsToGet -ErrorAction Stop
$TestEvents.Count

$TestEvents | Group-Object 'ActivityID' | ForEach-Object {
# use the great code above by Ansgar Wiechers
...

}
386DX
  • 21
  • 2