PowershellWindows

タスクスケジューラで組んだ処理が動かないときの切り分け【成功と出るのに動いていない】

スポンサーラベル
タスクスケジューラで処理が動かないときの3つの原因。作業フォルダー未指定でカレントがSystem32になる、スクリプトが失敗しても結果は0x0になる、履歴ログが既定で無効 Powershell

当サイトはアフィリエイト広告を利用しています。

バックアップスクリプトやログの収集を、タスクスケジューラで毎晩動かす。よくある作り方です。

ところが翌朝ファイルができていません。タスクスケジューラを開くと、「前回の実行結果」は 0x0(成功)。手動で実行すれば動きます。この状態がいちばん厄介です。

タスクスケジューラで処理が動かないときの3つの原因。作業フォルダー未指定でカレントがSystem32になる、スクリプトが失敗しても結果は0x0になる、履歴ログが既定で無効

原因のほとんどは3つに絞れます。実際にタスクを登録して動かし、結果を確かめました。検証環境は Windows 11 Pro・Windows PowerShell 5.1 です。

1. 結論:疑うのは作業フォルダー・終了コード・履歴の3つ

先に要点だけ挙げます。

  • 作業フォルダーを指定しないと、カレントディレクトリは C:\Windows\System32 になる
  • スクリプトが中で失敗しても、タスクの結果は 0x0(成功)になることがある
  • タスクスケジューラの履歴は既定で無効。何が起きたか後から追えない

順に確かめていきます。

2. 作業フォルダーを指定しないと System32 で動く

タスクの「操作」タブに、「開始(オプション)」という目立たない入力欄があります。ここが作業フォルダー(カレントディレクトリ)の指定です。

空欄のまま登録するとどうなるか。同じスクリプトを、作業フォルダーの指定あり・なしで登録して比べました。スクリプトの中身は「カレントディレクトリを記録して、相対パスでファイルを書く」だけです。

"cwd=$($PWD.Path)" | Out-File -FilePath ".\out.txt" -Encoding utf8 -Append

結果です。

登録の仕方実行時のカレントディレクトリ相対パスへの書き込み
作業フォルダーを指定スクリプトを置いたフォルダ成功
指定なしC:\Windows\System32失敗(アクセスが拒否されました)

指定しないとカレントは C:\Windows\System32 になります。 スクリプトを置いた場所ではありません。

手動実行では動くのに、タスクからだと動かない理由がこれです。エクスプローラーやPowerShellから叩けばカレントはスクリプトの場所ですが、タスクスケジューラから起動されたプロセスのカレントは別物になります。

対処は2つあります。

(A)作業フォルダーを指定する。 GUI なら「開始(オプション)」にスクリプトのあるフォルダを入れます。ここにはダブルクォートを付けません(付けると動かないことがあります)。

PowerShell から登録するなら -WorkingDirectory です。

$action = New-ScheduledTaskAction `
    -Execute 'powershell.exe' `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "D:\scripts\backup.ps1"' `
    -WorkingDirectory 'D:\scripts'

(B)スクリプト側で相対パスを使わない。 こちらのほうが確実です。スクリプトの先頭で自分の場所へ移動してしまいます。

# スクリプト自身のあるフォルダへ移動する。タスクから起動されても場所が決まる
Set-Location -Path $PSScriptRoot

(B)を入れておけば、登録の仕方に依存しなくなります。

3. 失敗しても「成功(0x0)」になる

ここが本題です。先ほどの検証で、作業フォルダーを指定しなかったタスクは相対パスへの書き込みに失敗していました。ファイルは1つもできていません。

にもかかわらず、タスクの結果はこうでした。

TaskName    LastTaskResult
--------    --------------
NoWorkDir                0     ← 書き込みに失敗しているのに 0(成功)
WithWorkDir              0

