Search code examples
powershelleventswindows-task-scheduler

How to use Powershell to determine the difference in time between two specific Windows Event IDs


I am looking to find a way to calculate the time difference between two specific event IDs, for a specific task scheduler job name, in the past 24 hours. Then I can trigger something if the time is too short (meaning something didn't work right), or if it ran for an appropriate amount of time, end the script.

I have a script that pulls just the events for just the one job, in just the last 24 hours (code is below).

I cannot figure out how to parse the $events (subset of events just for the last 24 hours and just for that job name) to get my "start" and "stop" time in order to make a calculation.

Example of my output for a job that ran "successfully" but something happened and it actually failed - it usually takes 3-4 minutes to run:


As of 08/03/2023 09:00:01 :

MachineName TimeCreated         Id TaskDisplayName            
----------- -----------         -- ---------------            
Servername  8/3/2023 3:00:03 AM 102 Task completed             
Servername  8/3/2023 3:00:03 AM 201 Action completed           
Servername  8/3/2023 3:00:00 AM 200 Action started             
Servername  8/3/2023 3:00:00 AM 100 Task Started               
Servername  8/3/2023 3:00:00 AM 129 Created Task Process       
Servername  8/3/2023 3:00:00 AM 107 Task triggered on scheduler

Can anyone provide some ideas? I've been through all sorts of posts and cannot get an idea.

I have a Powershell script that I use to pull the event IDs for a specific Task Scheduler job every day and then append that to a text file. I end up with a running history of those results.

I then have a list of known-good IDs that I filter out and if there is an event ID listed that is not "good" then it sends me an email that something out of the ordinary has happened.

Here is what I use as code to specify the task name, my text file path and such, but I was hoping to reuse the $events variable which should have everything in there:

Start-Transcript -Path "C:\Path\TextFile.txt"

$jobName = "TestJob";
$Date = Get-Date;

$file = "C:\Path\$jobName.txt"
$file_path = Test-Path $file 
$pc = $_

#If file hasn't been created, create it
If ($file_path -ne $true){
    New-Item C:\Path\$jobName.txt
}


#Filter xml to pull task scheduler events

#Task scheduler common event IDs to ignore (treat as good/success)
$notin = 100, 102, 107, 110, 129, 140, 200, 201;

try {

#Command to connect to remote Server (if needed)
#$events = Invoke-Command -ComputerName "servername.domain" -ScriptBlock {

#Command to execute locally
$events = Invoke-Command  -ScriptBlock {

#Pulls task scheduler, only events with the job (task's) name for events in the last 24 hours
$events = @(
 Get-WinEvent  -FilterXml @"
 <QueryList>
  <Query Id="0" Path="Microsoft-Windows-TaskScheduler">
   <Select Path="Microsoft-Windows-TaskScheduler/Operational">
    *[EventData/Data[@Name='TaskName']='\$($args[0])'] and *[System[TimeCreated[timediff(@SystemTime) &lt;= 86400000]]]
   </Select>
  </Query>
 </QueryList>
"@  -ErrorAction Stop
);
Return $events;

} -ArgumentList $jobName;
}

catch {
    Write-Warning -Message "Failed to query $($env:computername) because $($_.Exception.Message)" *> "C:\Path\errors.txt" 
    #If events exist from last 24 hours for this task, append the text (log) file with those events
}

   if ($events) {
    Add-Content C:\Path\$jobName.txt "As of $Date :" -Encoding UTF8
    $events | Select MachineName,TimeCreated,Id,TaskDisplayName |  Out-File -Append C:\Path\$jobName.txt -Encoding UTF8
   }else {
       Add-Content C:\Path\$jobName.txt "No files found for last 24 hours as of $Date `r" -Encoding UTF8
   }

#Email function to email us but only email if there are tasks with codes other than the common success ones - only send an email if something is out of the ordinary   
Function email (){
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
    $EmailTo = "[email protected]";
    $EmailFrom = "[email protected]";
    $Subject = "$jobName Failure";
    $Body = $ebody | Out-String;
    $SMTPServer = "smtp.office365.com"
    $SMTPMessage = New-Object System.Net.Mail.MailMessage($EmailFrom,$EmailTo,$Subject,$Body)
    $SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587) 
    $SMTPClient.EnableSsl = $true 
    $SMTPClient.Credentials = New-Object System.Net.NetworkCredential("emailaddress", "password");
    $SMTPClient.Send($SMTPMessage)
    };
$ebody = @();
$ebody = $events | % { If ($_.id -notin $notin) {$_ | FL } }

If($ebody){email};

Stop-Transcript

Solution

  • This should do the trick:

    $LatestStart = $events | Where-Object Id -eq '100' | Sort-Object TimeCreated -Descending | Select-Object -First 1
    $LatestEnd = $events | Where-Object {($_.ActivityId -eq  $LatestStart.ActivityId) -and ($_.Id -eq 102)}
    $TimeTaken = New-TimeSpan -Start $LatestStart.TimeCreated -End $LatestEnd.TimeCreated
    

    $TimeTaken is now the timespan for how long that latest event took. Would the best solution not be to check whatever the task is actually doing though, to determine whether it was successful, or fixing the issue that is causing the task to show it completed fine when it didnt.