Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A New Task Scheduler Class Library for .NET

0.00/5 (No votes)
17 Dec 2007 36  
A revision of a Task Scheduler class library by David Hall

Introduction

Task Scheduler is the Windows service that schedules and automatically starts programs. Windows Explorer presents a user interface to the service when you browse the %WINDIR%\TASKS folder, typically from the shortcut in the control panel. From the command line, the schtasks command and the old at command do the same. Programmers have a well-documented COM interface, but the .NET framework does not offer any wrapper for it. The present library provides that .NET wrapper.

Note: Since this library was created, Microsoft has introduced a new task scheduler (Task Scheduler 2.0) for Windows Vista. This library is a wrapper for the Task Scheduler 1.0 interface, which is still available in Vista and is compatible with Windows XP, Windows Server 2003 and Windows 2000.

The library is an expanded version of a package written by CodeProject.com member David Hall. See his article. In the original work, David demonstrated how a collection of COM interfaces could be tamed into a logical class library in the .NET style. This new version offers many improvements, including the elimination of COM memory leaks. Fixing the memory leak problem required the adoption of an incompatible class hierarchy, but, for clients in transition, the original hierarchy remains available as a kind of differently organized view of the same objects. It is deprecated, however, because of the leaks. A later section on compatibility gives more detail.

Documentation is contained in an HTML help file, MSDN-style, which is downloaded along with the library. The documentation is intended to be self-contained and sufficient for any client to use the library. MSDN documents the COM interface on which the library is based. It is worth consulting when the library's documentation lacks.

The second download contains the source code for the library and for a C# test application that also serves as sample code. The test application is a trivial command line interpreter that operates on scheduled tasks. It can easily be modified to insert whatever tests you may want to make.

This article introduces the library's class hierarchy and also describes some of the changes that were made from the original version.

Contents

Classes
Sample Code
Changes from Version 1
Compatibility with Version 1
Changes in Internals
History

Classes

The complete class hierarchy is illustrated by the following diagram, drawn to the standard David Hall set in his article. The abstract class StartableTrigger makes the hierarchy a bit more complicated than I'd like, but is included for completeness. For most purposes, clients can more simply consider the various concrete trigger classes as direct subclasses of Trigger .

Class Diagram

ScheduledTasks

A ScheduledTasks object represents the Scheduled Tasks folder on a particular computer. This may be either the local machine or a named machine on the network to which the caller has administrative privileges. In the machine's Scheduled Tasks folder, Windows keeps information for each scheduled task in a file with a *.job extension. All tasks in the folder are said to be "scheduled," regardless of whether they will actually ever run.

// Get a ScheduledTasks object for the computer named "DALLAS"
ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");

You use a ScheduledTasks object to access the individual tasks. Each task has a name, the same as its file name without the extension. From the ScheduledTasks object you can obtain the names of all the tasks currently scheduled. There are methods to create, open and delete tasks, all of which use the task's name.

// Get an array of all the task names
string[] taskNames = st.GetTaskNames();

A ScheduledTasks object holds a COM interface. When you are finished using the object, call its Dispose() method to release the COM interface.

// Dispose the ScheduledTasks object to release COM resources.
st.Dispose();

Task

A Task object represents an individual scheduled task that is open for access. Its properties determine what application the task runs and the various other items you can see in the Explorer user interface. One such property is its TriggerList, another class discussed below. After creating a Task or opening and modifying a Task, it must be saved to record the new state in its file. You can save it under its current name or you can provide a new name. Saving with a new name is like "Save As" in a Windows application: the open Task remains associated with the new name. There is no public constructor for Task. A Task can only be created by a ScheduledTasks object using its Open() or Create() methods.

// Open a task named "foo" from the local computer's scheduled tasks
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");

A Task retains COM interfaces. When you are finished using a Task, call its Close() method to release the interfaces.

// Close the Task object to release COM resources.
t.Close();

TriggerList

A TriggerList is a collection of Trigger objects. Every Task has a TriggerList that can be obtained from a get-only property. There are no public constructors for TriggerList. Every TriggerList is associated with a Task and is constructed along with the Task, so a TriggerList can only be obtained from its Task object.

