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

Harnessing the task scheduler

0.00/5 (No votes)
28 Aug 2013 3  
Using the Task Scheduler interface in applications can be tricky, as it requires a detailed knowledge of the COM technology. This article presents a practical solution to this problem, based on simplifying communications with the interface.

This article is written by Rafal Piotrowski and was originally published in the March 2005 issue of the Software 2.0 magazine. You can find more articles at the SDJ website.

Introduction

The family of Windows operating systems gives us an useful mechanism known as the Task Scheduler. Using it, we are able to instruct the system to perform tasks multiple times, with no need of running them manually and no need of remembering about them. A textbook example of a situation to which a task scheduler is applied is periodically scanning a system for viruses or trojans.

We can use the Task Scheduler in two ways, one is by running the Task Scheduler in the Control Panel and, using the dialog windows, setting the required options. The more interesting approach from our point of view will be to take advantage of the Task Scheduler API, a number of objects and COM interfaces which allow scheduling tasks from within our program. However, before we begin to use this mechanism, we have to make sure to check whether the Task Scheduler is running every time we want to schedule a task. Under Windows 95 and NT5, it is not installed by default, so you'll likely have to turn it on. Under Win98 and newer, the scheduler is installed and active by default, then again there are programs which can deactivate it.

Task Scheduler API

Using the Task Scheduler API, we can:

  • create a new task,
  • set time and frequency of execution of a task,
  • adjust time and frequency of execution of a task,
  • define how a task should be executed (parameters),
  • stop a running task.

We add tasks by creating appropriate objects and COM interfaces, and executing their appropriate methods.

Creating a task object

For creating a new task, we use the interfaces ITaskScheduler, ITask, and IPersistFile. In order for the creation to be successful, one should provide a unique task name to give to the ITaskScheduler::NewWorkItem() method. If such a name is already in use, the method returns ERROR_FILE_EXISTS.

Task creation implies a number of actions. To obtain a TaskScheduler class object, one has to call the functions CoInitialize() (initialisation of the COM library) and CoCreateInstance(). Then it is time to call ITaskScheduler::NewWorkItem() to create a new task; this method returns a pointer to an ITask interface. We write the newly-created task to disk by calling the method IPersistFile::Save()IPersistFile is a standard COM interface supported by the ITask interface). Finally, by calling the method ITask::Release(), we release the obtained resources (Release() is a method of the IUnknown interface, which ITask inherits from).

We have now got a task, but the system doesn't know when it should be run. For that to happen, we need to create a trigger – an object bound with a certain task, informing when and how often it should be executed.

Creating a trigger

While creating a trigger, we take advantage of three interfaces: IScheduledWorkItem, ITaskTrigger, and IPersistFile. We can distinguish between triggers executing a task for a given time period and triggers executing it after a chosen period of user inactivity. Since it is possible to assign multiple triggers to a single task object, we can, for instance, have our task run once a day and after 5 minutes of user inactivity.

In order to create a trigger, one first has to call CoInitialize() or CoCreateInstance(); if we're setting a trigger right after creating a task, this step is not required. Next, we call the ITaskScheduler::Activate method to obtain an ITask interface and IScheduledWorkItem::CreateTrigger() to create a trigger. We define a TASK_TRIGGER structure (the variables in wBeginDay, wBeginMonth, and wBeginYear must have acceptable values) and execute ITaskTrigger::SetTrigger to set the trigger's parameters. Finally, we store the newly-created trigger to disk with IPersistFile::Save() and release the resources with ITask::Release().

An example of adding a task to the Task Scheduler is shown as Listing 1.

Listing 1. Adding a task to the scheduler

HRESULT hr = ERROR_SUCCESS;
ITaskScheduler *pITS;

hr = CoInitialize(NULL);
if (SUCCEEDED(hr)){
    hr = CoCreateInstance(CLSID_CTaskScheduler,
    NULL,CLSCTX_INPROC_SERVER,
    IID_ITaskScheduler,(void **) &pITS);
}
else {
    return 1;
}