理由は、タスクスケジューラが見ているのは起動した実行ファイルの終了コードだけだからです。この例では powershell.exe が起動し、スクリプトを最後まで流し、終了コード0で終わりました。中で Out-File が失敗したかどうかは、タスクスケジューラの関知するところではありません。

PowerShell の既定では、コマンドのエラーは処理を止めません(非終了エラー)。エラーメッセージを出して次の行へ進み、最後まで到達して正常終了します。「動いていないのに成功と出る」のはこの組み合わせです。

失敗を失敗として返す

スクリプト側で終了コードを立てます。

Set-Location -Path $PSScriptRoot
$ErrorActionPreference = 'Stop'      # エラーで止める

try {
    # 本来の処理
    Copy-Item .\data\*.csv -Destination \\server\share\backup\ -ErrorAction Stop
}
catch {
    # ログに残してから、失敗を終了コードで伝える
    "$(Get-Date -Format s) ERROR: $($_.Exception.Message)" |
        Out-File -FilePath "$PSScriptRoot\error.log" -Encoding utf8 -Append
    exit 1
}
exit 0

$ErrorActionPreference = 'Stop'try/catchexit の3点セットです。これを入れていないスクリプトは、失敗しても永遠に 0x0 を返し続けます。

実際に終了コードがどう伝わるかも確かめました。

スクリプトの終わり方タスクの前回の実行結果
正常終了0x00000000
throw で終了0x00000001
-ErrorAction Stop のエラーで終了0x00000001
exit 30x00000003

スクリプトで exit した値はそのまま出ます。 処理の段階ごとに違う番号を返すようにしておくと、どこで落ちたかが結果コードだけで分かります。

4. 結果コードの読み方

「前回の実行結果」に出る値のうち、実務でよく見るものを挙げます。

コード意味見るべきところ
0x00000000正常終了中で失敗している可能性がある(3章)
0x00000001スクリプトがエラーで終了スクリプト側のログ
0x00041300タスクは実行準備完了
0x00041301現在実行中前回が終わっていない。多重起動と長時間実行を疑う
0x00041303まだ一度も実行されていないトリガーの条件。実行時刻・有効/無効
0x80070002指定したファイルが見つかりません「プログラム/スクリプト」のパス
0x80070005アクセスが拒否されました実行ユーザーの権限、「最上位の特権で実行する」

0x00041303 のタスクは、「前回の実行時刻」が 1999/11/30 0:00:00 と表示されます。日付がおかしいのではなく、一度も実行されていないことを示す既定値です。ここを見たら、トリガーの設定を疑ってください。

0x800700020x8007 + 0002 で、Win32 のエラーコード2番(ファイルが見つかりません)です。実行ファイルのパスの打ち間違い、あるいはスクリプトはあるのに実行ファイル側のパスが違うときに出ます。

5. 履歴は既定で無効になっている

「いつ動いて何が起きたか」を追いたくなります。タスクスケジューラには履歴タブがありますが、中身が空のことがほとんどです

確認してみます。

Get-WinEvent -ListLog 'Microsoft-Windows-TaskScheduler/Operational' |
    Select-Object LogName, IsEnabled, RecordCount

検証機での結果です。

LogName                                     IsEnabled RecordCount
-------                                     --------- -----------
Microsoft-Windows-TaskScheduler/Operational     False

IsEnabledFalse。記録していません。 これは壊れているのではなく既定値です。

有効にするには、タスクスケジューラの右ペインにある「すべてのタスク履歴を有効にする」をクリックします(管理者権限が必要です)。有効にすると、以後の実行が Microsoft-Windows-TaskScheduler/Operational に記録されるようになります。

記録が貯まれば PowerShell から追えます。

# 特定のタスクの動きだけを時系列で見る
Get-WinEvent -FilterHashtable @{
    LogName   = 'Microsoft-Windows-TaskScheduler/Operational'
    StartTime = (Get-Date).AddDays(-3)
} -ErrorAction SilentlyContinue |
    Where-Object { $_.Message -like '*BackupJob*' } |
    Select-Object TimeCreated, Id, Message

