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
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
.
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.
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.
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.
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.
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.
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.
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
Trigger
s 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
.
Trigger tg = new DailyTrigger(16, 30);
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 Trigger
s 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.)
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.
ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");
string[] taskNames = st.GetTaskNames();
foreach (string name in taskNames) {
Task t = st.OpenTask(name);
Console.WriteLine(" " + t.ToString());
t.Close();
}
st.Dispose();
Scheduling a New Task to be Run
Create a new task named "D checker" that runs chkdsk on the D: drive.
ScheduledTasks st = new ScheduledTasks();
Task t;
try {
t = st.CreateTask("D checker");
} catch (ArgumentException) {
Console.WriteLine("Task name already exists");
return;
}
t.ApplicationName = "chkdsk.exe";
t.Parameters = "d: /f";
t.Comment = "Checks and fixes errors on D: drive";
t.SetAccountInformation(@"THEDOMAIN\TheUser", "HisPasswd");
t.IdleWaitMinutes = 10;
t.MaxRunTime = new TimeSpan(2, 30, 0);
t.Priority = System.Diagnostics.ProcessPriorityClass.Idle;
t.Triggers.Add(new WeeklyTrigger(6, 30, DaysOfTheWeek.Sunday));
t.Save();
t.Close();
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.
ScheduledTasks st = new ScheduledTasks();
Task task = st.OpenTask("D checker");
if (task != null) {
foreach (Trigger tr in task.Triggers) {
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:
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:
- 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. - 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.
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.
06/10/02
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
I'm a long-time programmer in compilers, operating systems, microprogramming, applications, and web programming. I've got way too many years of experience piled up--old Burroughs mainframes, Unix, Macintosh, and Windows.
Software was finally beginning to get boring, but then along came .Net and brought the fun back!