// Calling ITaskScheduler::NewWorkItem()
// to create a new task
LPCWSTR pwszTaskName;
ITask *pITask;
IPersistFile *pIPersistFile;
pwszTaskName = L"Test Task1";
hr = pITS->NewWorkItem(pwszTaskName,// task name
     CLSID_CTask, // class identifier
     IID_ITask, // interface identifier
     (IUnknown**)&pITask); // address of a pointer
                          // to the ITask interface
// Call IUnknown::QueryInterface
// to obtain a pointer to
// IPersistFile and IPersistFile::Save,
// to write the task to disk
hr = pITask->QueryInterface(IID_IPersistFile,
     (void **)&pIPersistFile);
hr = pIPersistFile->Save(NULL,TRUE);

// creating a trigger
ITaskTrigger* pITaskTrig = NULL;
IPersistFile* pIFile = NULL;
TASK_TRIGGER rTrigger;
WORD wTrigNumber = 0;
hr = pITask->CreateTrigger ( &wTrigNumber, &pITaskTrig );

//filling the TASK_TRIGGER structure
ZeroMemory ( &rTrigger, sizeof (TASK_TRIGGER) );
rTrigger.cbTriggerSize = sizeof (TASK_TRIGGER);
rTrigger.wBeginYear = 2004;
rTrigger.wBeginMonth = 4;
rTrigger.wBeginDay = 10;
rTrigger.wStartHour = 10;
rTrigger.wStartMinute = 0;

// associate the trigger with the task
rTrigger.TriggerType = TASK_TIME_TRIGGER_ONCE;
hr = pITaskTrig->SetTrigger ( &rTrigger );
hr = pITask->QueryInterface ( IID_IPersistFile, 
                              (void **) &pIFile );
hr = pIFile->Save ( NULL, FALSE );
printf("Succesfully created task and trigger.\n");

The CTask class

Since writing COM technology-based programs is not the easiest of tasks in C++, I have created a class called CTask wrapping the COM calls. Using the class, it is possible to add tasks to the Task Scheduler quickly and efficiently.