イベントログの絞り込みは書き方で速度が桁違いに変わります。詳しくはWindowsのイベントログを絞り込むにまとめました。

ただし、履歴を有効にしても「スクリプトの中で何が起きたか」は残りません。 タスクが起動したこと・終了したこと・結果コードが記録されるだけです。中の記録は3章のようにスクリプト側で書く必要があります。ログ出力の型はPowershellでLogを出力するロガーClassを自作が使えます。

6. PowerShellスクリプトを登録するときの型

ここまでを踏まえた登録方法です。GUI でいうと次のようになります。

入れる値
プログラム/スクリプトpowershell.exe
引数の追加-NoProfile -ExecutionPolicy Bypass -File "D:\scripts\backup.ps1"
開始(オプション)D:\scripts

.ps1 を「プログラム/スクリプト」に直接書かないでください。 拡張子の関連付けに依存し、環境によってメモ帳で開いて終わります。必ず powershell.exe を呼び、-File でスクリプトを渡します。

引数の各オプションの意味です。

  • -NoProfile — プロファイルを読まない。実行ユーザーのプロファイルに依存しなくなるので必ず付けます
  • -ExecutionPolicy Bypass — 実行ポリシーで止まるのを防ぐ。検証機は RemoteSigned でローカルのスクリプトは動きましたが、ポリシーは組織で配られることが多いので明示しておくのが安全です
  • -File — スクリプトのパス。スペースを含むならダブルクォートで囲む

PowerShell から登録する場合はこうなります。

$action = New-ScheduledTaskAction `
    -Execute 'powershell.exe' `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "D:\scripts\backup.ps1"' `
    -WorkingDirectory 'D:\scripts'

$trigger = New-ScheduledTaskTrigger -Daily -At 2:00am

Register-ScheduledTask -TaskName 'BackupJob' -TaskPath '\MyJobs\' `
    -Action $action -Trigger $trigger -Description '毎晩2時にCSVを退避する'

登録したタスクの設定を確認するときは、GUI を開くより schtasks のほうが速いです。

schtasks /query /tn "\MyJobs\BackupJob" /fo LIST /v

なお ログオンしていない状態で動かすなら、タスクのプロパティで「ユーザーがログオンしているかどうかにかかわらず実行する」を選びます。この場合、ネットワーク共有へのアクセスが通らなくなることがあります。資格情報の扱いが変わるためで、共有へ書き出す処理では UNC パスと実行ユーザーの権限を必ず確認してください。

7. まとめ

タスクスケジューラで処理が動かないときの切り分けです。

  • まず作業フォルダーを疑う。 未指定だとカレントは C:\Windows\System32 になり、相対パスが全部外れる
  • 0x0 は「成功」を意味しない。 タスクが見ているのは powershell.exe の終了コードだけ。スクリプト内部の失敗は伝わらない
  • スクリプト側に $ErrorActionPreference = 'Stop'try/catchexit を入れる。 これが無いと永遠に成功と出続ける
  • 0x00041303(未実行・前回実行時刻が1999年)はトリガーの問題0x80070002 はパスの問題
  • 履歴は既定で無効。 「すべてのタスク履歴を有効にする」を押すまで何も記録されない

手動実行では動くのにタスクからだと動かない、という症状の大半は2章と3章で説明が付きます。 まず Set-Location $PSScriptRoot を1行入れてから、切り分けを始めるのが早いです。

PowerShellの学習におすすめの書籍

PowerShell を体系的に学ぶなら、PowerShell Core 以降のクロスプラットフォーム対応をふまえた解説書が分かりやすいです。Windows の自動化にとどまらず、macOS や Linux でも動く現在の PowerShell を、基礎から実務での使い方まで通して押さえられます。

PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説

コメント

タイトルとURLをコピーしました