Developing for Task Scheduler 2.0

Developing for Task Scheduler 2.0

Introduction

Task Scheduler in Windows Vista® enables you to schedule programs to run at a time you specify or when a specific event occurs, and can automatically perform routine tasks on a computer of your choice. Task Scheduler also monitors criteria you specify to initiate tasks and execute them when those criteria are met.

Task Scheduler 2.0, installed with Windows Vista and  provides many new features and functionalities beyond the earlier version. For instance, for the IT professionals who prefer to use command-line tools instead of GUI, the Schtasks.exe command-line utility (found in the %SYSTEMROOT%\System32 folder) has been extended to support most of the new functionality. For software developers, Task Scheduler provides new APIs and scripting capabilities that give developers greater flexibility to develop for Task Scheduler.

This article highlights the new APIs, the new scripting capability, and the new XML Schema support for Task Scheduler and also provides sample code developers may use. For more information about other new features of Task Scheduler version 2.0, see Task Scheduler 2.0.

What Is New in Task Scheduler 2.0 for Developers?

Windows Vista provides new features and tools that make the work of software developers easier. The redesigned Task Scheduler provides new triggers and actions, new Credentials Management using new security services, new MMC-compatibility and Task Settings, and an unlimited number of registered tasks. Task Scheduler also provides several API changes that give software developers flexibility when developing for Task Scheduler. For more information about the new features, see Task Scheduler 2.0.

The API changes in Task Scheduler include:

  • New interfaces for C++ developers

  • New scripting objects for VBScript developers

  • New Task Scheduler schema for defining tasks in XML

Applications developed in Task Scheduler 2.0 can use objects and interfaces to create, retrieve, modify, and delete the triggers and actions for a task. Two new, major capabilities of Task Scheduler 2.0 are the ability to perform these actions:

  • Send an e-mail when a task is triggered

  • Define a task that shows a message box

The following sections describe how to use these new capabilities. This section includes code examples and provides links to the MSDN Library where you can find additional information, code examples for the new APIs, new scripting capabilities, and new XML schema support.

New Interfaces for C++ Developers

Windows Vista offers several new interfaces that provide programmatic access to the functionality available within Task Scheduler 2.0. These interfaces provide an easy way to define a task that performs an action when an event occurs. Event Triggers enable you to specify an event query that is used to subscribe to events logged in an event log. When an event occurs, the action of the specified task executes.

Task Scheduler 2.0 provides two new actions developers may find very useful: sending an e-mail to a particular person and displaying a message box. To see the interface that enable actions to send an e-mail, see IEmailAction. To create and schedule a task to send an e-mail when an event occurs, see IEmailAction.

A defined task must contain an event trigger. When the event occurs, the task is triggered. If you want the task to send an email, the task must also contain an action that specifies that an e-mail should be sent. As part of the redesigned Task Scheduler, to run a task you can register that task using a password and user name that you have specified in the Security Context property page.

The following procedure describes how to use the new interfaces to schedule a task to send an e-mail when an event occurs.

To schedule an e-mail to be sent when an event occurs

  1. Initialize COM and set general COM security.

  2. Create the ITaskService object. This object allows you to create tasks in a specified folder.

  3. Get a task folder to create a task in. Use the ITaskService::GetFolder method to get the folder, and the ITaskService::NewTask method to create the ITaskDefinition object.

  4. Define information about the task using the ITaskDefinition object, such as the registration information for the task. Use the RegistrationInfo property of ITaskDefinition and other properties of the ITaskDefinition interface to define the task information.

  5. Create an event trigger using the Triggers property of ITaskDefinition to access the ITriggerCollection for the task. Use the ITriggerCollection::Create method (specifying the type of trigger you want to create) to create an event trigger. This allows you to set the event query to subscribe to events. For information about how to create an event query, see Event Selection.

  6. Create an action for the task to execute by using the Actions property of ITaskDefinition to access the IActionCollection interface for the task. Use the IActionCollection::Create method to specify the type of action that you want to create. This example uses an IEmailAction object, which represents an action that sends a specified e-mail message.

  7. Register the task using the ITaskFolder::RegisterTaskDefinition method.

The following C++ example schedules a task to send an e-mail when an event occurs.

