寫程式執行 Windows 工作排程

寫程式執行 Windows 工作排程

既然有了寫程式呼叫 SQL Agent 底下的作業(Jobs),那應該有人會想寫程式執行 Windows 排程,本篇就介紹如何利用 Task Scheduler Managed Wrapper Library 來完成這樣的需求,而對於 Task Scheduler Managed Wrapper 還不是很瞭解的人,建議參考MSDN 的 CodePlex 教學文章取得預備知識。

假設大家都做過功課了,就依照微軟 CodePlex 教學指示下載所需檔案,本文的範例只需用到 TaskScheduler.zip,目前我下載到的最新版本是 1.5 Beta 2,解壓縮後可以得到 Microsoft.Win32.TaskScheduler.dll(核心元件)、Microsoft.Win32.TaskSchedule.xml(註解文件),以及 TaskScheduler.chm(說明文件),需引用 dll 檔:

WindowsTask 

本例仍然寫 ASP.NET 程式來示範,動線規劃是這樣:
  1. 頁面載入時取得主機上的所有排程塞到下拉選單裡,選取之後可以看到基本排程資訊;
  2. 放置一個按鈕,可用來執行選取的排程。

<div>
    Root folder tasks:<asp:DropDownList ID="TasksDropDownList" runat="server" AutoPostBack="True"
        OnSelectedIndexChanged="TasksDropDownList_SelectedIndexChanged">
        <asp:ListItem> - 請選擇 - </asp:ListItem>
    </asp:DropDownList>
    <br />
    <asp:Literal ID="InfoLiteral" runat="server"></asp:Literal>
    <hr />
    <asp:Button ID="RunButton" runat="server" Text=">> Run, Forrest. Run!" OnClick="RunButton_Click"
        OnClientClick="return confirm('您確定要執行 Windows 工作排程?');" />
    <br />
    <asp:Literal ID="ResultLiteral" runat="server" EnableViewState="false"></asp:Literal>
</div>
</form>


const string workstation = "server";
const string domain = "domain";
const string userName = "hunterpo";
const string password = "P@ssW0rd";
const bool forceV1 = true;

/// <summary>
/// 取得主機上所有的工作排程
/// </summary>
private void GetTasks()
{
    using (TaskService ts = new TaskService(workstation, userName, domain, password, forceV1))
    {
        // 保留預設選項 "- 請選擇 -" 
        if (TasksDropDownList.Items.Count > 1)
        {
            int startIndex = TasksDropDownList.Items.Count - 1;

            for (int i = startIndex; i > 0; i--)
            {
                TasksDropDownList.Items.RemoveAt(i);
            }
        }

        // Windows Vista 之前內建的都是 Task Scheduler 1.0
        // 這情況下 Root Folder 是唯一的存放目錄
        TaskFolder root = ts.RootFolder;

        foreach (Task t in root.Tasks)
        {
            ListItem item = new ListItem(t.Name);

            TasksDropDownList.Items.Add(item);
        }
    }
}

/// <summary>
/// 擷取工作排程內容
/// </summary>
/// <param name="taskName">排程名稱</param>
private string GetTaskInfo(string taskName)
{
    StringBuilder sb = new StringBuilder();

    using (TaskService ts = new TaskService(workstation, userName, domain, password, forceV1))
    {
        try
        {
            Task t = ts.GetTask(taskName);

            sb.AppendFormat(
                "<ul><li>工作排程:{0}</li><li>建立者:{1}</li><li>狀態:{2}</li>",
                t.Name,
                t.Definition.RegistrationInfo.Author,
                t.State);

            foreach (Trigger trg in t.Definition.Triggers)
                sb.AppendFormat("<li>執行時間:{0}</li>", trg);

            foreach (Action act in t.Definition.Actions)
                sb.AppendFormat("<li>執行動作:{0}</li>", act);

            sb.Append("</ul>");
        }
        catch (Exception ex)
        {
            sb.AppendFormat(
                "取回 Windows 排程資訊發生例外:<br /><span style='color: #ff0000;'>{0}</span>",
                ex.Message);
        }
    }

    return sb.ToString();
}

/// <summary>
/// 立即執行工作排程
/// </summary>
/// <param name="taskName">排程名稱</param>
/// <returns></returns>
private int RunTask(string taskName)
{
    using (TaskService ts = new TaskService(workstation, userName, domain, password, forceV1))
    {
        Task t = ts.GetTask(taskName);
        t.Run();

        return t.LastTaskResult;
    }
}

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
        GetTasks();
}

protected void TasksDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
    if (TasksDropDownList.SelectedIndex > 0)
    {
        string taskName = TasksDropDownList.SelectedValue;

        InfoLiteral.Text = GetTaskInfo(taskName);
    }
    else
    {
        InfoLiteral.Text = String.Empty;
    }
}

protected void RunButton_Click(object sender, EventArgs e)
{
    if (TasksDropDownList.SelectedIndex > 0)
    {
        string taskName = TasksDropDownList.SelectedValue;
        int rc = RunTask(taskName);

        ResultLiteral.Text = String.Format(
            "<span style='color: #0000ff;'>***執行結果:{0}***</span><br /><br />",
            rc);
    }
    else
    {
        ResultLiteral.Text = "<span style='color: #ff0000;'>選取的項目無效。</span>";
    }
}

執行的效果如下:

WindowsTask_Exec 

這裡有個經驗不得不提,原本我的開發環境是運行在 Windows XP Pro 64-bit 上,而我們公司伺服器目前都還是停留在 Windows Server 2003,所以用 Task Sechduler 1.0 的物件模型存取 Windows 工作排程(本機或遠端)是沒有問題的,後來我把相同程式碼移到 Windows 7 上以取得本機的排程,然後將 forceV1 改為 false,單純的認為改用 Task Scheduler 2.0 物件模型應該行得通,沒想到卻發生例外狀況:

WindowsTask_Error_AccessDenied

令人不解的地方是我只要將指定的驗證資料去掉,改用 TaskService 預設建構式去做就能順利存取,例如:

/// 取得主機上所有的工作排程
/// </summary>
private void GetTasks()
{
    using (TaskService ts = new TaskService())
    {
        // 以下省略...
    }
}

但 TaskService 預設建構式是以當前使用者的 Windows 驗證資料去存取本機工作排程(我用隨附於 VWD 的 Web 開發伺服器測試),跟我一開始強制指定的帳號密碼其實是同一個,離奇的是執行結果卻不一樣,不知道是不是遺漏了什麼細節…,這點若有找到答案再更新上來好了。(實在是待辦事項太多,讓我苟且一下吧 XD)

好啦,本篇稍微小試了一下,這個函式庫其實相當完整,可以操作的動作還很多,若對於用程式控制 Windows 工作排程有需要的人,可以發揮想像力去研究更進一步的應用,應該是滿 powerful 滴!


參考連結: