65.9K
CodeProject 正在变化。 阅读更多。
Home

利用任务计划程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (18投票s)

2006 年 2 月 16 日

CPOL

9分钟阅读

viewsIcon

121626

downloadIcon

3433

在应用程序中使用任务计划程序接口可能会很棘手,因为它需要对 COM 技术有详细的了解。本文基于简化与接口的通信,提出了一个实用的解决方案。

本文由 Rafal Piotrowski 撰写,最初发表于 2005 年 3 月的 Software 2.0 杂志。您可以在 SDJ 网站上找到更多文章。

引言

Windows 操作系统系列为我们提供了一个名为“任务计划程序”的有用机制。利用它,我们可以指示系统多次执行任务,而无需手动运行,也无需记住它们。适用于任务计划程序的典型示例是定期扫描系统以查找病毒或木马。

我们可以通过两种方式使用任务计划程序,一种是通过在“控制面板”中运行“任务计划程序”,并使用对话框窗口设置所需的选项。从我们的角度来看,更有趣的方法是利用任务计划程序 API,即一系列对象和 COM 接口,它们允许在我们的程序中安排任务。但是,在我们开始使用此机制之前,每次想要安排任务时,都必须确保检查任务计划程序是否正在运行。在 Windows 95 和 NT5 下,它默认未安装,因此您可能需要将其打开。在 Win98 及更新版本中,计划程序默认安装并处于活动状态,但也会有程序可以将其禁用。

任务计划程序 API

使用任务计划程序 API,我们可以

  • 创建新任务,
  • 设置任务的执行时间和频率,
  • 调整任务的执行时间和频率,
  • 定义任务应如何执行(参数),
  • 停止正在运行的任务。

我们通过创建适当的对象和 COM 接口,并执行它们的适当方法来添加任务。

创建任务对象

为了创建新任务,我们使用接口 ITaskSchedulerITaskIPersistFile。为了成功创建,应向 ITaskScheduler::NewWorkItem() 方法提供一个唯一的任务名称。如果该名称已在使用中,该方法将返回 ERROR_FILE_EXISTS

任务创建涉及一系列操作。要获取 TaskScheduler 类对象,必须调用函数 CoInitialize()(初始化 COM 库)和 CoCreateInstance()。然后,调用 ITaskScheduler::NewWorkItem() 来创建新任务;此方法返回指向 ITask 接口的指针。通过调用 IPersistFile::Save() 方法将新创建的任务写入磁盘 — IPersistFileITask 接口支持的标准 COM 接口)。最后,通过调用 ITask::Release() 方法,我们释放获得的资源(Release()IUnknown 接口的方法,ITask 继承自该接口)。

现在我们有了一个任务,但系统不知道何时应运行它。要实现这一点,我们需要创建一个触发器 — 一个与特定任务绑定的对象,指示何时以及多久执行一次。

创建触发器

在创建触发器时,我们利用三个接口:IScheduledWorkItemITaskTriggerIPersistFile。我们可以区分执行任务的触发器(在给定时间段内)和执行任务的触发器(在用户选择不活动一段时间后)。由于可以将多个触发器分配给单个任务对象,因此我们可以例如让任务每天运行一次,并在用户不活动 5 分钟后运行。

要创建触发器,首先必须调用 CoInitialize()CoCreateInstance();如果紧随创建任务之后设置触发器,则此步骤不是必需的。接下来,我们调用 ITaskScheduler::Activate 方法来获取 ITask 接口,然后调用 IScheduledWorkItem::CreateTrigger() 来创建触发器。我们定义一个 TASK_TRIGGER 结构(wBeginDaywBeginMonthwBeginYear 变量必须具有可接受的值),然后执行 ITaskTrigger::SetTrigger 来设置触发器的参数。最后,我们使用 IPersistFile::Save() 将新创建的触发器保存到磁盘,并使用 ITask::Release() 释放资源。

Listing 1 展示了将任务添加到任务计划程序的示例。

Listing 1. 将任务添加到计划程序

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");

CTask 类

由于在 C++ 中编写基于 COM 技术程序并非易事,因此我创建了一个名为 CTask 的类来包装 COM 调用。使用该类,可以快速有效地将任务添加到任务计划程序。

为了使用 CTask 类创建新任务,我们需要指定

  • 程序的名称和完整路径(必需),
  • 我们希望传递给程序的命令行参数(可选),
  • 程序的启动目录(可选),
  • 帐户名和密码(NT 必需),
  • 开始日期和时间(可选,对于仅运行一次的任务不可用),
  • 结束日期(见上文,可选),
  • 执行频率(一次、每天、每周、每月,必需),
  • 注释(将在任务计划程序小程序的对话框中显示的文本 - 可选)。

