Contents Index Topic Contents | ||
Previous Topic: About the Task Scheduler Next Topic: Task Scheduler Reference |
Programming the Task Scheduler
The collection of interfaces and methods available to the developer from the Task Scheduler represents the programmatic techniques necessary to implement the functionality of task management.
Manipulating Scheduled Work Items
Starting the Task Scheduler Service
The Starting Point: ITaskScheduler
Manipulating Scheduled Work Items
The IScheduledWorkItem interface allows you to modify an existing work item's behaviors. When you have the interface pointer to IScheduledWorkItem, you have the ability to run, terminate, or modify the characteristics for a given task. The behavior and properties of each scheduled task are accessible through the IScheduledWorkItem interface.
Starting the Task Scheduler Service
The Task Scheduler service is not started by default; the developer is responsible for determining the current running state of the Task Scheduler. The following code is an example of how to start the Task Scheduler service.
//+-------------------------------------------------------------------------- // // Function: StartScheduler // // Synopsis: Start the task scheduler service if it isn't already running. // // Returns: ERROR_SUCCESS or a Win32 error code. // // Notes: This function works for either Win9x or Windows NT. // If the service is running but paused, does nothing. // //--------------------------------------------------------------------------- #define SCHED_CLASS TEXT("SAGEWINDOWCLASS") #define SCHED_TITLE TEXT("SYSTEM AGENT COM WINDOW") #define SCHED_SERVICE_APP_NAME TEXT("mstask.exe") #define SCHED_SERVICE_NAME TEXT("Schedule") DWORD StartScheduler() { OSVERSIONINFO osver; osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); // Determine what version of OS we are running on. GetVersionEx(&osver); if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { // Start the Win95 version of TaskScheduler. HWND hwnd = FindWindow(SCHED_CLASS, SCHED_TITLE); if (hwnd != NULL) { // It is already running. return ERROR_SUCCESS; } // // Execute the task scheduler process. // STARTUPINFO sui; PROCESS_INFORMATION pi; ZeroMemory(&sui, sizeof(sui)); sui.cb = sizeof (STARTUPINFO); TCHAR szApp[MAX_PATH]; LPTSTR pszPath; DWORD dwRet = SearchPath(NULL, SCHED_SERVICE_APP_NAME, NULL, MAX_PATH, szApp, &pszPath); if (dwRet == 0) { return GetLastError(); } BOOL fRet = CreateProcess(szApp, NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, NULL, NULL, &sui, &pi); if (fRet == 0) { return GetLastError(); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return ERROR_SUCCESS; } else { // If not Win95 then start the NT version as a TaskScheduler service. SC_HANDLE hSC = NULL; SC_HANDLE hSchSvc = NULL; hSC = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (hSC == NULL) { return GetLastError(); } hSchSvc = OpenService(hSC, SCHED_SERVICE_NAME, SERVICE_START | SERVICE_QUERY_STATUS); CloseServiceHandle(hSC); if (hSchSvc == NULL) { return GetLastError(); } SERVICE_STATUS SvcStatus; if (QueryServiceStatus(hSchSvc, &SvcStatus) == FALSE) { CloseServiceHandle(hSchSvc); return GetLastError(); } if (SvcStatus.dwCurrentState == SERVICE_RUNNING) { // The service is already running. CloseServiceHandle(hSchSvc); return ERROR_SUCCESS; } if (StartService(hSchSvc, 0, NULL) == FALSE) { CloseServiceHandle(hSchSvc); return GetLastError(); } CloseServiceHandle(hSchSvc); return ERROR_SUCCESS; } }Task Names
A task name is the name of the file that stores the task information. These files all have the .job extension and reside in the Tasks folder. You can retrieve the location of the Tasks folder from the registry by getting the data for the following value:
HKEY_LOCAL_MACHINE SOFTWARE Microsoft SchedulingAgent TasksFolderThe task names are always given in wide characters, so you need to convert the string to ANSI characters if your application is not built for Unicode. The ConvertString function handles this for you.
BOOL ConvertString(LPTSTR pszOut, LPWSTR pwszIn, DWORD dwSize) { #ifdef UNICODE if(lstrcpyn(pszOut, pwszIn, dwSize)) { return TRUE; } #else if(WideCharToMultiByte( CP_ACP, 0, pwszIn, -1, pszOut, dwSize, NULL, NULL)) { return TRUE; } #endif // UNICODE return FALSE; }All the Task Scheduler methods that take a task name examine the name and add the .job extension if it is not already present.
Creating Tasks
Listing the current tasks is all well and good, but how do you create your own task? There are two ways to do this. The first is to use ITaskScheduler::NewWorkItem. This method will instantiate an ITask interface and assign it the name you provide. The second is to call ITaskScheduler::AddWorkItem. When you take this route, it is your responsibility to instantiate the ITask interface and then add the task with the name you supply.
The name you supply for the task must be unique within the Tasks folder. If a task with that name already exists, ITaskScheduler::NewWorkItem and ITaskScheduler::AddWorkItem return ERROR_FILE_EXISTS. If you get this return value, you should specify a different name and attempt to add the task again.
Deleting Tasks
You use the ITaskScheduler::Delete method to delete a task. You simply supply the name of the task and, if the task is present, it will be deleted.
Editing a Task
When you are viewing the Tasks folder in Windows® Explorer, you can edit the task by double-clicking it. When you do this, the scheduling agent actually calls the tasks object's IScheduledWorkItem::EditWorkItem method. Fortunately, this method has been exposed, so you can do the same thing programmatically without having to implement all the user interface code and resources. The only thing you have to provide is a parent window handle.
For more information on editing tasks, see Task Property Sheet.
Enumerating Tasks
To enumerate tasks, call ITaskScheduler::Enum. This gives you an IEnumWorkItems interface pointer that you can use to retrieve the names of the tasks. When you have the name of a task, you can call ITaskScheduler::Activate to get the ITask interface for the task. When you have the ITask interface pointer, you can get or set any of the task information that you want. The following example enumerates all the tasks and adds them to a list view control to display them. For additional information on list views, see List View Controls.
Note Before the list view is destroyed, the release method for ITask is called for each item in the list view. The caller of ITaskScheduler::Activate is responsible for releasing the returned pointer.
BOOL LoadTaskList(HWND hwndListView) { HRESULT hr; IEnumWorkItems *pEnum; hr = g_pSchedulingAgent->Enum(&pEnum); if(SUCCEEDED(hr)) { LPWSTR *ppNames; DWORD dwFetched; while(SUCCEEDED(pEnum->Next(5, &ppNames, &dwFetched)) && dwFetched) { DWORD i; TCHAR szString[MAX_PATH]; for(i = 0; i < dwFetched; i++) { if(ConvertString(szString, *(ppNames + i), sizeof(szString))) { LV_ITEM lvItem; ITask *pTask; ZeroMemory(&lvItem, sizeof(lvItem)); lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; lvItem.pszText = szString; lvItem.iImage = I_IMAGECALLBACK; lvItem.iItem = ListView_GetItemCount(hwndListView); // Get the ITask for this task. hr = g_pSchedulingAgent->Activate( *(ppNames + i), IID_ITask, (LPUNKNOWN*)&pTask); if(SUCCEEDED(hr)) { lvItem.lParam = (LPARAM)pTask; // Add the item to the list. ListView_InsertItem(hwndListView, &lvItem); } } // Free the string. CoTaskMemFree(*(ppNames + i)); } // Free the array. CoTaskMemFree(ppNames); } pEnum->Release(); return TRUE; } return FALSE; }Executing and Terminating a Task
If you want to programmatically cause a task to run, you simply call the task object's IScheduledWorkItem::Run method. This method attempts to execute the task and returns as soon as the task has started. The Task Scheduler service must be running for this method to succeed.
If it becomes necessary for you to terminate a running task, you can use IScheduledWorkItem::Terminate. This method attempts to terminate the task and returns a success/failure code, informing your application if it was successful or not. IScheduledWorkItem::Terminate attempts to gracefully shut down the application by sending it a WM_CLOSE message. It then waits approximately three minutes for the process to terminate. If the process has not terminated within that three minutes, IScheduledWorkItem::Terminate uses TerminateProcess to terminate the task. As you can see, there is potential here for data loss if the application must be terminated the hard way. Therefore, you should avoid terminating tasks if at all possible.
Manipulating Tasks: ITask
ITask is the interface you use to control a task object. You can set and retrieve all of the task's information, execute and terminate the task, add and delete triggers, and even allow the user to modify the task with a standard user interface.
ITask implements all the methods necessary to set and retrieve all of the task's information. For a complete list of the methods for affecting the task, see the ITask references. The method names are rather self-explanatory.
Task Property Sheet
The IProvideTaskPage::GetPage method provides the ability to retrieve the property sheet page associated with a task object. The information for the task object's general page, schedule, and settings is available from the GetPage method.
VOID EditTaskSettings(ITask *pTask) { IProvideTaskPage *ptp; PROPSHEETHEADER psh; HPROPSHEETPAGE hpage; // Display the "Settings" property page. pTask->QueryInterface(IID_IProvideTaskPage, (VOID**)(IProvideTaskPage**)&ptp); // The settings will be saved to the task object on release. ptp->GetPage(TASKPAGE_SETTINGS, TRUE, &hpage); // Display the page in its own property sheet. psh.dwSize=sizeof(PROPSHEETHEADER); psh.dwFlags=PSH_DEFAULT; psh.hwndParent=NULL; psh.hInstance=NULL; psh.pszCaption=TEXT("Just the settings page on the task object"); psh.phpage=&hpage; psh.nPages=1; PropertySheet(&psh); }Task Triggers
Applications can use the methods available from the task scheduler interfaces to access, set, or modify the trigger for a task. Triggers specify task start times, repetition criteria, and other parameters.
Creating and Deleting Triggers
When you use IScheduledWorkItem::CreateTrigger, you are creating a brand new ITaskTrigger object. IScheduledWorkItem::CreateTrigger gives you both the new trigger's numeric index and the ITaskTrigger interface pointer. You can then use this interface to modify the trigger parameters. By the same token, IScheduledWorkItem::DeleteTrigger deletes the trigger that has the numeric index specified.
Manipulating Triggers: ITaskTrigger
The ITaskTrigger interface allows you to modify an existing trigger's parameters, but how do you get the ITaskTrigger interface for a trigger object? There are two ways to do this:
- Create a new trigger using IScheduledWorkItem::CreateTrigger.
- Use IScheduledWorkItem::GetTrigger. This method takes the desired trigger's numerical index.
When you have the interface, you can use ITaskTrigger::SetTrigger to set the trigger information and ITaskTrigger::GetTrigger to get the trigger object's information. ITaskTrigger::GetTriggerString gives you a formatted string describing the trigger. You can use this string to display information about the trigger.
The Starting Point: ITaskScheduler
The ITaskScheduler interface is the starting point for programming the Task Scheduler. ITaskScheduler allows you to enumerate, add, modify, and delete tasks, and administer tasks on another computer. All you have to do to obtain the ITaskScheduler interface is to call CoCreateInstance with the scheduling agent class identifier and interface identifier. The following example shows how this is done.
#include <mstask.h> ITaskScheduler *g_pSchedulingAgent; BOOL Initialize(void) { HRESULT hr; CoInitialize(NULL); hr = CoCreateInstance( CLSID_CSchedulingAgent, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (LPVOID*)&g_pSchedulingAgent); return SUCCEEDED(hr); }When you no longer need the ITaskScheduler interface, simply call its Release method. All of the methods contained within the IUnknown interface are inherited in ITaskScheduler; these include QueryInterface, AddRef, and Release. Don't forget that you need to call CoInitialize or OleInitialize before calling CoCreateInstance. Also, be sure to call CoUninitialize or OleUninitialize when you are done using OLE.
The Target Computer
In Task Scheduler terms, the target computer is the computer to which any instance of the ITaskScheduler interface is associated. All of the tasks that each instance of the ITaskScheduler interface controls are stored on the target computer and will run on the target computer. When you create a new instance of ITaskScheduler, the local computer is the target computer by default.
You can use ITaskScheduler to administer tasks on a remote computer. There are constraints for remote task administration:
- You can view tasks on a remote computer running Windows NT® only if you are an administrator on that remote computer.
- You can edit tasks on a remote Windows® 95 computer only if that remote computer has remote registry software installed and shares the x$ share, where x is the hard drive on which the Tasks folder resides.
- A computer running Windows 95 must have the remote registry service installed before it can set the target computer to a remote computer, or be specified as a remote target computer.
For more information on installing the remote registry service, see article Q141460 in the Microsoft Knowledge Base.
You use ITaskScheduler::SetTargetComputer to change the target computer and ITaskScheduler::GetTargetComputer to get the name of the target computer. Computer names are always in Universal Naming Convention (UNC) format.
Top of Page
© 1997 Microsoft Corporation. All rights reserved. Terms of Use.