工作排程器的工作不執行、以 0x1 結束 ── 原因排查與安全的維運設計

· · 工作排程器, Windows, PowerShell, 業務自動化, 批次處理, 維運, 疑難排解, 技術諮詢

「想讓用 PowerShell 寫的彙總腳本每天早上 6 點自動執行」「手動執行明明沒問題,一放進工作排程器就跑不動」。在接業務自動化的相談案件時,最後幾乎都會遇到這個話題。

本部落格也接連寫過幾篇自動化相關的文章,包括日誌整理的自動化用 Pester 為腳本寫測試從 C# 執行 PowerShell用 Power Automate 自動化業務。這些文章最終都以「用工作排程器定期執行」為前提。然而,工作排程器本身其實是一套個性相當強烈的機制,「手動可以動,但定期執行就失敗」「不知不覺間停止運作卻沒人發現」這類事故一直沒有斷過。

本文將工作排程器機制中直接關係到維運事故的部分──執行帳戶與登入類型、「不執行」時的排查方式、傳回值 0x1 的典型原因、日誌留存方式、多重啟動的控制──依實務上容易碰壁的順序整理出來。

1. 先下結論

  • 工作排程器的大多數問題,並不是出在腳本本身,而是源自對「以誰的身分、在什麼樣的工作階段中執行」理解上的落差。一旦選擇「不論使用者是否登入均執行」,就必須以「會在與互動式工作階段、登入時環境完全不同的世界中執行」為前提來設計。1
  • 調查「不執行」的問題,起點是「歷程記錄(History)」分頁與事件記錄檔。但工作歷程記錄預設是停用的,因此在正式上線前,務必先啟用「啟用所有工作歷程記錄」。2
  • 「上次執行結果」顯示的 0x1,並不是工作排程器本身的錯誤,而是代表所啟動的程式自己傳回了結束代碼 1。原因在腳本那一側,所以要先設計好結束代碼,並建立自行留存日誌的機制。3
  • PowerShell 腳本的基本呼叫方式是 -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "完整路徑",腳本內部的路徑則以 $PSScriptRoot 為基準。「起始位置(選用)」欄位不能加引號,這個經典陷阱也常常在這裡踩到。
  • 要避免因密碼變更而讓工作悄悄失效的事故,需要在執行帳戶的設計上下功夫(盤點服務帳戶,若在網域環境則考慮採用 gMSA)。4
  • 多重啟動的控制(預設為「不啟動新的執行例項」)、執行時間限制(預設 3 天)、電源條件(預設僅在 AC 電源時執行),都是最容易在沒注意到的情況下就沿用預設值上線的代表性設定。註冊工作時請務必明確地逐一決定。5

2. 工作排程器的結構 ── 觸發程序・動作・條件・設定

工作排程器的工作,大致由 4 個要素構成。

要素 內容 容易引發事故的地方
觸發程序 何時啟動(時刻、登入時、事件發生時等) 錯過時刻觸發時的處理方式(後述的 StartWhenAvailable)
動作 執行什麼(程式、引數、啟始資料夾) 引數加引號的方式、啟始資料夾指定錯誤
條件 是否處於允許執行的狀況(電源、網路、閒置) 預設啟用「僅限使用 AC 電源時」
設定 執行中的行為(多重啟動、時間限制、重試) 未確認預設值就直接上線

用 GUI(taskschd.msc)建立的工作,可以匯出為 XML。如果想用 Git 管理工作定義,或是要把同一份工作部署到多台機器,建議用 XML 匯出 + schtasks /Create /XML,或是用 PowerShell 的 ScheduledTasks 模組(New-ScheduledTaskAction / New-ScheduledTaskTrigger / New-ScheduledTaskSettingsSet / Register-ScheduledTask)把它寫成腳本。5