// Get the triggers for the local task "foo".
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
TriggerList tl = t.Triggers;

A TriggerList becomes invalidated when its task is closed. (Further access causes an error.)

Trigger

Triggers are conditions which, when satisfied, cause a task to run. In the Explorer UI, triggers are called "schedules." There are several types of triggers and different data is appropriate for different types. Thus, the Trigger class is actually an abstract class from which an assortment of concrete classes is derived. Each concrete Trigger class has unique constructors that specify the particular type of condition it represents. To set a new trigger for a task, construct a Trigger of the appropriate variety and then add it to the task's TriggerList .

// Add a trigger to run task "foo" at 4:30 pm every day
Trigger tg = new DailyTrigger(16, 30);  // hour and minute
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
t.Triggers.Add(tg);
t.Save();

When a new Trigger is created, it is not associated with any Task or TriggerList and is said to be unbound. An unbound Trigger does not hold any COM resources and has no special usage protocol. When a Trigger is added to a TriggerList, it is then said to be bound and it remains so unless it is removed from that collection. A bound Trigger holds a COM interface. The COM interface is released when the corresponding Task is closed, so no special care is required on the part of the client. The distinction between unbound and bound Triggers rarely comes up in client code, but the following points are the essence:

  • A Trigger can only be in one TriggerList at a time. Therefore a bound Trigger can�t be added or assigned to a TriggerList because it is already in one. To put the same Trigger in a second TriggerList, use Clone() to create an unbound copy.
  • A bound Trigger object can�t be used after its task is closed. (Further access causes an error.)

Sample Code

Several examples should suffice to give the flavor of interaction with the Task Scheduler. See the accompanying MSDN-style help file for complete documentation of the classes.

Listing the Scheduled Tasks from a Computer

This example connects to a computer named "DALLAS" and prints a summary of its scheduled tasks on the console. If DALLAS could not be accessed or the user account running the code does not have administrator privileges on DALLAS, then the constructor will throw an exception. Such exceptions aren't handled in the sample code.

// Get a ScheduledTasks object for the computer named "DALLAS"
ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");

// Get an array of all the task names
string[] taskNames = st.GetTaskNames();

// Open each task, write a descriptive string to the console
foreach (string name in taskNames) {
    Task t = st.OpenTask(name);
    Console.WriteLine("  " + t.ToString());
    t.Close();
}

// Dispose the ScheduledTasks object to release COM resources.
st.Dispose();

Scheduling a New Task to be Run

Create a new task named "D checker" that runs chkdsk on the D: drive.

//Get a ScheduledTasks object for the local computer.
ScheduledTasks st = new ScheduledTasks();

// Create a task
Task t;
try {
    t = st.CreateTask("D checker");
} catch (ArgumentException) {
    Console.WriteLine("Task name already exists");
    return;
}

// Fill in the program info
t.ApplicationName = "chkdsk.exe";
t.Parameters = "d: /f";
t.Comment = "Checks and fixes errors on D: drive";

// Set the account under which the task should run.
t.SetAccountInformation(@"THEDOMAIN\TheUser", "HisPasswd");

// Declare that the system must have been idle for ten minutes before 
// the task will start
t.IdleWaitMinutes = 10;

// Allow the task to run for no more than 2 hours, 30 minutes.
t.MaxRunTime = new TimeSpan(2, 30, 0);

// Set priority to only run when system is idle.
t.Priority = System.Diagnostics.ProcessPriorityClass.Idle;

// Create a trigger to start the task every Sunday at 6:30 AM.
t.Triggers.Add(new WeeklyTrigger(6, 30, DaysOfTheWeek.Sunday));

// Save the changes that have been made.
t.Save();
// Close the task to release its COM resources.
t.Close();
// Dispose the ScheduledTasks to release its COM resources.
st.Dispose();

Change the Time that a Task will be Run

This code opens a particular task and then updates any trigger with a start time, changing the time to 4:15 am. This makes use of the StartableTrigger abstract class because only those triggers have a start time.

// Get a ScheduledTasks object for the local computer.
ScheduledTasks st = new ScheduledTasks();

