Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

Task Scheduler Library for .NET

Rate me:
Please Sign up or sign in to vote.
4.84/5 (38 votes)
9 Jul 2009CPOL7 min read 518.4K   9.8K   194   124
A library for .NET that encapsulates the Task Scheduler COM object.

Important Notice!!!

This library has been significantly changed and updated to include all the functionality available in the Task Scheduler 2.0 library available on Vista/Server 2008 and later. The new library is available as a community project on GitHub and is available via NuGet. This new library automatically supports all current Windows platforms and uses the most current library. It compensates for missing features from both libraries where possible. The model is simpler and more modular. I won't be continuing any work on the code posted here in CodeProject as it does not conform to the constructs of the updated library.

The basics of building .NET Interop wrappers around COM libraries that are documented here are what I used for the new project. If you’re trying to understand how to do that, look here.

Thanks.

Introduction

The Task Scheduler is a component delivered with Internet Explorer since version 4.0, and is made up of a set of COM objects. The user can interact with tasks through the "Scheduled Tasks" entry in the Control Panel. The Task Scheduler objects provide an excellent means of scheduling events for your programs. I have never really liked the "pseudo object-oriented" approach taken by Microsoft in their design of COM objects. I have wrapped the Task Scheduler COM objects into a series of .NET classes. These classes expose the entire library and make it conform to more of a ".NET" style of class design.

In this article, I will present the library and the things I learned along the way about using .NET development and COM Interop services. This article has the following sections:

Learnings