$action   = New-ScheduledTaskAction -Execute 'pwsh.exe' `
    -Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
    -WorkingDirectory 'C:\Jobs'
$trigger  = New-ScheduledTaskTrigger -Daily -At '06:00'
# 電源條件的預設是「僅在使用 AC 電源時啟動・切換為電池供電時停止」。
# 如果是也要在筆電或現場端點上執行的工作,就要在這裡明確允許
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
    -MultipleInstances IgnoreNew `
    -ExecutionTimeLimit (New-TimeSpan -Hours 2) `
    -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
# 為避免密碼顯示在畫面上,透過 Get-Credential 取得
$cred = Get-Credential -UserName 'DOMAIN\svc-batch' -Message '執行帳戶的認證資訊'
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
    -Action $action -Trigger $trigger -Settings $settings `
    -User $cred.UserName -Password $cred.GetNetworkCredential().Password

只要工作定義變成程式碼,就能把在驗證機上測試過的內容原封不動地帶到正式環境,也能避免「不知道現在是用什麼設定在跑」這種狀態。

如果是部署到多台機器,把在 GUI 上建好的工作匯出成 XML、再用 schtasks 分發,也是實際驗證過可行的方法。

rem 在驗證機上匯出已建立好的工作
schtasks /Query /TN "KS-Cleanup-Logs" /XML > KS-Cleanup-Logs.xml

rem 在各台機器匯入(執行帳戶與密碼在註冊時指定)
schtasks /Create /TN "KS-Cleanup-Logs" /XML KS-Cleanup-Logs.xml /RU DOMAIN\svc-batch /RP *

XML 裡包含了觸發程序、條件、設定的所有內容,只要放進版本庫,就能對工作定義做審查與差異管理。反過來說,靠人工在 10 台機器上用 GUI 分別註冊同一個工作,一定會產生「只有一台設定不一樣」的落單工作。建議在台數還沒到兩位數之前,就先把它變成程式碼。

另外,本文的內容以 Windows 10 / 11、Windows Server 2016 之後的工作排程器(Task Scheduler 2.0 系列)為前提。如果環境中還殘留著更古早、源自 at 命令的工作,請先從盤點它們開始。

3. 執行帳戶與登入類型 ── 最容易出事的地方

在工作內容頁面中選擇的「僅在使用者登入時執行」「不論使用者是否登入均執行」,在內部其實對應的是登入類型(LogonType)的選擇。如果對這裡的理解有落差,就會在「手動可以動、定期執行卻不動」這類問題上,說不出真正原因而一直繞圈子。1

3.1 三種模式的差異

選項 內部機制 特色・限制
僅在使用者登入時執行 互動式權杖(InteractiveToken) 登入期間畫面上可看到視窗。登出期間則根本不會啟動
不論使用者是否登入均執行 密碼儲存(Password) 註冊時會儲存密碼。畫面看不到(非互動)。密碼變更後會導致啟動失敗
同上+勾選「不儲存密碼」 S4U 不儲存密碼,但相對地無法存取網路上的資源,也無法存取加密檔案(EFS)1

實務上常見的典型事故如下。

  • 用「不儲存密碼」(S4U)註冊一支存取共用資料夾的腳本。本機測試時可以動,但正式環境中偏偏只有存取共用資料夾這一步會失敗。→ 原因是 S4U 沒有網路認證資訊。
  • 用「不論使用者是否登入均執行」註冊後過了幾個月,網域密碼到期而變更了密碼。此後工作就一直卡在登入失敗(0x8007052E),卻沒有任何人發現。
  • 用「不論登入均執行」註冊一支啟動 GUI 應用程式的工作。應用程式其實已經啟動,但畫面完全不會顯示在任何地方,因而被誤以為「沒有在動」。→ 原因是它是在非互動式工作階段中執行。需要互動畫面的處理,原則上在這種設定下是無法執行的。

另外,以 Password / S4U 執行的帳戶,需要具備「以批次工作登入」的權利(SeBatchLogonRight)。Administrators 群組預設就有這項權利,但如果打算把一般使用者帳戶當作服務帳戶使用,就要另外確認本機安全性原則的設定。6

3.2 該用哪個帳戶執行

  • SYSTEM:不需要管理密碼,權限強大,但也正因為權限太強而要小心。用在僅限本機的維護處理上很方便,但不應該把所有會碰觸業務資料的工作都交給 SYSTEM 執行。關於是否真的需要系統管理員權限的思考方式,已在另一篇文章「Windows 應用程式在什麼情況下需要系統管理員權限」中整理過。
  • 專用的服務帳戶(一般使用者):可以做到最小權限,但相對地每次密碼變更時都需要更新工作設定。請把密碼有效期限和工作盤點綁在一起管理。
  • gMSA(群組受管理服務帳戶):在網域環境中是首選。因為密碼由網域控制站自動管理,「密碼變更導致工作失效」這個問題本身就不存在了。工作排程器支援以 gMSA 執行工作。4