// Open a task we're interested in
Task task = st.OpenTask("D checker");

// Be sure the task was found before proceeding
if (task != null) {
    // Enumerate each trigger in the TriggerList of this task
    foreach (Trigger tr in task.Triggers) {
        // If this trigger has a start time, change it to 4:15 AM.
        if (tr is StartableTrigger) {
            (tr as StartableTrigger).StartHour = 4;
            (tr as StartableTrigger).StartMinute = 15;
        }
    }
    task.Save();
    task.Close();
}
st.Dispose();

Frequently Asked Questions

Why am I getting access exceptions?

This problem usually comes up for clients who want to use the Task Scheduler from ASP.NET code. Ordinarily, such code runs in the ASPNET account which has rather low privilege and can't use the Task Scheduler. The solution to this is to set your code to run in another, more privileged account. This is called impersonation, and you can set it up in your web.config file.

The Task Scheduler doesn't require the client to run with administrative privilege, but if it's not, there will be restrictions on what can be done. I haven't found these to be well-documented. However, until recently it seemed that non-administrators could see and manipulate the tasks they created, but no others. In Windows XP SP2, there seems to be some generalization. In the Explorer, there is a new Security tab on the task Properties dialog box. There is also do a little documentation explaining that the file permissions on the task will govern what other users can do with them. Read = look at it, Read/Execute = run the task, Write = modify the task.

Must I have an account and password for a task?

A scheduled task must be given a specific account in which to run or it may be set to run in the local system account. The local system account is a pseudo-account used by many of the standard services. It has broad access to the local system, but it cannot always interact with the user directly and it has no network privileges. To set a task to run in the local system account, the client must be already running in that account or in an administrator account.

If the task will need to interact with the user, you need to set a specific user account and the task will only interact with that user. If your client runs in different accounts depending on who is using it, you can have it schedule tasks without actually knowing the user's password. To this, you set a specific task flag, RunOnlyIfLoggedOn and give the user name and a null password.

Why am I getting access denied on a remote machine?

You must be running in an account that has sufficient privileges, both on your local machine and the remote machine. If there is no domain controller, you will need to be running in an account that is set up with the same name and same password on both machines. There are lots of ways this can go wrong, but you can work on correcting it by trying to access private files across the two machines and make that work first.

I am opening the Scheduled Tasks on a remote machine, but it contains no tasks.

This is generally because you named the machine without including two backslashes at the beginning. For some reason, the Task Scheduler will find the machine and seem to have opened it, but it will have no tasks.

How to set the maximum runtime to be unlimited?

The underlying service requires a special value be passed as the MaxRunTime. This worked in earlier versions of the library, but was awkward. There is no a new property named MaxRunTimeLimited. Set it False to have an unlimited time.

How did you make the cool documentation?

The MSDN-like help file was built with Microsoft's Sandcastle (preview version) and Eric Woodruff's GUI front end, Sandcastle Help File Builder.

Does the library work in Vista?

Vista has introduced a new Task Scheduler with a new program interface, 2.0. This library accesses the old 1.0 interface so the same code can run in Vista, XP, Server 2003 or Windows 2000. The price of compatibility is that you don't get any of the features of the new Task Scheduler.

I've tried the library on Vista and it seems to work fine. I have not figured out all the privilege requirements, though, so beware. Vista's user account control (UAC) means that you may have difficulty with even the test application included in the package. The task scheduler will allow you to do some things with only user privileges, but others require you to be running as an administrator.

In the 2007 update to the project, I modified the test application so that it automatically runs with administrator privilege. You have to okay it, of course. This was done by adding a manifest file to the project and performing an automated post-build step to add the manifest to the executable. This uses the mt.exe tool, so that has to be in your path somewhere. My mt.exe is in the Windows SDK. If you use Visual Studio 2008, you can add the manifest without resorting to a post-build step. Just add it to the TestApp project.

Changes from Version 1

The primary aim of this new library was to provide control over COM resources. That required a change to the access model so clients could clearly tell when resources were allocated and freed. The new model is like a document application. Clients open and close tasks, and there are the equivalents of save and save-as that make changes permanent. When a Task is open it holds COM interfaces, etc., which are then released when the Task is closed. This avoids the memory leaks in the original design, at least to the extent that clients correctly close their tasks.

