この記事は Zenn にも同じ内容を載せています: https://zenn.dev/suisei498/articles/6b3370ab44a8be
自宅のWindows PCで、PowerShellのスクリプト(.ps1)をタスクスケジューラから毎日何本も動かしています。動画の自動投稿や、各種の監視です。
「手で実行すると動くのに、タスクにすると動かない」は、この数か月で何度も踏みました。検索すると原因の一覧はたくさん出てきますが、自分の場合に効いたのは、どこで止まったのかを先に切り分けることでした。
この記事は、実際に踏んだ3つの原因と、そのとき使った確認コマンドの記録です。環境は Windows 11 / Windows PowerShell 5.1 です。
最初に見るもの:止まった場所は3つのどれか
タスクが「動かない」と言っても、止まる場所は大きく3つに分かれます。
- そもそも起動していない(履歴に何も残らない・最終実行時刻が前日のまま)
- 起動したが、スクリプトの中ですぐ落ちた(最終結果が 0x1 など)
- 起動して正常終了したのに、仕事をしていない(最終結果は 0x0)
最初にこれを見ます。
Get-ScheduledTaskInfo -TaskName 'MyTask' | Select-Object LastRunTime, LastTaskResult, NextRunTime
LastRunTime が古いままなら 1、LastTaskResult が 0 以外なら 2、0 なのに成果物が無いなら 3 です。原因の候補がまったく違うので、ここを飛ばして設定をいじり始めると遠回りになります。
原因1:「ログオンしているときのみ実行する」と、再起動
一番被害が大きかったのがこれです。
ある朝、6時台のタスクは動いたのに、8時以降のタスクが1つも動いていませんでした。4本が全滅です。タスクスケジューラのサービスは正常で、次回の実行予定もすべて正しく入っていました。
原因は、タスクの設定が「ユーザーがログオンしているときのみ実行する」だったことです。この日は Windows Update で 7:47 に自動再起動していて、私がログオンしたのは 12:39 でした。その約5時間、誰もログオンしていなかったので、タスクは発火しませんでした。
手で試すときは当然ログオンしているので、手動では必ず動きます。「手動では動くのに」の典型だと思います。
確かめ方:起動時刻とログオン時刻を両方見る
# 最後に起動した時刻
(Get-CimInstance Win32_OperatingSystem).LastBootUpTime
# ログオンした時刻(2 = 対話ログオン、10 = リモートデスクトップ)
Get-CimInstance Win32_LogonSession |
Where-Object { $_.LogonType -in 2, 10 } |
Sort-Object StartTime | Select-Object StartTime, LogonType
起動時刻だけを見ると「PCは動いていたのに」と思ってしまいます。起動していても、ログオンしていなければ動きません。両方を並べて、その間に予定が入っていたタスクが飛んでいれば、これが原因です。
各タスクの設定はこれで一覧できます。
Get-ScheduledTask | Where-Object TaskPath -eq '\' |
Select-Object TaskName, @{n='LogonType'; e={$_.Principal.LogonType}}, State
Interactive が「ログオンしているときのみ」です。
「スケジュールされた時刻にタスクを開始できなかった場合、すぐにタスクを実行する」は助けてくれなかった
この設定(StartWhenAvailable)も付けていました。ところが、12:39 にログオンしたあと、取りこぼした4本がまとめて走ることはありませんでした。次の予定時刻が入り直しただけです。私の環境ではそうだった、という記録ですが、この設定を安全網として当てにするのはやめました。
直し方と、その代償
設定を「ユーザーがログオンしているかどうかにかかわらず実行する」(パスワードを保存しない場合は S4U)に変えれば、ログオンしていなくても動きます。
# 環境によっては管理者権限の PowerShell が要ります
$p = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Limited
Set-ScheduledTask -TaskName 'MyTask' -Principal $p
ただし、全部をこれに変えてはいけませんでした。S4U で動くタスクはデスクトップを持ちません。私の動画投稿の工程には、ブラウザを画面操作してサムネイルを入れる部分があり、そこが動かなくなります。GUIのアプリを操作するもの(取引ツールの画面など)も同じです。
なので、次のように分けました。
- 画面を使わないタスク(APIを叩くだけのもの)→ S4U に変更。別のPCで動かしている売買の判定は、切り替える前に必要な認証情報がS4Uでも読めることを確かめてから切り替え、次の定時実行で動いたことを確認しました
- 画面を使うタスク → 「ログオン中のみ」のまま。再起動のあとはログオンを忘れない、飛んだ回は手で回す
根本的には「画面を使う処理を持つPCは、常にログオンしている状態で運用する」しかない、というのが今のところの結論です。
原因2:BOM の無い UTF-8 で書いた .ps1(日本語入り)
2つ目は、起動はしたのに、すぐ落ちる型です。
Windows PowerShell 5.1 は、BOM の無い UTF-8 のスクリプトを、システムの文字コード(日本語環境なら Shift_JIS 系)として読みます。日本語のコメントや文字列が入っていると文字が化け、その影響で Missing closing '}' のような構文エラーになり、1行も実行されずに終わります。
エラーの内容が構文エラーなので、原因が文字コードだとは気づきにくいのが厄介な点です。タスクから動かすと画面にも出ないので、残るのは最終結果の 0x1 だけです。
私が踏んだのは、日本語のコメントの直後の行が壊れて、ログファイルのパスを入れた変数が空になり、そこで即終了するというものでした。ログが無いので、何が起きたのかも残りません。
「手動では動く」のはなぜか
手で試すときに、PowerShell 7(pwsh)やエディタのターミナルで実行していると、そちらは BOM の無い UTF-8 を正しく読むので動きます。一方、タスクに powershell.exe と書いていれば、動くのは 5.1 です。試した環境と、タスクが使う環境が違うわけです。
確かめ方と直し方
先頭の3バイトが 239 187 191 なら BOM 付きです。
[System.IO.File]::ReadAllBytes('C:\path\script.ps1')[0..2]
BOM を付け直すにはこうします。
$path = 'C:\path\script.ps1'
$c = Get-Content -Raw -Encoding UTF8 $path
[System.IO.File]::WriteAllText($path, $c, (New-Object System.Text.UTF8Encoding $true))
エディタによっては、保存のたびに BOM が外れます。編集したら毎回確かめるようにしています。
読み込む側でも同じことが起きる
スクリプト自体を直しても、スクリプトの中で UTF-8 のファイルを読むときに同じ問題が出ます。5.1 の Get-Content -Raw は、指定しなければ Shift_JIS 系として読みます。私の環境では、JSON から題名を読んでスマホに通知する処理で、通知が文字化けしました。
Get-Content .\meta.json -Raw -Encoding UTF8
書き出す側だけでなく、読み込む側にも -Encoding UTF8 を付ける必要があります。
原因3:同じ時間帯に、手で重い処理を回していた
3つ目は設定の問題ではありません。
ある夕方のタスクが、途中で 終了コード 0xC000013A を出して止まりました。プロセスが強制終了されたときのコードです。スクリプトにはエラー時に通知する仕組みを入れていましたが、プロセスごと落ちたので、その通知も動きませんでした。ログは途中で途切れていて、最後の行が「終了」に達していませんでした。
原因は、同じ時間帯に私が手で重い処理(音声合成・GPUを使う生成)を回していたことです。定時のタスクと干渉しました。クリーンな状態で同じタスクを再実行すると、何事もなく最後まで通りました。
「手動では動く」のは当たり前で、手動で試すときは他に何も動いていないからです。
いまは、定時タスクの時刻の一覧を見て、重い手作業はタスクが入っていない時間帯に回すことにしています。重い処理の前に「いまタスクが走っていないか」を確かめる小さなスクリプトも作りました。
おまけ:正常終了(0x0)なのに仕事をしていない
3つの分類の3番目、「正常に終わったのに何もしていない」は、また別の話です。ログを丁寧に書くプログラムほど、ログの更新時刻だけを見る監視からは健康に見える、という穴を踏みました。それはこちらに書いています。
→ プロセスは生きている。ログも更新されている。それでも仕事をしていなかった
まとめ:切り分けの順番
-
Get-ScheduledTaskInfoで、起動していないのか/すぐ落ちたのか/正常終了したのかを見る - 起動していない → 起動時刻とログオン時刻を両方見る。「ログオン中のみ」のタスクが、その間に予定されていなかったか
- すぐ落ちた → .ps1 の BOM と、手で試した PowerShell のバージョン(5.1 か 7 か)
- 途中で消えた(
0xC000013Aなど)→ 同じ時間帯に自分が何をしていたか
どれも、分かってしまえば一行で直るものばかりでした。時間がかかったのは、直すことではなく、どこで止まったのかを見ないまま設定を変えていたことの方です。