If you followed along with my recent articles about my PowerShell based backup system, you may recall that I used a PowerShell scheduled job an an event subscriber to monitor for file changes in key folders that I want to back up. I created the scheduled task to run at Windows startup and so far it appears to be working just fine. However, I did catch one instance where the scheduled task stopped. I didn't find any reason, although I didn't dig too deeply either. I simply restarted the scheduled task. But it got me thinking that since I'm relying on this task to log new and changed files, I need to make sure it is watching. In other words, I need to watch the watcher. This is the approach I took.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Checking the Task
It is simple enough to use Get-ScheduledTask to verify the status.
If it has stopped, I can use Start-ScheduledTask. Naturally, I don't want to remember to run this several times a day so I took a slightly different approach using my PowerShell profile script. Because I always have a PowerShell session open this seemed like a smart idea. The first thing I do when logging on is to open a PowerShell session. In my PowerShell profile script I added this code.
$task = Get-ScheduledTask -taskname DailyWatcher if ($Task.state -eq 'Ready') { Write-Host "Restarting scheduled task $($task.Taskname)" -ForegroundColor yellow $task | Start-ScheduledTask }
I realize this only runs once every few days but it's something. However, because my PowerShell session is pretty much constantly running I can create an event subscriber watching for the scheduled task to change.
Monitoring the Watcher
If you recall from earlier articles, an event subscriber only lasts for as long as your PowerShell session is open. That's why I created DailyWatcher subscription in a scheduled task that is constantly running. Since my PowerShell session is open almost as constantly, I can create an interactive event subscriber. If I restart PowerShell, I'll simply get a new version of the event subscriber. In my profile script I added these lines:
#create the event subscription to monitor the scheduled task . C:\scripts\MonitorDailyWatcher.ps1 | Out-Null
Let's take a peek at this script.
#requires -version 5.1 #requires -module CimCmdlets #verify the scheduled task exists and bail out if it doesn't. $name = "DailyWatcher" Try { $task = Get-ScheduledTask -TaskName $Name -ErrorAction Stop } catch { Throw $_ #make sure we bail out return } #if by chance the task is not running, go ahead and start it. if ($task.State -ne 'running') { $task | Start-ScheduledTask } <# the scheduled task object is of this CIM type Microsoft.Management.Infrastructure.CimInstance#Root/Microsoft/Windows/TaskScheduler/MSFT_ScheduledTask #> $query = "Select * from __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA 'MSFT_ScheduledTask' AND TargetInstance.TaskName='$Name'" $NS = 'Root\Microsoft\Windows\TaskScheduler' #define a scriptblock to execute if the event fires $Action = { $previous = $Event.SourceEventArgs.NewEvent.PreviousInstance $current = $Event.SourceEventArgs.NewEvent.TargetInstance if ($previous.state -eq 'Running' -AND $current.state -ne 'Running') { Write-Host "[$(Get-Date)] Restarting the DailyWatcher task" -ForegroundColor green Get-ScheduledTask -TaskName DailyWatcher | Start-ScheduledTask } } Register-CimIndicationEvent -SourceIdentifier "TaskChange" -Namespace $NS -query $query -MessageData "The task $Name has changed" -MaxTriggerCount 7 -Action $action
The script is written to be independent of my PowerShell profile. It first verifies that scheduled task exists and bails out with an error if it isn't found. The script will also start the task if it is not running. Now for the good stuff.
One way to create an event subscriber is through WMI. We've always been able to set up something to watch for changes to an instance of something from WMI. But because I think of the WMI-specific cmdlets as deprecated, I am going to use the CIM equivalent commands. The command to create the event subscription is Register-CimIndicationEvent. The key element is the query.
$query = "Select * from __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA 'MSFT_ScheduledTask' AND TargetInstance.TaskName='$Name'"
The __InstanceModificationEvent reference is to a system class that should exist in all namespaces. This class should fire an event whenever a changes is made to a specified type of object. In my query this is specified by using the TargetInstance property and the ISA operator. If the modified object is a ScheduledTask, which I discovered by piping Get-ScheduledTask to Get-Member, and if the specific scheduled task name matches my task, an event will be triggered and handled by the event subscriber. The other very important part of this query is the polling. In the query I'm asking to be notified within 10 seconds of a change. In other words, check every 10 seconds. You want this number to be small enough to meet your needs but not so small that you end up constantly polling.
The other element in my script is the Action scriptblock. When an event fires, part of the event arguments are references to the previous instance of the object and the new, or target instance. This comes in handy when when want to do something based on a property difference which is more or less what I am doing. If the previous instance was running, and the new instance is not running, then I'll start the scheduled task. It is really that simple.
When my profile runs, I end up with this event subscriber.
If the task stops, the event subscriber runs the action script block, which as written, displays a message using Write-Host in my PowerShell session.
I'm not expecting this to happen often if at all. This code is more like insurance for me.
Creating a CIM-based event subscription can be a useful management tool. I recommend starting out simple and be sure to read full help and examples.
I'll be back next time with some other fun stuff derived from my PowerShell backup work.