Another goal was expansion of the XML documentation and therefore the help file. I learned a lot about the Task Scheduler while testing the library and I tried to include what I learned. Many other improvements were made, but the following are most noteworthy:

  • A new overloaded version of Save() that takes a name argument. This allows you to change the name of an existing task.
  • Many formerly get-only properties on Trigger objects may now also be set.
  • There is a now user interface for editing tasks the way the Windows Task Scheduler does.
  • ICloneable was added to Trigger. This allows a Trigger to be copied for use in multiple Tasks.

Compatibility with Version 1

As noted above, the original classes from David Hall's library are still available for transitional use, so the library is essentially "plug-compatible." There are a couple of other minor issues that might affect a few users:

  1. The Hidden property of a Task becomes effective at Save() like all other properties. In the original version, it took effect immediately. The Hidden property is really only for compatibility with version 1 because the task flags provide the same capability.
  2. Version 1 provided the type of a trigger as a special TriggerType enum value, but that feature is now gone. Use the GetType() method to determine the type of a Trigger just as you would for any other type.

Internals

The source files are fairly easy to browse and are written to a high standard of readability. Well, readability is in the eye of the beholder. I can only claim that I tried to write and document everything well. Open the solution in Visual Studio and browse around.

There is a lot to be learned in reading any code, but one thing I can recommend the library for is learning how to write a wrapper for a COM interface. There are lots of design decisions, but the really messy stuff comes in how you actually interface with COM from C#. If you have to do it, this code might be interesting, especially from the aspect of resource allocation and deallocation.

String properties returned by the COM interfaces for the Task Scheduler are generally allocated in COM task memory and a pointer to the string is returned. If the marshalling handles the conversion of the pointer to a System.String, the memory is not released. I changed these to be marshaled as System.IntPtr and wrote code to free the memory before returning to the client.

History

06/10/02

  • Initial release

06/17/02

  • Added Scheduler and TaskList classes to make the library upward compatible with the original library by David Hall. The classes are not otherwise needed.
  • Expanded the article to explain compatibility issues.

08/09/02

  • Updated the library to fix bugs in Trigger properties reported by Mark Stafford.
  • Updated the documentation for the SetAccountInformation method.
  • Included the taskscheduler.xml file with the library and document download. This provides documentation in the Object Browser.
  • Repaired some formatting and added this history section to the article.

08/20/02

  • Updated the library to fix a Save(name) problem noted by Ashley Barton.

10/25/02

  • Added the method DisplayPropertySheet() to Task. Like the existing DisplayForEdit, it displays the properties dialog for editing the task. The new parameter, however, gives control over which pages appear in the dialog. It actually makes DisplayForEdit obsolete, but DisplayForEdit is still available for compatibility. The work was financed by CodeProject.com member Ingram Leedy. Thank you, Ingram, for allowing this enhancement to be made available to the community.
  • Added the method NextRunTimeAfter() to Task. The existing property NextRunTime returns the next time a task will run, but the new method allows the caller to specify a time from which to calculate the next run. That allows all future run times to be computed interactively.
  • Raised the assembly version to 1.1.
  • Updated the article to add some information and improve readability (I hope).

9/8/04

  • Updated the HTML help documentation with some minor corrections, especially the description of task flags.
  • Updated the article with some minor corrections and added a FAQ.
  • Added one task property, MaxTimeLimited to simplify setting of time limits.
  • Fixed the Hidden property and the Hidden task flag to unhide as well as hide (bug fix).
  • Raised assembly version to 1.2

11/30/07

  • Minor changes to compile with Visual Studio 2005/.NET 2.0. Also works with VS 2008.
  • Added an overload to SetAccountInformation that takes the password as a SecureString (Contributed by CodeProject.com member "aule").
  • Help file improved by switching to Microsoft's Sandcastle tool and the Sandcastle Help File Builder by Eric Woodruff.
  • Added a post-build step to add an administrator level elevation manifest to the test application.
  • Raised assembly version to 1.3

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here