上述每个任务参数都与相应命名的类方法相关联,用于设置正确的值

  • 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 )

此外,还可以通过使用前缀为 Get_ 的方法读取每个成员值,因此程序名称可以通过 GetProgram() 获取,而 GetParameters() 则检查参数等。

在 Listing 2 中,可以看到一个使用 CTask 类将任务添加到任务计划程序的简单控制台应用程序的源代码。警告:链接之前,您必须将运行时库更改为多线程的,方法是选择:项目->设置C++ 选项卡,代码生成类别,然后在“使用运行时库”下拉列表中选择“Debug Multithreaded”。

Listing 2. CTask 类的一个简单应用

#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;
    }
}

添加 GUI

现在是时候构建一个更高级的应用程序来管理任务计划程序,并配备图形用户界面。为此,我们将使用 MS Visual C++ IDE。

构建用户界面

让我们创建一个新项目,并为其指定一个我们选择的名称,例如 MyTaskDemo。在向导中选择 MFC Application 作为应用程序类型。选择“Dialog – Based”选项,将其他选项保留为默认值,然后选择“Next”再选择“Finish”。在“Resources Editor”中,选择向导为我们创建的对话框窗口。现在我们将为我们的应用程序构建一个用户界面,其目标外观如图 1 所示。

Figure 1. MyTaskDemo 应用程序的用户界面

Options”组不需要太多讨论。它由四个“Edit”字段、四个“Static”类型的控件和两个按钮组成。在“Frequency”组中,不要忘记在“Radio”按钮的选项中选择“Group”。开始日期和结束日期是 DateTimePicker 控件。开始日期和结束日期的格式为“Short Date”,开始时间为“Time”格式。为结束日期选择“Show Null”以允许任务无限期运行。在“Password”字段(一个“Edit”控件)的属性中,应选择“Password”字段。

准备 CTask 类的应用程序

CTask.cppCTask.h 文件添加到项目中(Project->Add To Project->Files...)。您应该会在类视图窗口中看到 CTask 类。暂时,我们将将其放在一边,切换到资源编辑器,然后选择我们构建了 GUI 的对话框。按键盘上的 [Ctrl+W],或选择 Class Wizard。我们将被询问是否要为我们的对话框创建新类或使用现有类;我们选择现有类 CMyTaskDemoDlg。下一个任务是为按钮添加“Event Handlers” - 处理事件的函数,例如鼠标点击;最简单的方法是在“Resources Editor”中双击按钮,然后通过将成员变量与相应控件绑定来将它们添加到 CMyTaskDemoDlg 类;我们可以通过从列表中选择控件并单击“Add Variable”按钮来完成此操作。

添加实际程序代码

现在是时候添加源代码来执行必要的初始化并使用 CTask 类了。比较下载中的两个源项目Step 2Step 3,在这方面可能会非常有启发性。

MyTaskDemo.cppInitInstance() 方法中,使用 AfxOleInit() 函数初始化 OLE。

大部分代码将放入 MyTaskDemoDlg.cpp。我只会简要讨论需要添加的内容,更多细节可以在下载的 Step 3 项目的源代码中找到。

首先,提供以下指令

  • #include CTask.h, //CTask 类的声明
  • #include <shlobj.h> //用于 SHBrowseForFolder()
  • OnInitDialog() 方法中,提供必要的初始化(参见源代码),
  • OnBrowseProgram() 方法中,创建一个 CFileDialog 类对象,它将允许我们选择所需的程序,
  • OnBrowsePath() 方法中,我们将使用 Shell API 函数 SHBrowseForFolder(),它可以轻松选择程序的执行目录,
  • OnDeleteTask() 方法中,我们将最终让我们的类发挥作用并使用其方法 Ctask::DeleteTask()
  • OnAddTask() 方法中,将完成大部分工作:此方法读取控件的值,检查它们的有效性,并在数据正确时设置 CTask 的适当成员变量并调用 Ctask::SaveTask()

我们的应用程序现在可以向任务计划程序添加任务。上面的示例可以成功用于编写防病毒软件、个人日历等等。请注意,如果我们为任务提供了名称,但没有提供帐户名或密码,任务将被添加但永远不会执行。我将此方面的改进留给读者作为练习。

可能的改进

另一种可能的改进是创建处理 ANSI 和 UNICODE 的函数对。XXX 不是最佳解决方案,因为从 2000 版本开始,Windows 仅使用 UNICODE,因此所有 ANSI 处理函数都会转换为 UNICODE。任务计划程序 API 还允许列出计划程序中设置的所有任务、浏览它们或编辑其内容;因此,本文绝非详尽无遗。

© . All rights reserved.