/********************************************************************
 This sample schedules a task to send an e-mail when an event occurs. 
********************************************************************/
#define _WIN32_DCOM
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <comdef.h>
#include <wincred.h>
//  Include the task header file.
#include <taskschd.h>
# pragma comment(lib, "taskschd.lib")
# pragma comment(lib, "comsupp.lib")
# pragma comment(lib, "credui.lib")
using namespace std;
int __cdecl wmain()
{
    //  ------------------------------------------------------
    //  Initialize COM.
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if( FAILED(hr) )
    {
        printf("\nCoInitializeEx failed: %x", hr );
        return 1;
    }
    //  Set general COM security levels.
    hr = CoInitializeSecurity(
        NULL,
        -1,
        NULL,
        NULL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        0,
        NULL);
    if( FAILED(hr) )
    {
        printf("\nCoInitializeSecurity failed: %x", hr );
        CoUninitialize();
        return 1;
    }
    //  ------------------------------------------------------
    //  Create a name for the task.
    LPCWSTR wszTaskName = L"Event Trigger Test Task";
    //  ------------------------------------------------------
    //  Create an instance of the Task Service. 
    ITaskService *pService = NULL;
    hr = CoCreateInstance( CLSID_TaskScheduler,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_ITaskService,
                           (void**)&pService );  
    if (FAILED(hr))
    {
          printf("Failed to CoCreate an instance of the TaskService class: %x", hr);
          CoUninitialize();
          return 1;
    }
        
    //  Connect to the task service.
    hr = pService->Connect(_variant_t(), _variant_t(),
        _variant_t(), _variant_t());
    if( FAILED(hr) )
    {
        printf("ITaskService::Connect failed: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }
    //  ------------------------------------------------------
    //  Get the pointer to the root task folder.  This folder will hold the
    //  new task that is registered.
    ITaskFolder *pRootFolder = NULL;
    hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
    if( FAILED(hr) )
    {
        printf("Cannot get Root Folder pointer: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }
    
    // If the same task exists, remove it.
    pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0  );
    
    //  Create the task builder object to create the task.
    ITaskDefinition *pTask = NULL;
    hr = pService->NewTask( 0, &pTask );
    
    pService->Release();  // COM clean up.  Pointer is no longer used.
    if (FAILED(hr))
    {
        printf("Failed to create an instance of the task: %x", hr);
        pRootFolder->Release();
        CoUninitialize();
        return 1;
    }
        
    //  ------------------------------------------------------
    //  Get the registration info for setting the identification.
    IRegistrationInfo *pRegInfo= NULL;
    hr = pTask->get_RegistrationInfo( &pRegInfo );
    if( FAILED(hr) )
    {
        printf("\nCannot get identification pointer: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    hr = pRegInfo->put_Author( L"Author Name" );
    pRegInfo->Release();  // COM clean up.  Pointer is no longer used.
    if( FAILED(hr) )
    {
        printf("\nCannot put identification info: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    //  ------------------------------------------------------
    //  Create the settings for the task
    ITaskSettings *pSettings = NULL;
    hr = pTask->get_Settings( &pSettings );
    if( FAILED(hr) )
    {
        printf("\nCannot get settings pointer: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    //  Set setting values for the task.  
    hr = pSettings->put_StartWhenAvailable(VARIANT_BOOL(true));
    pSettings->Release();  // COM clean up.  Pointer is no longer used.
    if( FAILED(hr) )
    {
        printf("\nCannot put setting info: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    //  ------------------------------------------------------
    //  Get the trigger collection to insert the event trigger.
    ITriggerCollection *pTriggerCollection = NULL;
    hr = pTask->get_Triggers( &pTriggerCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get trigger collection: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    //  Create the event trigger for the task.
    ITrigger *pTrigger = NULL;
    
    hr = pTriggerCollection->Create( TASK_TRIGGER_EVENT, &pTrigger );
    pTriggerCollection->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot create the trigger: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }     
    
    IEventTrigger *pEventTrigger = NULL;
    hr = pTrigger->QueryInterface( 
        IID_IEventTrigger, (void**) &pEventTrigger );
    pTrigger->Release();
    if( FAILED(hr) )
    {
        printf("\nQueryInterface call on IEventTrigger failed: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    } 
    
    hr = pEventTrigger->put_Id( _bstr_t( L"Trigger1" ) );
    if( FAILED(hr) )
       printf("\nCannot put the trigger ID: %x", hr);
    //  Set the task to start at a certain time. The time 
    //  format should be YYYY-MM-DDTHH:MM:SS(+-)(timezone).
    //  For example, the start boundary below
    //  is January 1st 2005 at 12:05
    hr = pEventTrigger->put_StartBoundary( _bstr_t(L"2005-01-01T12:05:00") );
    if( FAILED(hr) )
        printf("\nCannot put the trigger start boundary: %x", hr);
    hr = pEventTrigger->put_EndBoundary( _bstr_t(L"2015-05-02T08:00:00") );
    if( FAILED(hr) )
        printf("\nCannot put the trigger end boundary: %x", hr);
    //  Define the delay for the event trigger (30 seconds).
    hr = pEventTrigger->put_Delay( L"PT30S" );
    if( FAILED(hr) )
        printf("\nCannot put the trigger delay: %x", hr);
    //  Define the event query for the event trigger.
    //  The following query string defines a subscription to all
    //  level 2 events in the System channel.
    hr = pEventTrigger->put_Subscription( 
        L"<QueryList> <Query Id='1'> <Select Path='System'>*[System/Level=2]</Select></Query></QueryList>" );
    pEventTrigger->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot put the event query: %x", hr);  
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }              
    //  ------------------------------------------------------
    //  Add an action to the task. This task will send an e-mail.     
    IActionCollection *pActionCollection = NULL;
    //  Get the task action collection pointer.
    hr = pTask->get_Actions( &pActionCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get action collection pointer: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
        
    //  Create the action, specifying that it will send an e-mail.
    IAction *pAction = NULL;
    hr = pActionCollection->Create( TASK_ACTION_SEND_EMAIL, &pAction );
    pActionCollection->Release();
    if( FAILED(hr) )
    {
        printf("\nCannot create an e-mail action: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    IEmailAction *pEmailAction = NULL;
    //  QI for the e-mail task pointer.
    hr = pAction->QueryInterface(IID_IEmailAction, (void**) &pEmailAction );
    pAction->Release();
    if( FAILED(hr) )
    {
        printf("\nQueryInterface call failed for IEmailAction: %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    //  Set the properties of the e-mail action.
    hr = pEmailAction->put_From(L"SendersEmailAddress@domain.com");
    if( FAILED(hr) )
    {
        printf("\nCannot put From information: %x", hr );
        pRootFolder->Release();
        pEmailAction->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    hr = pEmailAction->put_To(L"RecipientsEmailAddress@domain.com");
    if( FAILED(hr) )
    {
        printf("\nCannot put To information: %x", hr );
        pRootFolder->Release();
        pEmailAction->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    hr = pEmailAction->put_Server(L"MyEmailServerName.domain.com");
    if( FAILED(hr) )
    {
        printf("\nCannot put SMTP server information: %x", hr );
        pRootFolder->Release();
        pEmailAction->Release();
        pTask->Release();
        CoUninitialize();
        return 1;
    }
    
    hr = pEmailAction->put_Subject(L"An event has occurred");
    if( FAILED(hr) )
        printf("\nCannot put the subject information: %x", hr);
    
    hr = pEmailAction->put_Body(L"A level 2 event occurred in the system channel.");
    if( FAILED(hr) )
        printf("\nCannot put the e-mail body information: %x", hr);
    
    pEmailAction->Release();
    //  ------------------------------------------------------
    //  Securely get the user name and password. The task will
    //  be created to run with the credentials from the supplied 
    //  user name and password.
    CREDUI_INFO cui;
    TCHAR pszName[CREDUI_MAX_USERNAME_LENGTH] = "";
    TCHAR pszPwd[CREDUI_MAX_PASSWORD_LENGTH] = "";
    BOOL fSave;
    DWORD dwErr;
    cui.cbSize = sizeof(CREDUI_INFO);
    cui.hwndParent = NULL;
    //  Ensure that MessageText and CaptionText identify
    //  what credentials to use and which application requires them.
    cui.pszMessageText = TEXT("Account information for task registration:");
    cui.pszCaptionText = TEXT("Enter Account Information for Task Registration");
    cui.hbmBanner = NULL;
    fSave = FALSE;
    //  Create the UI asking for the credentials.
    dwErr = CredUIPromptForCredentials(
        &cui,                             //  CREDUI_INFO structure
        TEXT(""),                         //  Target for credentials
        NULL,                             //  Reserved
        0,                                //  Reason
        pszName,                          //  User name
        CREDUI_MAX_USERNAME_LENGTH,       //  Max number for user name
        pszPwd,                           //  Password
        CREDUI_MAX_PASSWORD_LENGTH,       //  Max number for password
        &fSave,                           //  State of save check box
        CREDUI_FLAGS_GENERIC_CREDENTIALS |  //  Flags
        CREDUI_FLAGS_ALWAYS_SHOW_UI |
        CREDUI_FLAGS_DO_NOT_PERSIST);  
    if(dwErr)
    {
        cout << "Did not get credentials." << endl;
        pRootFolder->Release();
        pTask->Release();    
        CoUninitialize();
        return 1;      
    }
    
    //  ------------------------------------------------------
    //  Save the task in the root folder.
    IRegisteredTask *pRegisteredTask = NULL;
    hr = pRootFolder->RegisterTaskDefinition(
            _bstr_t( wszTaskName ),
            pTask,
            TASK_CREATE_OR_UPDATE, 
            _variant_t(_bstr_t(pszName)), 
            _variant_t(_bstr_t(pszPwd)), 
            TASK_LOGON_PASSWORD,
            _variant_t(L""),
            &pRegisteredTask);
    if( FAILED(hr) )
    {
        printf("\nError saving the Task : %x", hr );
        pRootFolder->Release();
        pTask->Release();
        CoUninitialize();
        SecureZeroMemory(pszName, sizeof(pszName));
        SecureZeroMemory(pszPwd, sizeof(pszPwd));
        return 1;
    }
    
    printf("\n Success! Task successfully registered. " );
    //  Clean up.
    pRootFolder->Release();
    pTask->Release();
    pRegisteredTask->Release();
    CoUninitialize();
    // When you have finished using the credentials,
    // erase them from memory.
    SecureZeroMemory(pszName, sizeof(pszName));
    SecureZeroMemory(pszPwd, sizeof(pszPwd));
    return 0;
}

This code example and others are available in the MSDN Library at https://msdn.microsoft.com/library/. You also can use VBScript and the XML Schema to schedule a task to send an e-mail when an event occurs. For code examples about how to schedule a task to send an e-mail when an event occurs in VBScript and XML Schema, see "Sending an E-mail When an Event Occurs" in the MSDN Library at https://msdn.microsoft.com/library/.

For a complete list of all interfaces available in Task Scheduler 2.0, see "Task Scheduler Interfaces" in the MSDN Library at https://msdn.microsoft.com/library/.

New Scripting Objects for VBScript Developers

Windows Vista provides new scripting objects that VBScript developers can use to programmatically access the functionalities that are available in Task Scheduler 2.0. Task Scheduler interfaces now are derived from IDispatch, providing full support for script development. Now, VBScript developers can use scripting to define a task just as C++ developers do.

You can use VBScript instead of C++ to define an event trigger and a message box action when an event occurs. Showing a message box when an event occurs enables you to alert the user with a specific message. You can specify the text in the message and the title of the message box. In both the title and message body, you can use parameterized strings that contain values from an event's XML. For example, if the task is triggered by an event that contains an element, x, you can set the value of the ValuesQuery property of the event trigger by specifying an XPath query to find x, and a name, y, for this value. The value of x can be included in the message box title or message by using $(y) in the title or message text.

When you create a task that shows a message box when an event occurs, the task must contain an event trigger that specifies an event query, where that event query subscribes to an event logged in an event log. When the event occurs, the task is triggered. The task also must contain a message box action that alerts the user with a specific message when that task is triggered. You also can define a start boundary time and an end boundary time for the task. Register the task using an interactive logon type for the security context of the task; that means the task will execute if the user who registered the task is logged on and an event is received. For detailed information, see "Message Box Example (Scripting)" in the MSDN Library at https://msdn.microsoft.com/library/.

For code examples that show a message box when an event occurs using C++ and XML Schema, see "Showing a Message Box When an Event Occurs" in the MSDN Library at https://msdn.microsoft.com/library/.

New Task Scheduler Schema

Task Scheduler now enables you to create and manage tasks through XML-formatted documents. Developers can use a Task Scheduler schema to define XML to register tasks with the Task Scheduler service. Software developers can create their own XML, validate it against this schema, and register tasks using the ITaskFolder::RegisterTask method.

You can use XML instead of C++ or VBScript to define an event trigger and a message box action when an event occurs. For more information about how to use XML to define a task that sends an e-mail when an event occurs, see "Event Trigger Example (XML)" in the MSDN Library at https://msdn.microsoft.com/library/. For an example of how to use XML to define a task that a message box when an event occurs, see "Message Box Example (XML)" in the MSDN Library at https://msdn.microsoft.com/library/.For detailed information about the new Task scheduler APIs, scripting objects and XML schema, see "Task Scheduler Reference" in the MSDN Library at in the MSDN Library at https://msdn.microsoft.com/library/.

Conclusion

The new Task Scheduler is a dependable, powerful addition in Windows Vista. Task Scheduler provides new APIs and scripting capabilities that give developers greater flexibility to develop for Task Scheduler. Task Scheduler 2.0 also provides greater flexibility in automating stand-alone, repeated, and sequential tasks. Task Scheduler 2.0 is designed to provide the flexibility administrators need to automate common desktop tasks more reliably and securely.

See Also

Concepts

Windows Vista Management

Developing for Windows Management

Task Scheduler 2.0