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.
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.
Using the Task Scheduler API, we can:
We add tasks by creating appropriate objects and COM interfaces, and executing their appropriate methods.
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.
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");
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:
Each of the above task parameters is associated with an appropriately-named class method, setting the right values: )
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; } }
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.
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.
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.
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.
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()
OnInitDialog()method, provide the necessary initialisations (see the source code),
OnBrowseProgram()method, create a
CFileDialogclass object, which shall let us choose our program of choice,
OnBrowsePath()method we shall use the Shell API function
SHBrowseForFolder(), which allows choosing the program's execution directory easily,
OnDeleteTask()method, we finally put our class to work and use its method
Ctask::DeleteTask(),
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
CTaskand 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.
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.
General
News
Question
Answer
Joke
Rant
Admin