バックアップスクリプトやログの収集を、タスクスケジューラで毎晩動かす。よくある作り方です。
ところが翌朝ファイルができていません。タスクスケジューラを開くと、「前回の実行結果」は 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/catch と exit の3点セットです。これを入れていないスクリプトは、失敗しても永遠に 0x0 を返し続けます。
実際に終了コードがどう伝わるかも確かめました。
| スクリプトの終わり方 | タスクの前回の実行結果 |
|---|---|
| 正常終了 | 0x00000000 |
throw で終了 | 0x00000001 |
-ErrorAction Stop のエラーで終了 | 0x00000001 |
exit 3 | 0x00000003 |
スクリプトで exit した値はそのまま出ます。 処理の段階ごとに違う番号を返すようにしておくと、どこで落ちたかが結果コードだけで分かります。
4. 結果コードの読み方
「前回の実行結果」に出る値のうち、実務でよく見るものを挙げます。
| コード | 意味 | 見るべきところ |
|---|---|---|
0x00000000 | 正常終了 | 中で失敗している可能性がある(3章) |
0x00000001 | スクリプトがエラーで終了 | スクリプト側のログ |
0x00041300 | タスクは実行準備完了 | — |
0x00041301 | 現在実行中 | 前回が終わっていない。多重起動と長時間実行を疑う |
0x00041303 | まだ一度も実行されていない | トリガーの条件。実行時刻・有効/無効 |
0x80070002 | 指定したファイルが見つかりません | 「プログラム/スクリプト」のパス |
0x80070005 | アクセスが拒否されました | 実行ユーザーの権限、「最上位の特権で実行する」 |
0x00041303 のタスクは、「前回の実行時刻」が 1999/11/30 0:00:00 と表示されます。日付がおかしいのではなく、一度も実行されていないことを示す既定値です。ここを見たら、トリガーの設定を疑ってください。
0x80070002 は 0x8007 + 0002 で、Win32 のエラーコード2番(ファイルが見つかりません)です。実行ファイルのパスの打ち間違い、あるいはスクリプトはあるのに実行ファイル側のパスが違うときに出ます。
5. 履歴は既定で無効になっている
「いつ動いて何が起きたか」を追いたくなります。タスクスケジューラには履歴タブがありますが、中身が空のことがほとんどです。
確認してみます。
Get-WinEvent -ListLog 'Microsoft-Windows-TaskScheduler/Operational' |
Select-Object LogName, IsEnabled, RecordCount
検証機での結果です。
LogName IsEnabled RecordCount
------- --------- -----------
Microsoft-Windows-TaskScheduler/Operational False
IsEnabled が False。記録していません。 これは壊れているのではなく既定値です。
有効にするには、タスクスケジューラの右ペインにある「すべてのタスク履歴を有効にする」をクリックします(管理者権限が必要です)。有効にすると、以後の実行が 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/catchとexitを入れる。 これが無いと永遠に成功と出続ける 0x00041303(未実行・前回実行時刻が1999年)はトリガーの問題、0x80070002はパスの問題- 履歴は既定で無効。 「すべてのタスク履歴を有効にする」を押すまで何も記録されない
手動実行では動くのにタスクからだと動かない、という症状の大半は2章と3章で説明が付きます。 まず Set-Location $PSScriptRoot を1行入れてから、切り分けを始めるのが早いです。
PowerShellの学習におすすめの書籍
PowerShell を体系的に学ぶなら、PowerShell Core 以降のクロスプラットフォーム対応をふまえた解説書が分かりやすいです。Windows の自動化にとどまらず、macOS や Linux でも動く現在の PowerShell を、基礎から実務での使い方まで通して押さえられます。
PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説


コメント