Task Scheduler Managed Wrapper: 由程式來排程工作

Codeplex 軟體套件(Package)資訊
套件名稱 Task Scheduler Managed Wrapper
作者 Coordinators: dahall
Developers: gserfaty, SteveHarveyUK
目前版本 1.4.1 Stable
URL http://taskscheduler.codeplex.com/
使用難易度
使用此套件時可用的輔助工具 工作排程器(Task Scheduler)
Task Scheduler Managed Wrapper 的文件
基礎知識 基本類別使用。
基礎 COM Interop。

排程工作:開發人員必備的知識

大多數的開發人員也許都會有這麼一段經歷:『老闆要我把常用的報表在每週一上班之前寄到他的 Email』、『我想要在半夜時重新計算資料庫以產生報表』或是『工作 A 如果在上班時段做,會讓系統 overload,要拿到半夜來做,但我又不想顧到半夜...』等,這些可能會在非工作時間,或是要定時處理的工作(job),通常作業系統都會提供工具來支援它,以 Windows 來說,就是 Task Scheduler(工作排程器),也就是常聽到的 at(命令列化的 Task Scheduler)指令。

 

NOTE

這裡所指的 at 指令,不是指數據機(Modem)在用的那種海斯指令集(Hayes Command Set; AT Command Set),讀者可以想像它是 @(英文字的 at)的意思,表示工作要 @ 何時執行。

 

NOTE

Unix/Linux 上也有這樣子的工具,名稱是 cron。

 

排程工作在網管和系統管理的領域用的非常多,所有與系統與網路有關的週期性工作,都會交由排程工作來做,當然,資料庫管理以及每段固定時間(週/月/季/半年/年等)需要執行的統計工作,也多由排程工具來做(以 SQL Server 為例,就是 SQL Server Agent),它可以把許多的工作自動化起來,讓人員可以在同一時間去做別的事情。因此,除了網管和系統工程師要學會如何操作它,開發人員也應該要學著如何使用,尤其是商用系統的開發人員。

在 Windows 95 的 Plus! Pack 時期,微軟加入了一個排程執行工具,稱為 mstask.exe,此即 Task Scheduler 1.0,並且在 Windows 98 開始內建在作業系統中,Windows NT 與之後的更新版本(Windows 2000, XP, 2003 等)將這個工具以 Windows 服務的方式來執行,並且在控制台放置一個功能區,作為 GUI 編輯的介面,不過它只有開放 C/C++ 的 COM 介面給應用程式存取,對於 Visual Basic 以及支援 COM Automation 的程式語言或平台未提供支援,這點對於非 C/C++ 的開發人員而言似乎不是一件好事,連 .NET Framework 也未內建此類支援,仍然要由 C/C++ 來存取。所幸,Windows Vista 開始,Task Scheduler 升級為 2.0 版,這個版本開放了 COM 自動化介面的 API 供開發人員使用,同時也允許 1.0 時期的 C/C++ 介面存取 2.0 版本的函式庫功能以保持相容性。

不過在 Windows Vista 還沒有出現之前,就已經有人試著將 C/C++ 的 COM 介面控制方式,在 .NET Framework 上實現,所幸在 COM 互通功能(Interoperability)的強力支援下,這個功能成功的被實現出來,也就是本文所要介紹的 Task Scheduler Managed Wrapper Library。

Task Scheduler 基本概念

Task Scheduler 是由一個或數個 Task(工作)組成,一個 Task 代表一個任務,它會有幾個參數:

參數 使用者介面
觸發器(trigger),指示此工作會在何時觸發。在 Task Scheduler 2.0 時,一個工作可以有多個觸發器。1.0 時則每個工作只能有一個觸發器。
動作(action),指示此工作會觸發哪些程式或指令。在 Task Scheduler 2.0 時,一個工作可以有多個動作(最多 32 個動作)。1.0 時則每個工作只能有一個動作。
執行權限(Principals, Security Context),指示此工作要用哪個帳戶或安全權限執行,所有動作都會使用相同的權限來呼叫。
條件(Settings),可以決定工作在何種情況下才會啟動。
登錄資料(Registration Information),記錄系統管理的相關資訊,像是建立者(author)或工作說明(description)。
資料(Data),此工作所需要的額外資訊。  

 