另外,勾選「以最高權限執行」的意思是:會使用 UAC 分割出的兩個權杖中、管理員一側(已提升權限)的權杖來執行。不需要管理員權限的工作,請不要勾選這個選項。

4. 排查「不執行」問題的步驟

4.1 先啟用歷程記錄,再開始懷疑

工作排程器右側窗格中的「啟用所有工作歷程記錄」,預設是停用的。如果歷程記錄一直保持停用,連「執行失敗」這個事實本身都不會留下任何記錄。上線前務必先啟用,並讓自己能同時查看事件記錄檔(事件檢視器 → 應用程式及服務記錄檔 → Microsoft → Windows → TaskScheduler → Operational)來確認狀況。2

排查的基本步驟,直接照著 Microsoft 疑難排解指南的流程走就已經很夠用。2

  1. 先單獨測試腳本 ── 在放進工作之前,先在與執行帳戶相同的條件下(可能的話用 runas 或驗證機)確認腳本本身能夠順利跑完。
  2. 查看狀態欄與歷程記錄分頁 ── 區分究竟是根本沒被觸發,還是啟動了卻失敗。如果沒有被觸發,就要懷疑觸發程序設定或條件(電源、網路),並用手動執行(右鍵 → 執行)確認動作本身能不能跑。
  3. 暫時改成「僅在使用者登入時執行」試試看 ── 如果這樣就能動,就可以把原因鎖定在非互動式工作階段或認證資訊(前一章)上。

4.2 如何解讀「上次執行結果」

顯示值 意義
0x0 正常結束(啟動的程式傳回結束代碼 0)
0x1 啟動的程式傳回結束代碼 1(並非工作排程器本身的錯誤)
0x41300 正在等待下次排定的執行(SCHED_S_TASK_READY)
0x41301 目前正在執行中(SCHED_S_TASK_RUNNING)
0x41303 尚未執行過一次(SCHED_S_TASK_HAS_NOT_RUN)
0x8007010B 起始位置(「起始位置(選用)」)的指定不正確。加了引號時的典型症狀
0x8007052E 登入失敗。可能是已儲存的密碼過期、權利不足等

0x413xx 系列是工作排程器自身的狀態代碼,0x8007xxxx 是 Windows 的錯誤代碼,而像 0x10x2 這種小數值,則是被啟動程式自身的結束代碼。3 只要能分清楚這幾種,就不會從一開始就選錯調查的方向(是工作設定,還是腳本本身)。

4.3 留意條件與設定的預設值

  • 電源條件:預設會啟用「僅在電腦使用 AC 電源時啟動工作」。如果用筆電當驗證機,就會變成只有在電池供電時才不動、變成一個「無法重現的問題」。而且從 Windows 10 開始,電池節省模式啟用期間,許多工作的觸發程序都會被延遲。7
  • 錯過開始時刻時:如果電腦當時處於關機狀態、錯過了開始時刻,預設要等到下一次排定時間才會執行。要明確地啟用「如果錯過排定時間,則立即執行工作」(-StartWhenAvailable),或是先在設計上決定這是不是一個「可以被跳過也無妨」的工作。5
  • 喚醒:如果是在夜間執行、電腦平時會進入睡眠的環境,也要決定是否需要啟用「喚醒電腦來執行此工作」(-WakeToRun)。

4.4 觸發程序設計本身的注意事項