In order to create a new task using the CTask class, we have to specify:

  • the program's name and full path (required),
  • command-line parameters we wish to pass to the program (optional),
  • the program's starting directory (optional),
  • account name and password (required for NT),
  • start date and time (optional, not available for tasks executed once),
  • end date (see above, optional),
  • frequency of execution (once, every day, every week, every month, required),
  • comment (text to be displayed in the Task Scheduler applet's dialog – optional).

Each of the above task parameters is associated with an appropriately-named class method, setting the right values:

  • void SetProgram ( LPCTSTR szProgram )
  • void SetParameters ( LPCTSTR szParams )
  • void SetStartingDir ( LPCTSTR szDir )
  • void SetAccountName ( LPCTSTR szAccount )
  • void SetPassword ( LPCTSTR szPassword )
  • void SetStartDateTime ( const CTime& timeStart )
  • void SetStartDateTime ( const SYSTEMTIME& timeStart )
  • void SetEndDate ( const CTime& timeEnd )
  • void SetEndDate ( const SYSTEMTIME& timeEnd )
  • void SetFrequency ( CScheduledTask::ETaskFrequency freq )
  • void SetComment ( LPCTSTR szComment )

Apart from this, it is possible to read each member value by using methods prefixed with Get_, so the name of the program can be obtained with GetProgram(), and GetParameters() checks parameters etc.

In Listing 2., one can see the source code of the simplest console application using the CTask class to add a task to the Task Scheduler. Warning: before linking, you have to change the run-time library to a multithreaded one by selecting: Project-> Settings, tab C++, category Code Generation, then choosing Debug Multithreaded from the Use run-time library drop-down list.

Listing 2. A simple application of the CTask class

#include "CTask.h"
int main(int /*argc*/, char* /*argv[]*/) {
    CTask task; //constructing a CTask class object
    CTime time(2004, 04, 01, 10, 10, 0); //date of creation
    LPCTSTR sTaskName("TaskName"); //name
    //replace if such a task already exists
    BOOL bReplace = TRUE;
    //full path to the programme
    task.SetProgram ( "" );
    //execution parameters
    task.SetParameters ( "" );
    //starting directory
    task.SetStartingDir ( "" );
    //account name
    task.SetAccountName ( "" );
    //account password
    task.SetPassword ( "" );
    //comment
    task.SetComment ( "" );
    task.SetStartDateTime ( time ); //start date
    //frequency adding the task to the scheduler
    task.SetFrequency ( CTask::freqOnce );
    if ( S_OK == task.SaveTask ( sTaskName, bReplace )){
        MessageBox(GetActiveWindow(), 
                   "The task has been added!", "", MB_OK);
        return 0;
    }
    else {
        MessageBox(GetActiveWindow(), 
                   "Failed to create a task!", "", MB_OK);
        return 1;
    }
}

Adding a GUI

It is now time to build a more advanced application to manage the Task Scheduler, equipped with a graphical user interface. In order to achieve this, we shall use the MS Visual C++ IDE.

Composing the user interface

Let us create a new project, giving it a name of our choice, e.g. MyTaskDemo. Select MFC Application in the wizard as application type. Choose the Dialog – Based option, leave the others at their defaults by selecting Next and then Finish. From the Resources Editor, select the dialog window created for us by the wizard. We shall now build a user interface for our application, with its target look as shown in Figure 1.

Image 1

Figure 1. The user interface of the MyTaskDemo application

The Options group doesn't require much discussion. It consists of four Edit fields, four Static-type controls, and two buttons. In the Frequency group, let us not forget to select Group in the Radio button's options. Start and end dates are DateTimePicker controls. The start date and the end date have the Short Date format, the start time – Time format. Select Show Null for the end date to allow running the task infinitely. In the properties of the Password field (an Edit control) one should select the Password field.

Preparing the application for the CTask class

Add the files CTask.cpp and CTask.h to the project (Project->Add To Project->Files...). You should see the CTask class appear in the class view window. For now, we shall leave it alone, switch to the resources editor, and select the dialog in which we composed our GUI. Press [Ctrl+W] on the keyboard, or select Class Wizard. We will be asked whether we want to create a new class for our dialog or use an existing one; we select an existing class CMyTaskDemoDlg. The next task is adding Event Handlers – functions handling events, e.g. mouse clicks for a certain control – to the buttons. The easiest way of doing this is to double-click a button in the Resources Editor, then add member variables of the CMyTaskDemoDlg class by binding them with the appropriate controls; we can do it by choosing the control from the list and clicking the Add Variable button.

Adding the actual program code

Now it is time do add the source code to perform the necessary initialisations and use the CTask class. Comparing the two source projects from the download, Step 2 and Step 3, can prove very educational in this respect.

In MyTaskDemo.cpp, initialise OLE with the AfxOleInit() function in the InitInstance() method.

The largest chunk of code is to be put into MyTaskDemoDlg.cpp. I will only briefly discuss what has to be added, more details can be found in the source code of the Step 3 project available for download.

To begin with, provide the following directives:

  • #include CTask.h, //declaration of the CTask class
  • #include <shlobj.h> //for SHBrowseForFolder()
  • in the OnInitDialog() method, provide the necessary initialisations (see the source code),
  • in the OnBrowseProgram() method, create a CFileDialog class object, which shall let us choose our program of choice,
  • in the OnBrowsePath() method we shall use the Shell API function SHBrowseForFolder(), which allows choosing the program's execution directory easily,
  • in the OnDeleteTask() method, we finally put our class to work and use its method Ctask::DeleteTask(),
  • in the OnAddTask() method, the major part of work will be done: this method reads values of the controls, checks their validity and ─ if the data is correct – sets the appropriate member variables of CTask and calls Ctask::SaveTask().

Our application can now add tasks to the Task Scheduler. The above example could be successfully used in writing antivirus software, a personal calendar, and many others. Please note that if we provide a name to the task, but no account name or password, the task will be added but will never be executed. Improving the application in this respect, I leave as an exercise for the reader.

Possible improvements

Another possible improvement would be to create function pairs handling ANSI and UNICODE. XXX is not an optimal solution because Windows, starting from version 2000, uses UNICODE exclusively, so all ANSI-handling functions are converted to it. The Task Scheduler API also allows listing all the tasks set in the scheduler, browsing them, or editing their contents; this article is therefore by no means exhaustive.

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