圖:Task 內部結構圖(來源:MSDN Task Scheduler SDK)

Task Scheduler 的動作支援四種類型,分別為啟動程式、自動發信、顯示訊息以及 COM 自訂處理器(handler),其中只有 COM 自訂處理器一項必須要由程式來做以外,其他三種均可以透過使用者介面設定。

觸發器則 2.0 和 1.0 的支援不太相同,列表如下:

觸發器 功能 2.0 1.0
Event trigger 以指定事件為觸發條件。  
Time trigger 以指定日期時間為觸發條件(在 1.0 版稱為 Once Trigger)。
Daily trigger 每天觸發(以日曆為主)。
Weekly trigger 每週觸發(以日曆為主)。
Monthly trigger 每月觸發(以日曆為主)。
Monthly day-of-week (DOW) trigger 以day-of-week方式觸發,像是每週的第幾天;每月的第幾週或是每年的第幾個月。
Idle trigger 在系統閒置時觸發(在 1.0 版稱為 OnIdle Trigger)。
Registration trigger 在工作登錄或更新時觸發。  
Boot trigger 在開機時觸發(在 1.0 版稱為 System Start Trigger)。
Logon trigger 在登入時觸發。
Session state change trigger 在 Terminal Service 的會談狀態變更時觸發。  

 

NOTE

在 Task Scheduler 2.0 使用者介面中看到的觸發器類型,可能會不止表中所列的那幾項,像是『鎖定工作站時觸發』以及『解除工作站鎖定時觸發』均不在上表中,但事實上它們都是 Event Trigger 的一種,只是已經有固定的參數了。

 

Task Scheduler 2.0 中新增資料夾階層的功能,有助於將各個工作分類存放,但 Task Scheduler 1.0 只支援單一根資料夾(root folder),因此在資料夾使用上要特別注意相容性的問題。


圖:Task Scheduler 2.0 資料夾階層功能

Task Scheduler Managed Wrapper Library

Task Scheduler Managed Wrapper Library 最早是在 Code Project 網站中的一個專案,由 David Hall 於 2002 年所開發,當時還只是 .NET Framework 1.0 的時代,在 Code Project 的專案內容中,David 說明了如何由 Platform SDK 所附的 mstask.idl,使用 Tlbimp.exe 產生引用的 C# 程式碼再移植到他的專案中的方法。裡面使用了許多的 COM Interoperability 的技巧,值得學習 COM Interop 的開發人員作為參考。

 

NOTE

通常要探知 COM 介面的原始結構,由 SDK 所附的標頭檔(.h 檔)以及介面定義清單檔(Interface Definition List, IDL,即 *.idl)就可以得到許多 COM 的介面宣告,包括定義、方法、參數型別、屬性清單、常數宣告以及列舉項(值)等都可以由這裡獲得,因此若要開發 COM/API 橋接到 .NET Framework 的程式時,API 宣告的標頭檔以及 COM 的介面清單檔都是重要的參考資料。

 

Task Scheduler Managed Wrapper Library 可以使用在 .NET 的任何一個版本,並且可同時支援 Task Scheduler 1.0 以及 2.0 版本,開發人員也可以選用微軟所提供的 Task Scheduler API 來存取 Task Scheduler 2.0 版。

使用方式很簡單,只要在它的網站中下載 TaskScheduler.zip 並解壓縮,即可得到 Microsoft.Win32.TaskScheduler.dll(核心元件)、TimeSpan2.dll(特製的 TimeSpan 物件函式庫)以及 Microsoft.Win32.TaskSchedulerEditor.dll(使用者介面元件)三個檔案,引用時需要引用 Microsoft.Win32.TaskScheduler.dll 以及 TimeSpan2.dll 兩個檔案,若要使用到它的使用者介面函式庫,則還要再引用 Microsoft.Win32.TaskSchedulerEditor.dll。

