This one was quite the journey. Obtaining this information was not as easy as one would think. Once I did my research, it turned out to be relatively easy to create a monitor in WhatsUp Gold. I will keep the remarks on that journey below the problem/solution if you are interested.
The Problem
· I have a critical Windows Scheduled Task that I need to ensure successfully completes every time it runs
· I want to be notified when a Windows Scheduled Task starts, stops, or is disabled
· WhatsUp Gold cannot monitor Windows Scheduled Tasks states directly out of the box
· WhatsUp Gold cannot monitor Windows Scheduled Tasks last run result directly out of the box
Solutions
This monitor allows you to look at the last run result of *ALL* Windows Scheduled Tasks on a system. If any result is returned from our query, the monitor is considered down. If we wanted to only look at a specific tasks, we could amend the query in the $Tasks variable.
#Get device information
$ip = $Context.GetProperty("Address");
$DnsEntry = [System.Net.DNS]::GetHostByAddress($ip)
$DnsName = [string]$DnsEntry.HostName;
# Get the Windows credentials
$WinUser = $Context.GetProperty("CredWindows:DomainAndUserid");
$WinPass = $Context.GetProperty("CredWindows:Password");
$pwd = ConvertTo-SecureString $WinPass -asplaintext -force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $WinUser,$pwd
#Task Info list
[array]$Error_List=@("0:The operation completed successfully."
"1: Incorrect function called or unknown function called."
"2: File not found."
"10: The environment is incorrect."
"267008:Task is ready to run at its next scheduled time."
"267009:Task is currently running."
"267010:Task is disabled."
"267011:Task has not yet run."
"267012:There are no more runs scheduled for this task."
"267014:Task is terminated."
"-2147216609: An instance of this task is already running."
"-2147023651: The service is not available (is 'Run only when a user is logged on' checked?)."
"-1073741510:The application terminated as a result of a CTRL+C."
"-1066598274:Unknown software exception."
"267008: The task is ready to run at its next * Scheduled time."
"267009: The task is currently running."
"267010: The task will not run at the * Scheduled times because it has been disabled."
"267011: The task has not yet run."
"267012: There are no more runs * Scheduled for this task."
"267013: One or more of the properties that are needed to run this task on a * Schedule have not been set."
"267014: The last run of the task was terminated by the user."
"267015: Either the task has no triggers or the existing triggers are disabled or not set."
"267016: Event triggers do not have set run times."
"-2147216631: A tasks trigger is not found."
"-2147216630: One or more of the properties required to run this task have not been set."
"-2147216629: There is no running instance of the task."
"-2147216628: The Task * Scheduler service is not installed on this computer."
"-2147216627: The task object could not be opened."
"-2147216626: The object is either an invalid task object or is not a task object."
"-2147216625: No account information could be found in the Task * Scheduler security database for the task indicated."
"-2147216624:Unable to establish existence of the account specified."
"-2147216623: Corruption was detected in the Task * Scheduler security database."
"-2147216622: Task * Scheduler security services are available only on Windows NT."
"-2147216621: The task object version is either unsupported or invalid."
"-2147216620: The task has been configured with an unsupported combination of account settings and run time options."
"-2147216619: The Task * Scheduler Service is not running."
"-2147216618: The task XML contains an unexpected node."
"-2147216617: The task XML contains an element or attribute from an unexpected namespace."
"-2147216616: The task XML contains a value which is incorrectly formatted or out of range."
"-2147216615: The task XML is missing a required element or attribute."
"-2147216614: The task XML is malformed."
"267035: The task is registered, but not all specified triggers will start the task."
"267036: The task is registered, but may fail to start.' Batch logon privilege needs to be enabled for the task principal."
"-2147216611: The task XML contains too many nodes of the same type."
"-2147216610: The task cannot be started after the trigger end boundary."
"-2147216609: An instance of this task is already running."
"-2147216608: The task will not run because the user is not logged on."
"-2147216607: The task image is corrupt or has been tampered with."
"-2147216606: The Task * Scheduler service is not available."
"-2147216605: The Task * Scheduler service is too busy to handle your request.' Please try again later."
"-2147216604: The Task * Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition."
"267045: The Task * Scheduler service has asked the task to run."
"-2147216602: The task is disabled."
"-2147216601: The task has properties that are not compatible with earlier versions of Windows."
"-2147216600: The task settings do not allow the task to start on demand."
)
function Get-TaskStatus ([string]$Status)
{
$Return="Return Code is $Status"
Foreach($E in $Error_List){
if($E.ToString().Contains($Status))
{
$Return=($E.split(":")[1])
return $Return;break
}
}
return $Return
}
$bDown = 0
$Tasks = gwmi -Query "SELECT * FROM MSFT_ScheduledTask" -Namespace Root/Microsoft/Windows/TaskScheduler -ComputerName $DnsName -Credential $cred
ForEach($Task in $Tasks){
If($Task.TaskPath.Length -eq 1){
$TaskName = $Task.TaskName
$TaskOutput = Invoke-WmiMethod -Class PS_ScheduledTask -Namespace ROOT\Microsoft\Windows\TaskScheduler -Name GetInfoByName -ArgumentList $TaskName -ComputerName $DnsName -Credential $cred | Select -expand cmdletOutput | Where LastTaskResult -ne 0 | Select-Object TaskName,LastTaskResult
If($TaskOutput){
$bDown = 1
$TaskName = $TaskOutput.TaskName
$TaskResult = Get-TaskStatus($TaskOutput.LastTaskResult)
$Results += "$TaskName - $TaskResult`r`n"}
}
}
If($Results){
$bDown = 1
$Context.SetResult(1, $Results);
}
else {
$Context.SetResult(0, "All scheduled tasks last run completed successfully")
}
This monitor allows you to be notified whenever a specific task on a system enters a certain state. For example, if running then monitor is down. Using this, you can notify administrators when a task begins and when it completes.
###CONFIGURATION
$TaskName = "XblGameSaveTask" #Name of the scheduled task to find state of
$TaskStatus = 4 #The status of the task to expect - possible values are below
#0 = Unknown 1 = Disabled 2 = Queued 3 = Ready 4 = "Running"
###END CONFIGURATION
#Get device information
$ip = $Context.GetProperty("Address");
$DnsEntry = [System.Net.DNS]::GetHostByAddress($ip)
$DnsName = [string]$DnsEntry.HostName;
#Get the Windows credentials
$WinUser = $Context.GetProperty("CredWindows:DomainAndUserid");
$WinPass = $Context.GetProperty("CredWindows:Password");
$pwd = ConvertTo-SecureString $WinPass -asplaintext -force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $WinUser,$pwd
#Query WMI for task information
$Tasks = gwmi -Query "SELECT State FROM MSFT_ScheduledTask where TaskName like '$TaskName'" -Namespace Root/Microsoft/Windows/TaskScheduler -ComputerName $DnsName -Credential $cred
$Result = [int]$Tasks.State
If($Result -eq $TaskStatus){
$Context.SetResult(0, "UP! The task was is in expected state")
}
else{
$Context.SetResult(1, "DOWN! The task is *NOT* in the expected state")
}
The Journey
Figuring this one out actually proved to be quite challenging and expanded my knowledge in the “new Microsoft Windows” world where you cannot simply look up data in WMI. In a lot of scenarios, you must run a PowerShell cmdlet in order to populate the information you are looking for — or so I thought. I always start with: where would I normally look for this data? The ‘Task Scheduler’ dialog in Windows has the ‘Last Run Result’ field.
But, wait how do I populate it as readable text to WhatsUp Gold? Perhaps using Microsoft’s PowerShell cmdlet, Get-ScheduledTask. That only shows the current state, not the last run result like I want. But if I pipe it too Get-ScheduledTaskInfo, then I get my last run time and last task result as desired. The problem is, this is all through PowerShell. Those commands do not support the ‘-ComputerName’ or ‘-Credential’ arguments, thus there are additional hoops to jump through as well as potential PowerShell security problems.
Questions I Asked Myself
· Is there a way to trace what is happening in WMI, like a SQL trace?
· Why can’t I run a “single query” like the Windows GUI seems to do when I open my Task Scheduled dialog?
Turns out you can trace WMI activity, but it seemed a bit complicated. I next wondered if someone had already automated the process using PowerShell. Of course, someone already had done this and shared it because it’s the internet and there are almost 8 billion people on the planet and quite a few of them have an internet connection. Using the script above, I was able to see that the namespace being called was \\.\Root\Microsoft\Windows\TaskScheduler. Interesting…I wonder what is in that namespace? That question then led me to this nice WMI explorer.
Using a combination of the WMI tracing PowerShell, WMI explorer, and the built-in PowerShell cmdlets I was able to find exactly what I was looking for. I do use SELECT * FROM MSFT_ScheduledTask to get a list of all scheduled tasks, and then invoke the same WMI method the PowerShell commands normally do in order to get my desired output.
The important thing to note here is in order to gather our data this code uses *ONLY* WMI and *NO* PowerShell commands. This sounds strange considering it is a PowerShell script, but let me explain a bit. When using plain WMI, you simply need to authenticate using your username and password. When using PowerShell and the -Credential parameter on some commands, you also have other layers of security to consider as well as remote PowerShell specific things like total sessions, trusted clients, execution policies, and session timers to name some.
Because of this, I really wanted to find a “WMI only” way of obtaining the data. This simplifies the collection process universally as it eliminates potential backward compatibility problems. Noting that, different Windows Server versions come with different versions of PowerShell ranging from version 2.0 to 6.0. I tested my script all the way back to Windows 2008 and did not note any issues.
So, the idea was to first run the WMI query to get the task name, and then invoke the same WMI method the PowerShell commands normally do. This was easy now that I had all the information. First, let us collect all the tasks.
$Tasks = gwmi -Query "SELECT * FROM MSFT_ScheduledTask" -Namespace Root/Microsoft/Windows/TaskScheduler -ComputerName $DnsName -Credential $cred
Now, using a loop let us cycle through each of the tasks and invoke the WMI method to get its last task result.
$TaskName = $Task.TaskName
$TaskOutput = Invoke-WmiMethod -Class PS_ScheduledTask -Namespace ROOT\Microsoft\Windows\TaskScheduler -Name GetInfoByName -ArgumentList $TaskName -ComputerName $DnsName -Credential $cred | Select -expand cmdletOutput | Where LastTaskResult -ne 0 | Select-Object TaskName,LastTaskResult
This will give us the ability to have a single monitor looking at *ALL* Windows Scheduled Tasks. With some slight modification, you can look for a specific task as opposed to all of them if that’s what you desire.
Based off the information I had now, I was able to easily create the second monitor to look at current state of tasks. Since I already knew my WMI query would return this, it was as simple as plugging it into the PowerShell monitor like so:
#Query WMI for task information
$Tasks = gwmi -Query "SELECT State FROM MSFT_ScheduledTask where TaskName like '$TaskName'" -Namespace Root/Microsoft/Windows/TaskScheduler -ComputerName $DnsName -Credential $cred
$Result = [int]$Tasks.State
Using the result, we can label monitors up/down very easily within WhatsUp Gold as you can see in the last part of the second script:
If($Result -eq $TaskStatus){
$Context.SetResult(0, "UP! The task was is in expected state")
}
else{
$Context.SetResult(1, "DOWN! The task is *NOT* in the expected state")
}
Well I do not consider this a successful venture unless our customers begin leveraging my hard work here. Please feel free to try out the scripts and if you have any problems, post a comment. Happy monitoring!
Get our latest blog posts delivered in a weekly email.