有時候「以為不執行」,其實是觸發程序的設計本身就和原本的意圖不一致。

  • 「每月 31 日」在沒有 31 日的月份就不會執行。 如果是月底處理,比較安全的做法是改成「每月最後一天」的設計意圖(例如在月初處理上個月的資料,或是在腳本內自行判斷日期)。
  • 時刻是註冊該工作的那台機器的本地時間。 如果把同一份 XML 部署到海外據點的機器,或極少數以 UTC 設定運作的伺服器上,各據點的實際執行時刻就會不一致。請事先以規格明確定義「所有據點都在日本時間早上 6 點」還是「各據點當地時間早上 6 點」。
  • 如果開始用工作排程器做短間隔的重複執行(例如每 5 分鐘),就要留意。 它作為「一天一次的批次」工具非常優秀,但如果開始需要分鐘等級的輪詢或常駐監控,那就已經進入常駐處理程序的範疇了(後述第 8 章)。
  • 事件觸發程序很強大,但要先確認目標事件是否真的能穩定被記錄下來。 以應用程式記錄檔中特定事件識別碼作為觸發程序的架構,一旦應用程式更新導致事件的出現方式改變,就會在毫無預警的情況下失效。改用時刻觸發程序+在腳本內自行做條件判斷,結果上往往反而更容易追蹤。

5. 以 0x1 結束的典型模式與 PowerShell 的正確呼叫方式

0x1 只是「腳本失敗了」這個結果本身,原因其實在於腳本執行環境的差異。手動執行與定期執行之間,主要有以下幾點不同。

  • 目前的工作目錄不同:如果沒有指定「起始位置(選用)」,就會在 C:\Windows\System32 之類的目錄下執行。用相對路徑寫的腳本會在這裡出問題。腳本內部要以 $PSScriptRoot 為基準組出路徑,工作那一側則在「起始位置(選用)」欄位指定工作資料夾。這時候「起始位置(選用)」欄位不能加引號,即使路徑中含有空格,也要不加引號地寫(加了就會以 0x8007010B 失敗)。
  • 環境變數・設定檔不同:登入腳本或使用者設定檔所設定的環境變數、已對應的網路磁碟機(X: 等),請視為在非互動式工作階段中並不存在。應該直接使用 UNC 路徑(\\server\share\...),並用 -NoProfile 排除設定檔造成的差異。
  • 執行原則不同:即使已經為使用者設定了 RemoteSigned,服務帳戶那邊也可能完全沒有設定。要在工作的引數中明確加上 -ExecutionPolicy Bypass
  • 工具本身的結束代碼規則特殊:例如 robocopy 在「有檔案需要複製並成功複製」的情況下會傳回 1。如果用直接回傳結束代碼的包裝腳本,就會出現「明明正常卻看起來像 0x1」,或反過來的情況。使用的外部命令,其結束代碼規則請務必事先確認。

PowerShell 呼叫的基本形式如下。

程式或腳本:            pwsh.exe          (若為 Windows PowerShell 則為 powershell.exe)
新增引數:             -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"
起始位置 (選用):        C:\Jobs           (不加引號)

使用 -File 而不是 -Command,除了引數轉義比較單純之外,還因為腳本裡的 exit n 會直接變成處理程序的結束代碼,才能從工作排程器的「上次執行結果」判斷成敗。腳本內部也要設計成:成功時明確傳回 0,失敗時明確傳回非 0。

# 為了讓 Cmdlet 的「非終止錯誤」也能被 catch 捕捉到,設定為 Stop。
# 如果不設這個,Copy-Item 等命令失敗時可能會被直接放過,結果變成 exit 0
$ErrorActionPreference = 'Stop'

try {
    Main
    exit 0
}
catch {
    Write-Error $_
    exit 1
}

關於該在哪裡捕捉例外、又該如何記錄的思考方式,也已在另一篇文章「例外的捕捉與日誌設計」中詳細說明過。

6. 日誌要自己留存

工作排程器的歷程記錄,最多只能告訴你「有沒有啟動、結束代碼是多少」。至於腳本實際做了什麼、做到哪一步,就必須由腳本自己留下日誌記錄。

最基本的做法,不是在工作的引數欄裡做重新導向(因為工作排程器的「引數」欄並不會透過殼層執行,重新導向語法不會生效),而是在腳本內部直接取得逐字稿記錄,這樣比較省事。

$logDir = 'C:\Jobs\logs'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
Start-Transcript -Path (Join-Path $logDir ("cleanup-{0:yyyyMMdd}.log" -f (Get-Date))) -Append
try {
    # 主要處理
}
finally {
    Stop-Transcript
}

至於日誌本身持續累積的問題(世代管理、封存),正好可以直接沿用「PowerShell 腳本應用 ── 安全地自動化日誌調查、封存與報表化」一文中的內容。

