Lựa chọn

 Danh mục

 Chi tiết tài nguyên
A New Task Scheduler Class Library for .NET
   Đăng bởi: host | Ngày : 01:43 05/12/09 | Xem: 637 |        

 

A New Task Scheduler Class Library for .NET

By Dennis Austin.

Introduction

The Task Scheduler is a 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. It is an expanded version of a library written by CodeProject 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, and 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, and 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 .

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 privilege. 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 to, 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 privilege on DALLAS, then the constructor would 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 D: drive.

Collapse

//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 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 not, there will be restrictions on what can be done. I haven't found these to be well documented, but 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 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 adminstrator 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 ndoc, an open source tool you can find at ndoc.sourceforge.net.

 

Bình luận

Thêm nhận xét