One of the things I wanted to do was provide a single library that would give full access to the Task Scheduler. This meant that I could not use the Visual Studio .NET model of importing COM objects which creates a new .dll file for each imported class. To accomplish this, I had to use some .NET Framework SDK tools to create the .cs file that would provide access to the COM objects via COM Interop services. Since Microsoft decided to not provide a type library for the Task Scheduler, I created one with the MIDL tool from the Platform SDK and the mstask.idl file provided by Microsoft. Once I had the library, I was able to use the TLBIMP tool from the .NET Framework SDK to create an assembly. I then used the Object Browser along with the original .idl file to effectively cut and paste the class, method, and property definitions to my own .cs file. During this part of my adventure, I discovered two important things. First, the order of methods in the interop classes must match the order in the .idl file, and if an interface derives from another interface, you have to provide the base interface's methods in the interop (there is no derivation allowed). Second, the TLBIMP tool does some special things that aren't initially apparent in the area of attributes.

  • All structures must have the [StructLayout(LayoutKind.Sequential)] attribute defined.
  • All unions must have the [StructLayout(LayoutKind.Explicit)] attribute defined, and each block of the union must have the [FieldOffset(0)] attribute defined.
  • All parameters that convert from a wide string to System.String must have the [MarshalAs(UnmanagedType.LPWStr)] attribute defined.
  • All parameters that pass a COM interface must have the [MarshalAs(UnmanagedType.Interface] attribute defined.
  • All parameters that pass a .idl defined structure must have the [MarshalAs(UnmanagedType.Struct)] attribute defined.
  • Dealing with pointers to variable length parameters is a black art.
  • If the .idl file says a parameter is [in], the parameter is [In] in C#.
  • If the .idl file says a parameter is [out], the parameter is [Out] in C#.
  • If the .idl file says a parameter is [in, out], mark the parameter as ref and specify if it is really [In] and/or [Out].
  • If the .idl file says a parameter is an [out] pointer to a value, you do not need to make it a pointer in C#.
  • If the .idl file says a parameter is a pointer to a pointer (**) and it is not an interface, you have to make it a System.IntPtr and deal with converting it in the code.
  • When getting an interface pointer where the .idl file has it as [out] ISomeInterface** pSomeInt, you will use [Out, MarshalAs(UnmanagedType.Interface)] out ISomeInterface SomeInt.
  • If a method returns an HRESULT as an out or retval parameter, it must have the [MarshalAs(UnmanagedType.Error] attribute and be passed as a System.Int32.

There are two bits of coding that were nontrivial. The first was the Hidden property on the Task class. Warning, this was a hack. I realized that the tasks are actually just files in a special directory. Once they are created, you have to use the IPersist interface to save them. This interface lets you find out the path of the file. Using this information, I experimented and found that setting the hidden attribute on the file does hide the task from the UI but does not seem to have an effect on the COM object finding it. I did have to clear that attribute before saving it though.

The second challenge was demystifying the black art of variable length arrays. This was done in the Task.Tag property. I decided to use serialization to get and put information into the tag. This provides a way to encode and decode objects into a byte stream. To put the object into the array, I first make sure it is serializable. I then serialize it into a MemoryStream and pass the stream's buffer into the COM object.

C#
set
{
   if (!value.GetType().IsSerializable)
      throw new ArgumentException("Objects set as Data for Tasks must " + 
                                  "be serializable", "value");
   BinaryFormatter b =3D new BinaryFormatter();
   MemoryStream stream =3D new MemoryStream();
   b.Serialize(stream, value);

   iTask.SetWorkItemData((ushort)stream.Length, stream.GetBuffer());
}

To get the object out of the array, I had to use the Marshal.Copy function to convert the IntPtr to a byte array. I then converted the byte array into a BinaryFormatter object so that I could convert the data back into an object instance. The code below shows the process.

C#
get
{
   ushort DataLen;
   IntPtr Data;
   iTask.GetWorkItemData(out DataLen, out Data);
   byte[] bytes =3D new byte[DataLen];
   Marshal.Copy(Data, bytes, 0, DataLen);
   MemoryStream stream =3D new MemoryStream(bytes, false);
   BinaryFormatter b =3D new BinaryFormatter();
   return b.Deserialize(stream);
}

Library Documentation

Included with the library is an HTML help file describing all of the objects defined. The following graphic provides a broad overview of the classes.

Image 1

The Scheduler class represents the machine specific instance of the system task scheduler. It has very little use other than providing access to the list of tasks through its Tasks property. The Tasks property exposes a TaskList instance that provides an indexer which allows access to individual tasks by name. The TaskList class also has methods that allow for the creation and deletion of tasks by name. Since TaskList implements the IEnumerable interface, you can also enumerate all tasks using the foreach construct.

A task is represented by a Task instance. The Task class exposes all of the properties of a task which allow you to define what will run when the task is triggered. The only property that must be set for proper execution is ApplicationName, which specifies the full path of the executable which will be run when triggered. The Task class also provides some properties that give information about the execution of the task and some methods that allow for the running and termination of a task.

Each task has a list of triggers that determine when the task will be run. These are accessed through the Triggers property which exposes a TriggerList instance. TriggerList provides an indexer which allows access to individual triggers by their position in the list. The TriggerList class also has methods that allow for the addition and removal of triggers. TriggerList implements the IList interface so you can also enumerate all tasks using the foreach construct.

The Trigger class is an abstract class that forms the foundation of the different types of triggers that can be specified for a task. There are eight different specializations that provide different ways to specify the time a task will run. See the help file for details about each of the trigger classes.

Example Code

C#
// Write out information on all tasks and triggers

// Can optionally specify computer (e.g. @"\\hostname")

Scheduler sched = new Scheduler();   

foreach (Task t in sched.Tasks) 
{
   Console.WriteLine(t.ToString());
   foreach (Trigger tr in t.Triggers)
      Console.WriteLine(tr.ToString());
}

// Set only trigger on an existing task to be an idle trigger

Task t1 = sched.Tasks["Disk Cleanup"];
if (t1 != null)
{
   t1.Triggers.Clear();
   t1.Triggers.Add(new OnIdleTrigger());
   t1.Save();
}

// Create a new task with one of each kind of trigger

Task t2;
try 
{
   t2 = sched.Tasks.NewTask("Testing");
   t2.ApplicationName = "notepad.exe";
   t2.Comment = "Testing Notepad";
   t2.Creator = "Author";
   t2.Flags = TaskFlags.Interactive;
   t2.Hidden = true;
   t2.IdleWaitDeadlineMinutes = 20;
   t2.IdleWaitMinutes = 10;
   t2.MaxRunTime = new TimeSpan(1, 0, 0);
   t2.Parameters = @"c:\test.log";
   t2.Priority = System.Diagnostics.ProcessPriorityClass.High;
   t2.WorkingDirectory = @"c:\";
   t2.Triggers.Add(new RunOnceTrigger(DateTime.Now + TimeSpan.FromMinutes(1.0)));
   t2.Triggers.Add(new DailyTrigger(8, 30, 1));
   t2.Triggers.Add(new WeeklyTrigger(6, 0, DaysOfTheWeek.Sunday));
   t2.Triggers.Add(new MonthlyDOWTrigger(8, 0, DaysOfTheWeek.Monday | 
                                               DaysOfTheWeek.Thursday, 
                                               WhichWeek.FirstWeek | 
                                               WhichWeek.ThirdWeek));
   int[] days = {1,8,15,22,29};
   t2.Triggers.Add(new MonthlyTrigger(9, 0, days, MonthsOfTheYear.July));
   t2.Triggers.Add(new OnIdleTrigger());
   t2.Triggers.Add(new OnLogonTrigger());
   t2.Triggers.Add(new OnSystemStartTrigger());
   t2.SetAccountInformation("DOMAIN\\username", "mypassword");
   t2.Save();
}
catch {}

// Remove the idle trigger from the task

Trigger trigger = new OnIdleTrigger();
int idx = t2.Triggers.IndexOf(trigger);
if (idx != -1)
   t2.Triggers.RemoveAt(idx);

// Delete a task

sched.Tasks.Delete("Testing");

History

22 January 2002

   

Original posting.

1 April 2002

 

Converted all uint variables to int on public classes and enums to make it work better in VB.

2 April 2002

 

Added exception handling to some task properties that should not generate errors.

14 May 2002

 

Incorporated fixes to the Trigger.Equals method (it works now) and the MonthlyTrigger class to properly set days of month. Enhanced the Scheduler.TargetComputer property and the corresponding constructor to allow for computer names without preceding "\\". Thanks to Bob Grimshaw for the fixes.

25 March 2008

 

Moved project to CodePlex and updated to include Task Scheduler 2.0 functionality and support.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
United States United States
I have been a Windows software developer since 1991. Most of what I create fills the need for some aspect of bigger projects that I consult on.

Comments and Discussions

 
QuestionI'm getting "(40,4):Task:" error when I Execute RegisterTaskDefinition method from WCF Services Deployed on IIS, same code works fine in Console Application. Pin
NavEed ZaiDi20-Jul-17 20:14
NavEed ZaiDi20-Jul-17 20:14 
QuestionI am unable to use the function scheduled tasks even though i added the namespace task scheduler. Please help. Pin
Member 1314451021-Apr-17 21:24
Member 1314451021-Apr-17 21:24 
QuestionThe command "mt.exe -manifest "..\TSNewLib_src\TaskScheduler Project\TestApp\app.manifest" -outputresource:"..\TSNewLib_src\TaskScheduler Project\TestApp\bin\Debug\TestApp.exe;#1" " exited with code 9009. TestApp Pin
shravaniraj24-Jun-13 11:25
shravaniraj24-Jun-13 11:25 
AnswerRe: The command "mt.exe -manifest "..\TSNewLib_src\TaskScheduler Project\TestApp\app.manifest" -outputresource:"..\TSNewLib_src\TaskScheduler Project\TestApp\bin\Debug\TestApp.exe;#1" " exited with code 9009. TestApp Pin
David Hall12-May-14 8:21
professionalDavid Hall12-May-14 8:21 
Questioni want to read the previous one month history of job Pin
Member 427891821-Dec-12 1:39
Member 427891821-Dec-12 1:39 
QuestionPulling Tasks from remote Server 2008R2 machine Pin
Member 799030619-Oct-12 13:55
Member 799030619-Oct-12 13:55 
AnswerRe: Pulling Tasks from remote Server 2008R2 machine Pin
David Hall20-Oct-12 11:04
professionalDavid Hall20-Oct-12 11:04 
GeneralRe: Pulling Tasks from remote Server 2008R2 machine Pin
Member 799030623-Oct-12 14:54
Member 799030623-Oct-12 14:54 
GeneralMy vote of 5 Pin
Abinash Bishoyi15-Nov-11 6:05
Abinash Bishoyi15-Nov-11 6:05 
GeneralMy Vote of 5 Pin
Abinash Bishoyi15-Nov-11 6:04
Abinash Bishoyi15-Nov-11 6:04 
General"Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
tinybear7614-Apr-11 8:57
tinybear7614-Apr-11 8:57 
GeneralRe: "Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
David Hall14-Apr-11 9:19
professionalDavid Hall14-Apr-11 9:19 
GeneralRe: "Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
tinybear7614-Apr-11 9:28
tinybear7614-Apr-11 9:28 
GeneralRe: "Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
David Hall14-Apr-11 9:30
professionalDavid Hall14-Apr-11 9:30 
GeneralRe: "Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
tinybear7614-Apr-11 9:36
tinybear7614-Apr-11 9:36 
GeneralRe: "Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
David Hall14-Apr-11 9:46
professionalDavid Hall14-Apr-11 9:46 
GeneralRe: "Object reference not set to an instance of an object. “ error message appears if new job schedule name contains <4 symbols after "." symbol Pin
tinybear7614-Apr-11 10:52
tinybear7614-Apr-11 10:52 
QuestionTask Scheduler library and local system account? Pin
Lothar Behrens7-Feb-11 4:28
professionalLothar Behrens7-Feb-11 4:28 
AnswerRe: Task Scheduler library and local system account? Pin
David Hall7-Feb-11 5:23
professionalDavid Hall7-Feb-11 5:23 
GeneralIs this TaskScheduler available in c++ language. Pin
mewadale29-Jun-10 0:23
mewadale29-Jun-10 0:23 
GeneralRe: Is this TaskScheduler available in c++ language. Pin
David Hall29-Jun-10 4:26
professionalDavid Hall29-Jun-10 4:26 
QuestionCan I use TaskScheduler on a free open source codeplex project? Pin
juliotrujilloleon18-Feb-10 7:06
juliotrujilloleon18-Feb-10 7:06 
AnswerRe: Can I use TaskScheduler on a free open source codeplex project? Pin
David Hall18-Feb-10 7:58
professionalDavid Hall18-Feb-10 7:58 
Questioncombining enum MonthsOfTheYear dynamically at runtime Pin
mbluger4-Jan-10 7:29
mbluger4-Jan-10 7:29 
AnswerRe: combining enum MonthsOfTheYear dynamically at runtime Pin
David Hall18-Feb-10 8:01
professionalDavid Hall18-Feb-10 8:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.