若想再進一步,也可以考慮寫入 Windows 事件記錄檔。與檔案日誌不同的優點是,成敗結果會直接送到維運人員原本就已經在看的地方(事件檢視器、既有的監控工具)。

# --- 只在安裝時執行一次,且需要系統管理員權限(安裝程式或初始化腳本)---
if (-not [System.Diagnostics.EventLog]::SourceExists('KS-Jobs')) {
    [System.Diagnostics.EventLog]::CreateEventSource('KS-Jobs', 'Application')
}

# --- 工作主體(以最小權限帳戶執行)只負責寫入。
#     SourceExists 在搜尋所有記錄檔時可能需要系統管理員權限,執行時不要呼叫它。
#     以下假設是在 Main 的失敗處理器(catch)內呼叫 ---
catch {
    $err = $_
    try {
        [System.Diagnostics.EventLog]::WriteEntry('KS-Jobs',
            "Cleanup-Logs failed: $($err.Exception.Message)",
            [System.Diagnostics.EventLogEntryType]::Error, 1001)
    }
    catch {
        # 不能因為寫不進事件記錄檔,就把原始的失敗吞掉不管
        Write-Warning "寫入事件記錄檔失敗: $_"
    }
    exit 1
}

這裡補充兩點。首先,事件來源的註冊(CreateEventSource)需要系統管理員權限。如果把註冊處理混進工作主體,正式環境中以最小權限服務帳戶執行、第一次失敗時就會變成「想註冊卻先丟出例外 → 結果連真正重要的事件都寫不進去」的雙重失敗。請像上面那樣把註冊獨立到安裝階段處理,執行時只負責寫入。其次,如果像本文的工作註冊範例一樣把執行引擎設為 pwsh.exe,就無法使用 Windows PowerShell 5.1 時代的 New-EventLog / Write-EventLog Cmdlet(會因為找不到命令而讓整支腳本失敗)。像上面那樣直接呼叫 .NET 類別,則不論 5.1 或 7 都能運作。

在這之上,再加入一套「失敗時會通知到人」的機制──例如寄送 Email 或發送 Teams / Slack 通知──就能避免「盤點時才發現已經停止好幾個月」這類事故。不需要多複雜的通知架構,光是在失敗時 POST 到 Webhook 的幾行程式碼就已經很夠用。反過來說,「每次成功都通知」遲早會沒人看,因此建議把成功通知控制在週報等級即可,並把「失敗」與「沒有執行」(上次執行時間過舊)當作偵測目標。至於日誌該寫些什麼,判斷標準也已在「例外的捕捉與日誌設計」中整理過。

7. 多重啟動與長時間執行的控制

如果上一次執行還沒結束,下一個排定時刻就到了,會發生什麼事?這由「設定」分頁中的「工作已在執行時所套用的規則」決定,對應到 PowerShell 中的 -MultipleInstances5

設定值 動作 適合的用途
IgnoreNew(GUI 預設:不啟動新的執行例項) 執行中則跳過新的啟動 具冪等性的一般定期批次。優先選這個
Queue 執行中則在結束後依序執行 不容許漏算的彙總類工作
Parallel 並行啟動 原則上應避免。只有能保證並行安全時才用

同時建議把「停止之前的時間」(-ExecutionTimeLimit,預設 3 天)設成符合現實的數值(大約是預估執行時間的 2〜3 倍),可以避免處理程序卡死之後把隔天的工作也一起拖垮。5

需要注意的是,IgnoreNewQueue 保護的範圍,僅限於同一個工作定義之內。如果是另一個工作呼叫了同一支腳本,或是人員在故障處理時手動執行造成的衝突,這些設定是防不了的。如果有多條路徑會碰觸同一份資源(檔案、資料庫、外部系統),就要在腳本這一側也加上互斥機制。慣用做法是使用具名的 Mutex。

$mutex = New-Object System.Threading.Mutex($false, 'Global\KS-Cleanup-Logs')
if (-not $mutex.WaitOne(0)) {
    Write-Warning '因為有其他執行例項正在執行,所以結束。'
    exit 0   # 如果「沒有執行」不算失敗就用 0,若要當成失敗則用非 0
}
try {
    # 主要處理
}
finally {
    $mutex.ReleaseMutex()
}

