Saturday, February 23, 2008 2:20 AM
Calling the Task Scheduler in Windows Vista (and Windows Server 2008) from managed code
Today I received the following mail from one of my blog readers:
The task scheduler in Vista and Windows Server 2008 has improved dramatically.
Unfortunately, there are no classes in the .NET Framework that allow us VB.NET/C# developers to leverage its power.
Many .NET applications require some form of scheduling or alerting, and instead of trying to roll our own with timers and stuff, over and over again, it seems to me that it would be much nicer to use the stable and powerful foundation that the OS offers for this.
Therefore, I thought it would be a great idea for a future article on your blog, about how to access and use the Task Scheduler 2.0 from .NET, with possibly an easy-to-use .NET wrapper class?
It seems that a lot of developers don't realize or think about what's sitting there right under the hood, 'cause I haven't seen any blog posts about this new Task Scheduler and its many features from a .NET developer's perspective yet. So here's my thought... :)
Obviously I completely agree with the statement on leveraging the power of the OS foundations whenever possible rather than reinventing the wheel once over again. Task Scheduler 2.0 is a great sample of such rich functionality offered by the OS and especially now we're shipping Windows Server 2008 this becomes even more important for server applications. Nevertheless, for desktop uses the Task Scheduler provides a tremendous amount of functionality as well and Windows Vista is using its own dogfood as you can see when you execute schtasks from the command-line (indicated a few well-known tasks in red):
In this post I'll cover how to use this API in a fairly easy way from managed code though COM interop, and I'll explain some of the richness the platform can give you.
Importing the library
I assume you've already created a Console Application in C# (though all of this would work in e.g. VB.NET as well). The Task Scheduler 2.0 API lives in a file called Taskschd.dll under the System32 folder on your system. In order to reference it from your .NET project, simply go to Solution Explorer, right-click the project node and choose Add Reference:
This will create the COM interop assembly as shown in Solution Explorer:
A simple demo task
Time to write our first task, or better to register it. Essentially tasks in Task Scheduler 2.0 are represented as XML fragments as you can see from schtasks:
I'd encourage readers to take a closer look at schtasks and the information one can obtain through it about the wide variety of tasks registered on the system. The API we'll be talking to allows us to manage these tasks (create new ones for example) though code, which provides an object model to create the metadata that represents a task as displayed above under the format of XML.
Step 1 - Establish a connection with the service
In order to talk to the Task Scheduler service we need to create a proxy object to it and connect to the service, either on the local machine or remote machine. We'll stick with the local machine for the scope of this post. Start by writing the following piece of code:
This will require to import the namespace TaskScheduler as revealed by the 'SmartTag' in Visual Studio. Notice I'm using the C# 3.0 local variable type inference keyword "var" here, but one could well write:
TaskSchedulerClass scheduler = new TaskSchedulerClass();
but not (using the TaskScheduler interface provided by the interop assembly)
TaskScheduler scheduler = new TaskSchedulerClass();
(little quiz - why?). Anyhow, we still need to connect to it using the Connect method:
We can simply supply four null arguments indicating we want to use the token of the user logged on currently (tip: run using administrative privileges to manage the service effectively). Needless to say, you can use other values for those parameters to connect to a particular machine (first parameter) and to specify a particular user (parameters 2-4 specify user name, password and domain) but we won't go there in this post.
Step 2 - Create a new task
The scheduler class provides a factory approach to create new tasks using the NewTask method. It takes one parameter that's reserved for future use (a typical COM API phenomenon) and should be set to 0 for the time being. Once the task has been created, we'll set some properties on it; the most typical ones living under RegistrationInfo and Settings (others will be covered further on):
Notice the amount of settings available to tweak the task, e.g. to control behavior with respect to the current power state of the machine, idle time, etc. For our purposes, the RegistrationInfo settings are enough as shown above.
Step 3 - Triggers
When to run the task? Enter triggers. There are a bunch of different trigger types available as revealed when calling Triggers.Create:
Most of these are self-explanatory (in case of doubt more information can be found on MSDN). The more interesting part is how to create a trigger in managed code. The crux lies in the fact you need to cast the result of the Create call to the right interface, such as ITimeTrigger for the TASK_TRIGGER_TIME type. Let's show a sample:
Other similar interfaces for other types of triggers can be found in the TaskScheduler namespace. Time triggers are pretty simple to understand so let's stick with those. In the sample above, we add an identifier to the trigger (tasks can have more than one trigger by the way) as well as some specific settings for this particular trigger. Besides of this we set the start and end time for the trigger; the settings in the sample specify a point in the past and the future so our current time falls nicely in between, triggering the demo task right away once we run it. If you want more powerful triggering, you can take a look at the Repetition property or use tasks such as 'daily' or 'monthly day-of-week (DOW)' or ...
Notice the strange (ISO 8601) date/time format specified on MSDN as: YYYY-MM-DDTHH:MM:SS(+-)HH:MM. In here, the first part is self-explanatory; the part after the +- is used to specify a time zone since tasks are stored based on UTC time. Tip: a string formatter will prove useful to generate this format.
Step 4 - Actions
After when comes what. Again there's some choice amongst the different types of actions to be taken:
The EXEC task is one of the most common ones though. The common pattern is the same again: Create, cast, configure. Here's an example of a mail-task but this will require some server configuration in order to work:
Here's another one in the category EXEC:
Feel free to choose either of those, I'll go for yet another one that displays a message (by now the pattern should be captured I guess):
Step 5 - Task registration
We've created all the data needed to hook up the task. Last step is to hook it really up by calling RegisterTaskDefinition in some folder. Tasks are logically grouped in folders which you can managed through the ITaskFolder interface. One can obtain a specific folder using the GetFolder call on the scheduler service object. For demo purposes (and because of lack of inspiration tonight :-)) we'll drop the task in the root folder:
Again there's a bunch of flexibility available here but simplicity rules for blogging, so the stuff above is pretty much the easiest one can get. Basically we create (or update if it already exists) a named task "Demo" with the credentials of the logged-on used that can only be run when an interactive user is logged on to the machine and with no custom ACL to protect the task (which could be set using an SDDL descriptor).
Step 6 - Running it
To run the task we could add a line of code (though you could use schtasks /Run too or obivously rely on your (complex) triggers you've put in place). Since the API is not only about creating tasks, this shows nicely how to control tasks. Here's the whole program with the run line at the bottom:
Run it and you should see the following dialog coming out of the blue:
Victory at last :-).
Step 7 - Geeks only
Geeks can check where the message comes from e.g. using task manager (or a more advanced tool like Process Explorer):
Also, you can take a look at the task metadata using schtasks.exe:
And finally, if you want to delete the experiment, use schtasks.exe with the /Delete flag:
schtasks /delete /tn Demo
(or use the API to do so obviously :-)).
| Digg It
Filed under: Windows Vista