下列範例程式為簡單操作 Task Scheduler API 的範例:

[C#]

using System;
using Microsoft.Win32.TaskScheduler;

class Program
{
   static void Main(string[] args)
   {
      // Get the service on the local machine
      TaskService ts = new TaskService();

      // Create a new task definition and assign properties
      TaskDefinition td = ts.NewTask();
      td.RegistrationInfo.Description = "Does something";

      // Create a trigger that will fire the task at this time every other day
      td.Triggers.Add(new DailyTrigger { DaysInterval = 2 });

      // Create an action that will launch Notepad whenever the trigger fires
      td.Actions.Add(new ExecAction("notepad.exe", "c:\\test.log", null));

      // Register the task in the root folder
      ts.RootFolder.RegisterTaskDefinition(@"Test", td);

      // Remove the task we just created
      ts.RootFolder.DeleteTask("Test");
   }
}

Task Scheduler Wrapper 的起點是 TaskService 類別,它提供了管理與列舉資料夾的功能,以及取得工作以及連線狀態的功能,並且也可以在建構時設定連結到指定的伺服器。

TaskFolder 物件是處理資料夾的物件,它具有下列功能:

  • 建立與刪除子資料夾(CreateFolder 與 DeleteFolder 方法)。
  • 登錄工作(RegisterTaskDefinition 方法)以及刪除工作(DeleteTask 方法)。
  • 取得子工作清單(Tasks 屬性)。
  • 取得子資料夾清單(SubFolders 屬性)。
  • 設定與取得 Security Descriptior 的 SDDL Form 資訊(GetSecurityDescriptorSddlForm 與 SetSecurityDescriptotSddlForm 方法)。

它的使用方法也很簡單,例如下列程式可以建立與刪除資料夾:

[C#]

// create folder.
TaskService service = new TaskService();
TaskFolder folder = service.GetFolder(@"\");

folder.CreateFolder(this.txtFolderName.Text, folder.GetSecurityDescriptorSddlForm(AccessControlSections.Access));

folder = null;
service = null;

[C#]

// create folder.
TaskService service = new TaskService();
TaskFolder folder = service.GetFolder(@"\");

folder.CreateFolder(this.txtFolderName.Text, folder.GetSecurityDescriptorSddlForm(AccessControlSections.Access));

folder = null;
service = null;

[C#]

// delete folder.
this._taskService = new TaskService();

TaskFolder folder = this._taskService.GetFolder(@"\MyFolder");

if (folder.Tasks.Count > 0)
{
    throw new InvaildOperationException("TASKS_EXIST");
}

if (folder.SubFolders.Count > 0)
{
    throw new InvaildOperationException("SUB_FOLDERS_EXIST");
}

TaskFolder folderParent = this._taskService.GetFolder(folder.Path.Replace(@"\" + folder.Name, ""));
folderParent.DeleteFolder(folder.Name);

folderParent = null;
folder = null;

NOTE

Task Scheduler 的根資料夾為 “\”,且 Task Scheduler 1.0 只支援根資料夾,不支援建立資料夾的任何作業。

 

NOTE

TaskFolder.CreateFolder() 需要的第二個參數,可以直接由父資料夾的 GetSecurityDescriptorSddlForm() 方法來取得它的 Security Descriptor SDDL Form 作為安全設定的基礎。

 

NOTE

在 Task Scheduler 2.0 上執行刪除資料夾的作業時,即使會成功,也會擲出一個 NotV1SupportedException,這是程式碼上的小瑕疪,可以利用 try/catch 的方式來避開,或是直接修改原始程式碼,加上版本的處理來解決它。

 

Task 物件則是代表工作,它的實際設定為 TaskDefintion 物件(由 Task.Defintion 屬性取得),而它的執行狀態以及設定值,可直接由相關的屬性得知:

  • Enabled 設定工作是否啟用。
  • LastRunTime 以及 LastRunResult 可以取得最後一次執行的時間與結果。
  • NextRunTime 為下一次執行時間。
  • State 可取得目前工作的狀態。
  • NumberOfMissedRuns 可得知被遺漏排程的次數。
  • Run() 與 RunEx() 可以執行工作。
  • Stop() 可停止工作的執行。

TaskDefinition 物件裝載了 Task 的基本設定,以及其包含的 Action、Trigger、RegistrationInfo 與 Settings 等物件的設定(可參考前面的說明),Actions 和 Triggers 都是集合物件,可用 Add() 來加入特定的 Action 或 Trigger,或用 AddNew() 來建立新的 Action 或 Trigger,再設定它們的屬性值。

可用的 Action 均繼承自 Action 物件,支援的 Action 清單如下:

動件 功能 物件名稱
Email Action 發送 Email(1.0 不支援)。 EmailAction
Show Message Action 顯示訊息(1.0 不支援)。 ShowMessageAction
Execution Action 執行應用程式、批次檔或指令。 ExecAction
COM Handler Action 執行自訂的 COM 處理器(1.0 不支援)。 ComHandlerAction

 

例如建立一個 Email 的 Action 的程式如下:

[C#]

EmailAction eAction = new EmailAction("Task fired", "sender@email.com", "recipient@email.com", "You just got a message", "smtp.company.com");
eAction.Cc = "alternate@email.com";

可用的 Trigger 均繼承自 Trigger 物件,支援的 Triggers 清單如下:

觸發器 功能 物件名稱
Event trigger 以指定事件為觸發條件(1.0 不支援)。 EventTrigger
Time trigger 以指定日期時間為觸發條件(在 1.0 版稱為 Once Trigger)。 TimeTrigger
Daily trigger 每天觸發(以日曆為主)。 DailyTrigger
Weekly trigger 每週觸發(以日曆為主)。 WeeklyTrigger
Monthly trigger 每月觸發(以日曆為主)。 MonthlyTrigger
Monthly day-of-week (DOW) trigger 以 day-of-week 方式觸發,像是每週的第幾天;每月的第幾週或是每年的第幾個月。 MonthlyDOWTrigger
Idle trigger 在系統閒置時觸發(在 1.0 版稱為 OnIdle Trigger)。 IdleTrigger
Registration trigger 在工作登錄或更新時觸發(1.0 不支援)。 RegistrationTrigger
Boot trigger 在開機時觸發(在 1.0 版稱為 System Start Trigger)。 BootTrigger
Logon trigger 在登入時觸發。 LogonTrigger
Session state change trigger 在 Terminal Service 的會談狀態變更時觸發(1.0 不支援)。 SessionStateChangeTrigger

 

例如設定每月執行的觸發器的程式如下:

[C#]

MonthlyTrigger mTrigger = new MonthlyTrigger();
mTrigger.StartBoundary = DateTime.Today + TimeSpan.FromHours(10);
mTrigger.DaysOfMonth = new int[] { 10, 20 };
mTrigger.MonthsOfYear = MonthsOfTheYear.July | MonthsOfTheYear.November;

進階功能:COM 處理器

這是 Task Scheduler 2.0 才有的新功能,允許開發人員自行建立一個支援 Task Scheduler COM 工作的元件,讓 Task Scheduler 幫你執行,雖然它名為 COM 處理器,但是 .NET 程式可以利用 COM Interop 方式將元件公開給 COM 介面,如此就可以當做一個 COM 元件供外部存取。另外,COM 處理器的工作只可以在程式碼中實作,無法透過 GUI 介面處理,因此這個能力算是保留給開發人員使用的,也就是需要額外撰寫登錄 COM 工作的程式碼並在安裝程式使用(或是撰寫成獨立的小程式亦可)。

要建立 COM 處理器,請執行下列步驟:

  1. 建立一個類別庫專案。

  2. 加入 Microsoft.Win32.TaskScheduler.dll 以及 TimeSpan2.dll 的參考。

  3. 建立一個新類別,並設定與實作 Microsoft.Win32.TaskScheduler 命名空間的 ITaskHandler 介面。

  4. 設定專案要產生 Type Library,即註冊 COM Interop:

     

  5. 建置專案,並撰寫註冊COM Handler工作的程式碼,例如下列的程式碼:

[C#]

ComHandlerAction comAction = new ComHandlerAction(new Guid("{CE7D4428-8A77-4c5d-8A13-5CAB5D1EC734}"));
comAction.Data = "Something specific the COM object needs to execute. This can be left unassigned as well.";
  1. 登錄完成的工作,可以在動作中看到『自訂處理常式』,代表是COM Handler,而且這個動作是無法被編輯的:

 

ITaskHandler 介面具有四個方法,分別是 Start()、Pause()、Resume() 以及 Stop() 四個方法,代表啟動、暫停、繼續以及停止四個動作,由 Task Scheduler 視情況呼叫控制。其中 Start() 方法會傳入 Data 參數,這個參數是註冊 COM Handler 時所給定的 Data 屬性值,作為執行時的參數,而 Stop() 方法會需要傳回一個 RetCode,代表成功或失敗,若執行成功應傳回 0,若出現非零值時會被判斷是失敗。

 

NOTE

RetCode 參數的 COM 宣告是 HRESULT 值,因此參數設為0時,會被轉換成 ERROR_SUCCESS,代表執行成功,若是非零值時,會被轉譯成錯誤的資訊,並寫入執行記錄中。

 

下列程式碼是 COM Handler 的範例實作,功能為每五秒於事件記錄中寫入一筆資料,會寫入 12 次:

[ComVisible(true), Guid("CE7D4428-8A77-4c5d-8A13-5CAB5D1EC734"), ClassInterface(ClassInterfaceType.None)]
public sealed class MyCOMTask : ITaskHandler
{
    private ITaskHandlerStatus handlerService;
    private Timer timer;
    private DateTime lastWriteTime = DateTime.MinValue;
    private byte writeCount = 0;
    private const string file = @"C:\TaskLog.txt";

    void ITaskHandler.Start(object pHandlerServices, string Data)
    {
        handlerService = pHandlerServices as ITaskHandlerStatus;
        lastWriteTime = DateTime.Now;
        timer_Elapsed(null, null);
        timer.Enabled = true;
    }

    void ITaskHandler.Stop(out int pRetCode)
    {
        timer.Enabled = false;
        pRetCode = 0;
    }

    void ITaskHandler.Pause()
    {
        timer.Enabled = false;
    }

    void ITaskHandler.Resume()
    {
        timer.Enabled = true;
    }

    public MyCOMTask()
    {
        timer = new Timer(5000) { AutoReset = true };
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
    }

    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (writeCount < 12)
        {
            try
            {
                using (StreamWriter wri = File.AppendText(file))
                    wri.WriteLine("Log entry {0}", DateTime.Now);

                handlerService.UpdateStatus((short)(++writeCount / 12), string.Format("Log file started at {0}", lastWriteTime));
            }
            catch { }
        }

        if (writeCount >= 12)
        {
            timer.Enabled = false;
            writeCount = 0;
            handlerService.TaskCompleted(0);
        }
    }

}

結語

Task Scheduler Wrapper Library 是相容性高(可用在 1.0 和 2.0)的一組工具函式庫,並直接控制 Task Scheduler 的功能,以實現能利用程式碼去存取與控制工作排程的能力,不必再額外去撰寫命令列指令或是使用 GUI 工具去設定排程了,讀者可以善用它來開發自己的工具,像是 Web 化的排程管理員或集中的管理工具等。