只要在名稱前面加上 Global\,即使是在不同的工作階段之間(例如不同使用者的工作與手動執行),互斥也能生效。是要等待取得鎖(在 WaitOne 傳入逾時時間),還是要立刻放棄,請依工作的性質決定。另外要注意,Global\ 開頭的具名物件在整台機器上任何人都看得到。在有多個使用者登入的共用伺服器上,如果因惡意或意外先搶到了同名的 Mutex,工作就會永遠被跳過(而且因為是 exit 0,看起來還會像是正常結束)。在這類環境中,建議為 Mutex 設定 ACL(MutexSecurity)以限制能取得它的帳戶,或至少把「因取得失敗而跳過」這件事,一併送進前一節提到的通知與事件記錄檔中,讓監控能偵測到連續被跳過的情況。透過檔案進行連動時的互斥控制,已在「檔案整合的互斥控制基礎知識」中詳細說明過。

8. 工作排程器的「該收手的時機」 ── 與常駐服務的取捨

工作排程器並不是萬能的。當需求逐漸成長時,有一條界線是與其硬撐著繼續用,不如換一套機制反而更好。

需求 適合的機制
一天數次以內的定時批次 工作排程器
啟動時機混雜了人・事件・時刻,且想呈現整個流程 Power Automate(另一篇文章
以分鐘等級輪詢、常時監控、佇列處理 Windows 服務/常駐處理程序
需要在多次處理之間保留狀態、想細緻控制重試與退避策略 Windows 服務/常駐處理程序

一旦開始用「每 5 分鐘一次的工作」來做輪詢,每次啟動都要付出建立處理程序、載入模組的成本,還得額外做一套把前一次狀態存到檔案之類的機制,實質上等於是把常駐處理程序拆成一小段一小段重新實作。走到這個階段,直接改用 .NET 的 Generic Host 與 BackgroundService 做成常駐服務會比較乾脆。實作模式已在「在桌面應用程式中使用 Generic Host 與 BackgroundService」中說明過。

反過來說,把月結、日結這種批次特意改成服務、還自己管理計時器,也是一種過度設計。大致的判斷原則是:只要符合「執行間隔在一小時以上・處理彼此獨立・不需保留狀態」,就用工作排程器;一旦開始不符合這些條件,就該考慮改成常駐化,這樣的界線大致不會出錯。

9. 上線前的檢查清單

建議在註冊工作之前,依序逐一確認以下項目。

  • 是否已決定執行帳戶(是不是隨手選了 SYSTEM?如果是網域環境,有沒有考慮過 gMSA?)
  • 是否理解登入類型的限制(S4U 無法存取網路;若使用 Password,是否已決定好密碼變更時的維運方式)
  • 是否已在等同於執行帳戶的條件下單獨測試過腳本
  • 是否以 -NoProfile -NonInteractive -ExecutionPolicy Bypass -File 的形式呼叫
  • 腳本內的路徑是否以 $PSScriptRoot / UNC 為基準(是否還依賴對應磁碟機或相對路徑)
  • 「起始位置(選用)」欄位是否沒有加引號
  • 是否已設計結束代碼(成功為 0/失敗為非 0,且已確認外部命令的結束代碼規則)
  • 是否已啟用工作歷程記錄?是否已有腳本自身的日誌與失敗通知
  • 是否已明確設定電源條件・StartWhenAvailable・多重啟動・執行時間限制
  • 工作定義是否已用 XML 或 PowerShell 腳本的形式存進版本庫

10. 總結

工作排程器並不是「寫完腳本就結束」,而是要先把執行帳戶、工作階段、預設值這三個前提都設計好,才會真正進入穩定維運的狀態。反過來說,只要在註冊時把本文提到的重點──登入類型的選擇、啟用歷程記錄、結束代碼與日誌的設計、明確指定多重啟動與時間限制──一次確實掌握好,之後就會出乎意料地不太需要再費心。

「手動可以動、但定期執行卻不動」,幾乎可以確定原因就在工作階段與環境的差異上。在盲目調整設定之前,請先在歷程記錄分頁確認執行到了哪一步,並依序試試本文提到的排查步驟。

相關文章

相關諮詢領域

小村軟體有限公司承接以 PowerShell、工作排程器為基礎的業務自動化設計審查,以及協助重建那些「雖然還在動、但已經沒人能修」的定期工作。

參考連結

  1. Microsoft Learn,logonType Simple Type (Task Scheduler)。登入類型的定義。關於 S4U 不儲存密碼、但相對地無法存取網路及加密檔案。  2 3

  2. Microsoft Learn,Troubleshoot issues with scheduled tasks not running。「先單獨測試腳本 → 確認狀態與歷程記錄 → 變更安全性選項」的排查步驟,以及 TaskScheduler Operational 事件記錄檔的所在位置。  2 3

  3. Microsoft Learn,Task Scheduler error and success constants。SCHED_S_TASK_READY (0x41300)、SCHED_S_TASK_RUNNING (0x41301)、SCHED_S_TASK_HAS_NOT_RUN (0x41303) 等狀態與錯誤代碼的定義。  2

  4. Microsoft Learn,Manage group Managed Service Accounts。gMSA 的密碼可由網域端自動管理,以及工作排程器支援以 gMSA 執行工作。  2

  5. Microsoft Learn,New-ScheduledTaskSettingsSet。工作設定物件的參數,包括 MultipleInstances(Parallel/Queue/IgnoreNew)、StartWhenAvailable、ExecutionTimeLimit(預設 3 天)等。  2 3 4 5

  6. Microsoft Learn,Security Contexts for Tasks。工作的安全性內容,以及以 Password / S4U 註冊的工作在執行時需要「以批次工作登入」權利。 

  7. Microsoft Learn,What’s New in Task Scheduler。關於從 Windows 10 開始,電池節省模式啟用期間,非互動式工作的觸發程序會被延遲。 

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

工作排程器的「上次執行結果」顯示的 0x1 是什麼意思?
0x1 並不是工作排程器本身的錯誤,而是代表所啟動的程式傳回了結束代碼 1。原因在腳本那一側。手動執行與定期執行之間,典型的差異在於目前的工作目錄、環境變數與設定檔、執行原則不同。另外也要留意像 robocopy 這類即使正常結束也會傳回 1 的外部命令結束代碼規則。只要知道 0x413xx 系列是工作排程器的狀態代碼、0x8007xxxx 是 Windows 的錯誤代碼,就不會選錯調查的方向。
手動執行明明可以動,為什麼放進工作排程器就不動了?
幾乎可以確定原因就在執行帳戶與工作階段・環境的差異上。選擇「不論使用者是否登入均執行」,就會在非互動式工作階段中執行,因此對應的網路磁碟機或使用者設定檔的環境變數都不存在。而且「不儲存密碼」(S4U)也無法存取網路上的資源。有效的排查方式是:先確認歷程記錄分頁、單獨測試腳本、暫時改成「僅在使用者登入時執行」。由於工作歷程記錄預設是停用的,正式上線前請務必先啟用。
在工作排程器中呼叫 PowerShell 腳本的正確寫法是什麼?
基本做法是在「程式」指定 pwsh.exe(或 powershell.exe),引數則寫成 -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "完整路徑" 的形式。使用 -File 而不是 -Command,腳本裡的 exit n 就會直接變成處理程序的結束代碼,用來判斷成敗。腳本內部的路徑要以 $PSScriptRoot 為基準,「起始位置(選用)」欄位絕對不能加引號(加了會以 0x8007010B 失敗)。腳本那一側則要設計成:成功時明確傳回 0,失敗時明確傳回非 0。
該如何防止因密碼變更而導致工作停止的事故?
以「不論使用者是否登入均執行」註冊時會儲存密碼,因此密碼變更之後,工作就會一直卡在登入失敗(0x8007052E)。如果是網域環境,首選是密碼由網域控制站自動管理的 gMSA(群組受管理服務帳戶),這個問題本身就會消失。如果使用專用的服務帳戶,請把密碼有效期限與工作盤點綁在一起管理。同時再加入一套失敗時會寄送 Email 或發送 Teams 通知的機制,就能避免發生「已經停止好幾個月卻沒人發現」的事故。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