1. 手動なら動くのに、タスクだと動かない — 何が変わるのか
まず、画面の症状で現在地を確認してください。壊れているのはタスクスケジューラの設定ではなく、たいてい「実行される側の前提」です。
| 画面の表示 | 行き先(意味は 3 章の表に一本化) |
|---|---|
前回の実行結果が 0x1。ログも成果物も無い |
3.1 → 7 章 |
前回の実行結果が 0x2 / 0x80070002 |
3.2 |
| 状態が「実行中」のまま戻らない | 3.3 → 10 章 |
結果は 0x0(成功)なのに、出力ファイルがどこにも無い |
4 章 |
ログや CSV が文字化けする。手元では出ない UnicodeEncodeError で 0x1 になる |
8 章 |
「手動なら動く」のは、コマンドプロンプトがスクリプトに都合の良い状態を無意識に作っているからです。タスクスケジューラは、まっさらなプロセスを 1 個作るだけです。
この 4 つは「シェルが用意していたものが、無い」という 1 つの事実の 4 つの顔なので、対策も 1 か所にまとまります。
2. まず 5 分で通す — .bat ランチャー 1 枚の最小構成
先に動く状態を作ってから、原因の話に入ります。手順は 4 つです。自分のユーザーで動かす分には管理者権限は要りません。コマンドは schtasks 系がコマンドプロンプト(cmd)、Get- 系(3 章・9 章)がPowerShellです。
2.1 スクリプト側を「どこから呼ばれても平気」にする
例として C:\work\daily_report というフォルダを作り、下のコードを daily_report.py として保存します。以降の .bat とタスク登録もこの場所が前提です(フォルダ名は自由)。相対パスをやめ、自分のファイルの位置を基準にします。同じフォルダにメモ帳で config.ini を作り、name = daily_report の 1 行だけ書いて保存してください(2.4 の出力はこの状態のものです)。
from pathlib import Path
import sys
BASE = Path(__file__).resolve().parent # このスクリプトが置かれたフォルダ
print("start:", BASE)
cfg = BASE / "config.ini" # open("config.ini") と書かない
print("config:", cfg.read_text(encoding="utf-8").strip())
print("絵文字も書ける: 😀") # cp932 では書けない文字(8 章)
sys.exit(0) # 正常終了なら 0(省略しても 0)
Path(__file__) はスクリプトの位置であって、実行時のカレントディレクトリではありません。タスク側の設定に依存しなくなるのが利点です。
2.2 .bat ランチャーを 1 枚置く
スクリプトと同じフォルダに run.bat を置きます。冒頭の「先に結論」と同じものです。
@echo off
cd /d "%~dp0"
if not exist "%~dp0logs" mkdir "%~dp0logs"
set PY=C:\work\daily_report\.venv\Scripts\python.exe
"%PY%" -X utf8 daily_report.py >> "%~dp0logs\task.log" 2>&1
exit /b %ERRORLEVEL%
@echo off を除く 5 行の役割は順に、作業ディレクトリ(4 章)/logs の初回作成/venv(5 章)/文字コードとログ(8 章・7 章)/戻り値(3 章)です。set PY= は自分の環境のパスに書き換えてください。書き換えないまま動かすと、.bat は Python を起動できず、logs\task.log に「指定されたパスが見つかりません。」が残ります(8.3 の文字コードの話とは別です)。
.bat を作ったことがなければ、メモ帳で書き、「名前を付けて保存」で種類を「すべてのファイル」、文字コードを「ANSI」にして run.bat と入力します(run.bat.txt に注意・理由は 8.3)。ログは追記なので、ローテーションは 9 章、自動再起動つきの型は PyInstaller 配布の記事 7 章へ。
2.3 タスクに登録する
GUI なら「基本タスクの作成」→ 操作は「プログラムの開始」。実際に埋めるのは 1 欄だけです。
| GUI の欄 | 入れるもの | schtasks のオプション |
|---|---|---|
| プログラム/スクリプト | C:\work\daily_report\run.bat(絶対パス) | /TR |
| 引数の追加(オプション) | 空欄でよい(.bat 側で完結しているため) | /TR の続き |
| 開始(オプション) | 空欄でよい(cd /d "%~dp0" が担当) | /Create のオプションでは指定不可(XML 経由なら可・4.3) |
コマンドライン 1 行でも同じものが作れます。
schtasks /Create /TN "DailyReport" /TR "C:\work\daily_report\run.bat" /SC DAILY /ST 07:30 /F
⚠️ /F は同名タスクを確認なしで上書きするため、運用中のタスクと名前がぶつかればその定義は失われます。登録の前に schtasks /Query /TN "DailyReport" で同名の有無を確かめてください(無ければ「指定されたファイルが見つかりません」と出ます)。
2.4 待たずに動作確認する
予定時刻を待つ必要はありません。/Run で即実行し、戻り値を見ます。
schtasks /Run /TN "DailyReport"
schtasks /Query /TN "DailyReport" /FO LIST /V
検証機でこの構成を登録して実行した結果、戻り値は 0(0x0)、logs\task.log には次が記録されました。絵文字が化けずに残るのは -X utf8 が効いているためです(8 章)。
start: C:\work\daily_report
config: name = daily_report
絵文字も書ける: 😀
ここまでで動かない場合は、3 章の戻り値から原因を引いてください。
自分の環境で確かめるときにも使えます。os.getcwd()・sys.executable・sys.stdout.encoding を出すスクリプトを /SC ONCE で登録し、/Run のあと /Query /FO LIST /V を見ます。sys.exit(1) する版に差し替えれば、0x1 が自分のコードから来ていることも目で確認できます。試したタスクは schtasks /Delete /TN "タスク名" /F で必ず消します。/F は確認を出さないので、先に schtasks /Query /TN "タスク名" で対象を確かめてください(2.3 と同じ手順です)。
3. 「前回の実行結果」から原因を引く — 戻り値コード切り分け表
「前回の実行結果」は、タスクが起動したプログラムの終了コードをそのまま表示します。Microsoft の仕様書にもこうあります。
サーバーは、タスクの最後の実行の終了コードを返さなければならない(拙訳)
Microsoft Learn — MS-TSCH SchRpcGetLastRunInfo
つまり 0x1 は「タスクスケジューラが失敗した」ではなく「あなたのプログラムが 1 を返した」です。この前提で表を引いてください。
| 16 進 | schtasks の表示 | 意味 | 最初に見る場所 | 当サイトでの再現 |
|---|---|---|---|---|
0x0 | 0 | 正常終了 | ——(成果物が無いなら 4 章) | 再現 |
0x1 | 1 | プログラムが 1 を返した(未捕捉例外・sys.exit(1)) | スクリプトのログ(7 章)。パス系の例外なら 4 章 | 再現 |
0x2 | 2 | python.exe は起動した。.py を開けなかった/2 を返した | 引数に書いた .py のパス | 再現 |
0x80070002 | -2147024894 | 実行ファイル自体が見つからない(下位 16 ビットが 0x0002) | 「プログラム/スクリプト」欄 | 再現 |
0x41301 | 267009 | タスクは現在実行中(SCHED_S_TASK_RUNNING) | 前回の起動が終わっているか | 再現 |
0x41303 | 267011 | まだ一度も実行されていない(SCHED_S_TASK_HAS_NOT_RUN) | トリガーの条件(10 章) | 再現 |
0x41306 | 267014 | タスクが停止させられた(SCHED_S_TASK_TERMINATED) | 手動停止・実行時間の上限(10 章) | 再現 |
0x800710E0 | —— | Win32 エラー 4320「ERROR_REQUEST_REFUSED」の HRESULT 形式。原因との関連は当サイト未確認 | 条件タブ(10 章) | 未再現 |
0x41301 系の 3 つは Microsoft の定数一覧で SCHED_S_ 系(成功を表す HRESULT)と定義されています。S は success の S です。
画面(GUI)は 16 進、schtasks /Query /V は 10 進表示で、0x80070002 は符号付きの -2147024894。検索の前に 16 進へ戻すのが早道です。次のブロックは PowerShell で実行します(先頭の # は cmd ではコメントになりません)。
# 実行結果を 16 進で見る(タスク名を指定)
Get-ScheduledTaskInfo -TaskName "DailyReport" |
ForEach-Object { "0x{0:X}" -f $_.LastTaskResult }
3.1 0x1 が出たら、まず Python 側を疑う
0x1 は Win32 のエラー 1「ERROR_INVALID_FUNCTION」と同じ数字なので、その字面から調べると迷子になります。例外で終わるスクリプトをタスクに登録して実行した結果が次です(import sys は実測に使ったスクリプトのまま)。
# err.py — わざと例外で落とす
import sys
print("stdout: これから失敗します")
raise RuntimeError("設定ファイルが読めませんでした")
タスクの戻り値は 1(0x1)。2>&1 付きでログに落とすと、原因はログ側に全部残りました(実行結果)。
stdout: これから失敗します
Traceback (most recent call last):
File "C:\work\daily_report\err.py", line 3, in <module>
raise RuntimeError("設定ファイルが読めませんでした")
RuntimeError: 設定ファイルが読めませんでした
同じスクリプトを 2>&1 無しで動かすと、ログは 1 行目だけで Traceback は消えました。0x1 だけが手掛かりになる典型です。順序は「①ログを見る → ②無いならまず出す(7 章)→ ③それでもだめならタスク設定」。設定画面は最後です。
3.2 0x2 と 0x80070002 は別物
「0x2=ファイルが見つからない」と紹介されがちですが、検証機で切り分けると 2 つは別の場所で起きていました。
- 存在しない
.exeを「プログラム/スクリプト」欄に指定 →0x80070002(表示は-2147024894)。Python はまだ 1 行も動いていません python.exeは正しいが、引数の.pyが無い →0x2。Python は起動しており、ログには python 自身のメッセージが残りました
C:\Users\<ユーザー名>\AppData\Local\Programs\Python\Python312\python.exe: can't open file 'C:\\work\\daily_report\\nosuch.py': [Errno 2] No such file or directory
さらに sys.exit(2) するだけのスクリプトも 0x2 でした。つまり 0x2 は「あなたのコードの側の 2」です。0x80070002 は「Windows がプロセスを作れなかった 2」で、検証機では /TR "py スクリプト.py" も後者でした。プログラム欄には必ず絶対パスを。
3.3 0x41301 は「まだ終わっていない」
90 秒かかるスクリプトを実行中に照会すると、状態は「実行中」、前回の結果は 267009(0x41301)でした。この状態で /Run しても 情報: スケジュール タスク "..." は現在実行中です。 と出て 2 つ目は起動しません(既定の設定・10 章)。
0x41301 のまま固まっているなら、前日の処理が終わっていません。タスクマネージャーに python.exe が残っていないか見て schtasks /End /TN "タスク名" で止め、無限ループ・通信待ち・入力待ちを疑ってください。一度も実行していないタスクは 0x41303(267011)で、前回の実行時刻は 1999/11/30 0:00:00 でした。
4. 0x0 なのに出力ファイルが無い — 作業ディレクトリで相対パスが消える(実測: System32)
「開始(オプション)」欄について、Microsoft はこう説明しています。
「開始(オプション)」テキストボックスには、プログラムまたはスクリプトを実行するコマンドラインの作業ディレクトリを指定できる。(拙訳)
Microsoft Learn — Actions(Task Scheduler)
では空欄のときは何になるのか。検証機で os.getcwd() を出力させた実行結果がこれです。
cwd = C:\WINDOWS\system32
sys.executable = C:\Users\<ユーザー名>\AppData\Local\Programs\Python\Python312\python.exe
open(config.ini) NG : FileNotFoundError: [Errno 2] No such file or directory: 'config.ini'
C:\WINDOWS\system32 です。同じフォルダに置いた config.ini はそこには無く、FileNotFoundError になります。open("output.csv", "w") のような書き込みも基準は同じです。書き込みが通ってしまう環境では、CSV は作業ディレクトリ側(C:\WINDOWS\system32)に作られます。戻り値は 0x0 のまま、成果物だけが行方不明になります。多くの環境では書き込み権限が無く 0x1 で落ちます(書き込み側の実挙動は当サイト未実測)。
4.1 直し方は 3 通り+コード側
| 方法 | やること | 向いている場面 |
|---|---|---|
| コード側で基準を持つ | BASE = Path(__file__).resolve().parent | 最優先。タスク設定に依存しなくなる |
| .bat ランチャー | cd /d "%~dp0" | 既存スクリプトを書き換えたくないとき |
| GUI の「開始(オプション)」 | フォルダのパスを入力(引用符は付けない) | GUI だけで完結させたいとき |
XML の <WorkingDirectory> | XML を書いて schtasks /Create /XML | コマンドラインで作業ディレクトリを設定したいとき(4.3) |
4.2 cmd /c cd /d で与える
.bat を置きたくない場合は、cmd.exe 経由で作業ディレクトリを与えられます。検証機ではこの形で cwd が指定フォルダになり、config.ini の読み込みも通りました。次の例はコマンドプロンプト(cmd)で実行します。行末の ^ は cmd の行継続なので、PowerShell に貼っても効きません。
schtasks /Create /TN "DailyReport" ^
/TR "cmd /c cd /d C:\work\daily_report && C:\work\daily_report\.venv\Scripts\python.exe daily_report.py" ^
/SC DAILY /ST 07:30 /F
この例は 98 文字ですが、パスを 2 回書くので長いフォルダ名では簡単に上限に届きます(7.1)。長くなるなら .bat が安全です。
4.3 schtasks には「開始」を設定するオプションが無い
schtasks /Create に作業ディレクトリのオプションは無いので、CLI では XML を経由します。GUI でタスクを 1 つ作り、/XML ONE で書き出したものを雛形にするのが確実です。<Actions> に <WorkingDirectory> を足せば、引数側は相対パスで書けます。
<Actions Context="Author">
<Exec>
<Command>C:\work\daily_report\.venv\Scripts\python.exe</Command>
<Arguments>daily_report.py</Arguments>
<WorkingDirectory>C:\work\daily_report</WorkingDirectory>
</Exec>
</Actions>
schtasks /Create /TN "DailyReport" /XML C:\work\daily_report\task.xml /F
検証機ではこの形で os.getcwd() が指定フォルダになりました。XML は encoding="UTF-16" 指定なので Out-File -Encoding Unicode で保存します。
5. venv が効かない — py ランチャーとの差を実測で比べる
venv(仮想環境)はプロジェクト専用の Python 一式を .venv に置いたもので、pip install したライブラリもこの中に入ります。「手元では import pandas が通るのに、タスクだと ModuleNotFoundError」の原因はほぼこれです。venv 専用モジュールを 1 つ置き、4 通りの起動方法で比べました(実行結果)。
| 「プログラム/スクリプト」に書いたもの | sys.executable | VIRTUAL_ENV | venv 専用モジュールの import |
|---|---|---|---|
システムの python.exe(絶対パス) | Python312\python.exe | None | ModuleNotFoundError |
py.exe(絶対パス) | Python312\python.exe | None | ModuleNotFoundError |
.venv\Scripts\python.exe(絶対パス) | .venv\Scripts\python.exe | None | 成功 |
py(パスなし) | —— | —— | そもそも起動せず 0x80070002 |
結論は 1 行です。venv の Scripts\python.exe を絶対パスで指定してください。 activate.bat は要りません。
5.1 なぜ py ランチャーでは切り替わらないのか
py ランチャーが仮想環境を使うのは「仮想環境がアクティブな場合」(公式)で、VIRTUAL_ENV は activate したシェルの中にしかありません。venv の実体は python.exe の隣の pyvenv.cfgなので、直接指定した行は VIRTUAL_ENV が None でも import が通りました。
5.2 ユーザー単位インストールの Python は、別アカウントから見えないことがある
「Install just for me」で入れた Python はユーザーのプロファイル配下に入ります。検証機もこの構成で、場所は %LocalAppData%\Programs\Python\PythonXY です。SYSTEM や別ユーザーでタスクを実行すると見つからず 0x80070002 になり得ます(別アカウントからの見え方は当サイト未実測)。共有 PC なら全ユーザー向けに入れ直すのが素直です。
where python の出力が %LocalAppData%\Microsoft\WindowsApps\python.exe なら要注意です。Microsoft Store 版のアプリ実行エイリアスで、実体ではありません(Store 版は未実測)。
6. python.exe と pythonw.exe — 黒い画面と print の行き先
公式ドキュメントによれば、pythonw はコンソールウィンドウを作らないウィンドウ版です。タスクから両方を起動し、GetConsoleWindow() と sys.stdout を確認しました(実行結果)。
| 起動に使った exe | GetConsoleWindow() | sys.stdout | print() |
|---|---|---|---|
python.exe | 非 0(ハンドルが返る) | TextIOWrapper(encoding は utf-8) | コンソールへ出る |
pythonw.exe | 0 | None | 例外にならず、黙って捨てられる |
判定はハンドルの有無で、画面の目視確認はしていません。黒い画面が邪魔なら pythonw.exe、標準出力にログを頼るなら python.exe です。
pythonw.exe なら、ライブラリが sys.stdout.write() を直接呼ぶ箇所を if sys.stdout is not None: で守ります(ログは 9 章)。
ただし本記事が勧める .bat ランチャー経由では、中の python.exe を pythonw.exe に替えても cmd のウィンドウ自体は出ます。窓を消したいなら 2 択です。①タスクのプロパティで「ユーザーがログオンしているかどうかにかかわらず実行する」を選ぶ(対話的に実行されないため)。こちらは .bat をそのまま使えますが、保存時にアカウントの資格情報を求められます(「パスワードを保存しない」= S4U を選べば保存はされません・10.1)。②.bat をやめて pythonw.exe を直接登録する(作業ディレクトリは「開始(オプション)」欄、ログは logging・9 章。パスワードの保存は不要)。①②とも、画面に何も出ないことの目視確認は当サイト未実測です(13 章)。
7. 標準出力を残す — リダイレクトと「253 文字」の罠
「プログラム/スクリプト」欄はシェルではないので、> log.txt と書いてもリダイレクトにはならず、ただの引数として Python に渡ります。/TR "…\probe.py argvtest > …\o8.log" で登録したときの sys.argv がこれです(実行結果。o8.log は作られませんでした)。
sys.argv = ['C:\\work\\daily_report\\probe.py', 'argvtest', '>', 'C:\\work\\daily_report\\o8.log']
ログを残すには 2 択です。
cmd.exeを経由する(cmd /c "... > log.txt 2>&1")- .bat ランチャーを置く(2.2。こちらを推します)
2>&1 は「標準エラーも同じ先へ流す」指定で、3.1 のとおりこれが無いと Traceback がまるごと消えます。
7.1 /TR は 253 文字で黙って切られる
公式リファレンスは /TR に渡すパス名を「262 文字以内」としています。検証機で cmd /c echo XXXX… の長さを 200〜270 文字の間で 26 通りに変えて登録し、保存された内容と突き合わせた実行結果が次です。
| 渡した文字数 | タスク作成 | 実際に保存された文字数 |
|---|---|---|
| 200〜253 | 成功 | 渡した文字数と同じ |
| 254〜261 | 成功してしまう | 253(末尾が黙って切り落とされる) |
| 262 以上 | 失敗(構文エラー) | —— |
危険なのは真ん中の行です。エラーは出ないのに、末尾から数文字が消えます。実際、257 文字の cmd /c cd /d ... > ...\o2.log 2>&1 を登録したところ、保存された引数は末尾の 2>&1 を失っていました。「ログは出るのにエラーだけ記録されない」という、いちばん気づきにくい壊れ方をします。
実務では 253 文字を上限とみなすのが安全です。長いパスを 2 回書く cmd /c cd /d ... && ... の形は簡単に届くので、作成後は必ず保存内容を確認してください。
schtasks /Query /TN "DailyReport" /XML ONE | findstr /C:"Arguments"
.bat ランチャーにすれば /TR はパス 1 個で済み、この問題は起きません。
7.2 ログを「上書き」にしない
> は毎回上書き、>> は追記です。日次バッチで > を使うと失敗した日のログが翌朝の正常実行で消えます。追記にして、サイズ管理は Python 側のローテーションへ(9 章)。
8. 文字化けと UnicodeEncodeError — 手元で再現しない理由
「手元では出ないのに、タスクにすると UnicodeEncodeError で落ちる」。sys.stdout.encoding を 5 通りの起動方法で採取した実行結果が理由を示します。
| 起動のしかた | sys.stdout.encoding | print("😀") |
|---|---|---|
タスクから python.exe(リダイレクトなし) | utf-8 | 成功 |
cmd /c ... > log.txt(ファイルへ) | cp932 | UnicodeEncodeError |
python.exe -X utf8 ... > log.txt | utf-8 | 成功(ログも UTF-8) |
set PYTHONUTF8=1&& python ... > log.txt | utf-8 | 成功 |
set PYTHONUTF8=1 && python ...(&& の前に空白) | —— | 起動すらしない(8.2) |
ポイントは 2 行目です。コンソールに繋がった標準出力は utf-8、ファイルへリダイレクトした瞬間に cp932。これが「手元で再現しない」の正体で、① や ㈱ が混ざった瞬間に落ちます。全体像は cp932 と UTF-8 の記事へ(タスクとの関係は同 10 章)。
なお同記事の 10 章が対象にするのは「コンソールに繋がらない実行形態」(ログオン非依存の実行やサービス経由)です。python.exe にコンソールが付く本記事の条件とは違います。
8.1 対策は -X utf8 が確実
公式は UTF-8 モードの有効化手段を 2 つ挙げています。
Python UTF-8 モードを使って既定のテキストエンコーディングを UTF-8 に変更できる。
docs.python.org — Using Python on Windows-X utf8コマンドラインオプション、またはPYTHONUTF8=1環境変数で有効化できる(拙訳)
環境変数をシステム全体に設定すると、公式によれば Python 3.7 以降の全アプリに影響します。-X utf8 ならそのタスクの中だけで閉じます。コード側で open(path, encoding="utf-8") と明示すればさらに確実です(Excel で開く CSV なら utf-8-sig)。
8.2 set PYTHONUTF8=1 && の空白でタスクが落ちる
cmd の set は && の前の空白まで値に含めるため、PYTHONUTF8 に "1 "(末尾に空白)が入ります。検証機ではこれで Python が起動時に停止し、戻り値は 0x1 でした(実行結果)。
Fatal Python error: preconfig_init_utf8_mode: invalid PYTHONUTF8 environment variable value
Python runtime state: preinitializing
直し方は set PYTHONUTF8=1&& python ...(空白なし)か、-X utf8 に替えることです。標準エラーを 2>&1 でログに落としていなければ、残るのは 0x1 という数字だけです。
8.3 .bat 自体の文字コードでも落ちる
日本語コメント入りの run.bat を UTF-8 のまま置いてタスクから実行したところ、戻り値は 0x2331(10 進 9009)でした。同じ .bat を cmd から直接叩くと、コメント行が文字化けしてコマンドとして解釈されていました(実行結果・抜粋/3 件のうち先頭)。
'堤畑諢上☆繧・if' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
cmd.exe は .bat を既定のコードページ(日本語環境なら cp932)で読みます。UTF-8 の 3 バイト文字が 2 バイト単位で区切り直され、行末のバイトが「2 バイト文字の 1 バイト目」と解釈されると、直後の改行まで飲み込まれます。検証機のエラーが '…繧・if'・'…霑ス險・set' と、化けたコメントの末尾に次の行の先頭(if や set)が連結された形で出たのはこのためです。同じ内容を cp932(メモ帳の「ANSI」)で保存し直したら戻り値 0x0。日本語コメントを書くならこちらで保存してください。
.bat / .ps1 そのものの保存形式(BOM・chcp・PowerShell 側の事情)は姉妹記事に分けています(PowerShell と .bat の文字化け対策・2027 年 1 月公開)。
9. ログ設計と失敗通知 — 履歴は既定で無効だった
タスクスケジューラの「履歴」タブは、検証機では既定で無効でした。イベントログを照会すると IsEnabled: False で、記録が 1 件もありません。
Get-WinEvent -ListLog Microsoft-Windows-TaskScheduler/Operational |
Format-List LogName, IsEnabled, RecordCount
LogName : Microsoft-Windows-TaskScheduler/Operational
IsEnabled : False
RecordCount :
有効化は「操作」メニューからできますが、履歴が答えるのは「起動したか」までです。頼りになるのは自分のスクリプトのログで、最低限この 3 つを毎回残してください。
- 開始と終了の時刻(所要時間が分かる)
- 処理した件数(0 件の正常終了を見逃さない)
- 例外の全文(
logging.exception()か2>&1)
ファイルは実行ごとに新規作成せず、1 本に追記します。失敗した日だけ findstr ERROR で拾えるほうが早いからです。実装は Python logging 設計の記事、保存先は SQLite の記事へ。
9.1 失敗しても、誰も気づかない
タスクスケジューラには「失敗したらメールを送る」機能が事実上ありません。かつての「電子メールの送信」操作(EmailAction オブジェクト)について、公式はこう書いています。
このオブジェクトはサポートされなくなりました。回避策として PowerShell の
Microsoft Learn — EmailAction objectSend-MailMessageコマンドレットをIExecActionで使ってください(拙訳)
クライアント OS のサポートは Windows 7 で終了しました。毎朝 7 時のタスクが 3 週間前から 0x1 を返し続けていた、という事故はこうして起きます。
対策は 2 つ。1 つは try/except で Webhook を叩く方法(実装は Slack / Teams への通知の記事・2026 年 12 月公開)。もう 1 つは成功したときだけ更新するファイルのタイムスタンプを監視する方法です。後者は通知先が無くても成立し、起動しなかったケースも拾えます。
import logging
from datetime import datetime
from pathlib import Path
BASE = Path(__file__).resolve().parent
(BASE / "logs").mkdir(exist_ok=True) # .bat 経由でなくても自分で作る
logging.basicConfig(
filename=BASE / "logs" / "app.log", encoding="utf-8",
level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s",
)
logger = logging.getLogger(__name__)
def main() -> None:
... # 本体の処理
if __name__ == "__main__":
try:
main()
# 正常終了したときだけ更新する「生存確認」ファイル
(BASE / "last_success.txt").write_text(
datetime.now().isoformat(timespec="seconds"), encoding="utf-8"
)
except Exception:
logger.exception("日次処理が失敗しました")
raise # 再送出して終了コードを 1 にする(= 0x1 が記録される)
raise で握りつぶさないのが要点です。ログには全文を残し、終了コードは非ゼロで返す。タスクの一覧を眺めるだけでも異常に気づけます。
10. 実行アカウント・権限・電源条件 — 公式仕様と既定値
schtasks で作ったタスクの設定を XML で書き出すと、既定値が確認できます(実行結果・抜粋)。
<LogonType>InteractiveToken</LogonType>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
これは schtasks /Create で作った場合の既定で、GUI から作った場合と一致するかは未確認です。この 4 要素はいずれも「動かない」の原因になります。下の表には、この抜粋に出てこない 3 項目も足しました。
| 設定 | 既定(実測) | 意味と、はまりどころ |
|---|---|---|
LogonType | InteractiveToken |
「ログオンしているときのみ実行」。サインアウトしている間は動きません |
DisallowStartIfOnBatteries | true |
バッテリ駆動中は開始しない。ノート PC を電源から抜いた日だけ動かないの原因 |
StopIfGoingOnBatteries | true |
実行中に電源が抜けると停止させられる(この経路での 0x41306 は当サイト未実測) |
MultipleInstancesPolicy | IgnoreNew |
前回が終わっていなければ新しい起動を捨てる。0x41301 で固まる正体 |
| タスクを停止するまでの時間 | 72:00:00 |
公式も「既定では開始から 72 時間で停止される」と明記。長時間処理は ExecutionTimeLimit を明示する |
StartWhenAvailable | XML に要素なし | 「開始できなかった場合すぐに開始」。前の晩に PC を落としていた日のタスクは、これが無いと飛びます(10.3) |
WakeToRun | XML に要素なし | 「タスクの実行時にスリープを解除」。スリープ中の時刻は、これが無いと拾えません(10.3) |
10.1 「ログオンしているかどうかにかかわらず実行する」の代償
ログオフ中も動かしたい場合はこの設定に切り替えます。公式(Task Security Context)は「このラジオボタンを選択した場合、タスクは対話的に実行されない」(拙訳)と明記しています。ここから来る注意が 3 つあり、いずれも公式記述からの整理で当サイトでは未実測です。
- GUI は画面に出ません(tkinter などはプロセスだけが動き続けます)。画面が要るなら「ログオンしているときのみ実行」を選びます
- S4U(パスワードを保存しない設定)ではネットワークリソースを使えません(公式は制約付き委任の構成時だけを例外としています)
- ドライブレターはログオンセッションに紐づきます。別セッションのタスクから
Z:は見えないのが原則で、コード側は\\FILESRV01\tools\...のような UNC 表記にします
10.2 「最上位の特権で実行する」を反射的に付けない
動かないときにとりあえずチェックを入れる、という使い方はしないでください。原因の大半は権限ではなくパス・作業ディレクトリ・例外で、昇格が要るのはレジストリ書き込み・サービス制御・保護されたフォルダへの書き込みだけです。
10.3 前の晩に PC を落としていた日は、朝のタスクが飛ぶ
電源が切れていた・スリープしていた時刻のトリガーは、そのまま流れます。対策の 2 設定は、schtasks が作った XML にはどちらも現れませんでした(=指定なし)。
- タスクを設定した時刻に開始できなかった場合、すぐにタスクを開始する(
StartWhenAvailable)。公式によれば適用対象は時間ベースのトリガーで、遅れた実行は既定で 10 分の遅延後に走ります - タスクの実行時にスリープを解除する(
WakeToRun)。公式によれば、スリープから復帰させて実行し、完了まで起きたままにします
この 2 つの実挙動は当サイト未実測です(公式記述と、XML に要素が無いことの確認まで)。それでも 0x41303 のまま止まっているタスクは、戻り値より先に「その時刻に PC が起きていたか」を疑ってください。
11. サービスと定期実行の使い分け
目安は「10 分ごとより短い間隔で動かしたいか」。並べたくなったら常駐処理へ寄せる合図です。
| 観点 | タスクスケジューラ | Windows サービス |
|---|---|---|
| 向いている処理 | 決まった時刻に 1 回走って終わる(日報集計・バックアップ・データ吸い上げ) | 止まらずに動き続ける(設備の常時監視・受信待ち) |
| 失敗したとき | 次の時刻まで待つ。戻り値だけが残る | 復旧設定で自動再起動できる |
| 状態の確認 | 「前回の実行結果」を見る | sc.exe query で稼働中か即座に分かる |
| 作るコスト | 低い(.bat 1 枚+登録) | 高い(NSSM か pywin32 が要る) |
実装は Python アプリを Windows サービスとして常駐させる記事へ。
12. よくある質問(FAQ)
前回の実行結果 0x1 は何のエラーですか?
タスクスケジューラのエラーではなく、起動されたプログラムが返した終了コード 1 です。Python なら未捕捉の例外か sys.exit(1) が該当します。Win32 のエラー 1「Incorrect function」と同じ数字ですが、その線で調べても手掛かりは出ません。先に 2>&1 付きでログを残してください(3.1)。
タスクスケジューラで venv の Python を使うには?
「プログラム/スクリプト」欄に C:\work\daily_report\.venv\Scripts\python.exe のように venv 内の python.exe を絶対パスで指定します。activate.bat は不要で、py ランチャー経由では切り替わりません(比較は 5 章)。
実行時に黒い画面が一瞬出るのを消せますか?
python.exe の代わりに pythonw.exe を指定すると、コンソールウィンドウが作られません(検証機では GetConsoleWindow() が 0)。ただし sys.stdout が None になり print() は捨てられるので、ログは logging でファイルへ書きます。.bat ランチャーを登録している場合は、中の python.exe を替えても cmd の窓は出ます(残る 2 択は 6 章)。
ログオンしていなくてもタスクを実行できますか?
できます。「ユーザーがログオンしているかどうかにかかわらず実行する」を選び、パスワードを保存します。ただし公式のとおり対話的には実行されないため、GUI ウィンドウは出ません。S4U(パスワードを保存しない設定)ではネットワークリソースを使えない制約もあります(10.1)。
前の晩に PC を落としていたら、朝のタスクは飛んでしまいますか?
そのままでは飛びます。対策は「タスクを設定した時刻に開始できなかった場合、すぐにタスクを開始する」(StartWhenAvailable)と「タスクの実行時にスリープを解除する」(WakeToRun)の 2 つ。どちらも schtasks が作る XML には現れませんでした(=指定なし)。前者を有効にすると、公式によれば遅れた分は 10 分の遅延を挟んで実行されます(10.3・実挙動は未実測)。
手元では出ない UnicodeEncodeError がタスクだけで出ます
標準出力の文字コードが違うためです。検証機では、コンソールに繋がった python.exe の sys.stdout.encoding は utf-8、ファイルへリダイレクトすると cp932 でした。-X utf8 で起動すればリダイレクト先でも utf-8 のままです(8 章)。
13. 検証環境と、当サイトが測れなかったこと
どこまでが検証機で動かした結果で、どこからが一次資料からの整理かを並べます。右の列は「当サイトでは確認できていない」という意味で、間違いという意味ではありません。
| 実測した(自宅の検証機で実行) | 実測していない(一次資料からの整理) |
|---|---|
戻り値 0x1 / 0x2 / 0x80070002 / 0x41301 / 0x41303 / 0x41306 の再現 |
0x800710E0(バッテリ駆動・スリープ復帰などの条件を再現できず。コード自体は Win32 エラー 4320 として公式に定義がありますが、タスクスケジューラでどの条件で返るかを説明した公式文書は見つけられていません) |
「開始(オプション)」空欄時の os.getcwd() が C:\WINDOWS\system32 になること・cmd /c cd /d と XML の <WorkingDirectory> で作業ディレクトリが変わること |
「ログオンしているかどうかにかかわらず実行する」の挙動(パスワード保存が必要なため未実施。10.1 は公式記述の引用)/SYSTEM アカウント・最上位の特権での実行(管理者権限が必要なため) |
venv の python.exe 直指定・py.exe・システム Python の import 差と VIRTUAL_ENV の値 |
ユーザー単位インストールの Python が別アカウントから見えるか(5.2)/Microsoft Store 版 Python(アプリ実行エイリアス)の挙動。検証機に導入していないため未確認 |
pythonw.exe で GetConsoleWindow() が 0・sys.stdout が None になること |
画面の目視確認(判定はハンドルの有無のみ)。.bat 経由や「ログオン非依存」で窓が出ないこと、GUI(タスクスケジューラの画面)の表示・操作も未確認=本記事の実測はすべてコマンドライン |
/TR が 254〜261 文字で 253 文字に切られること(26 通りの長さで確認)・リダイレクト時に sys.stdout.encoding が cp932 になること・-X utf8 で解決すること |
AC 電源条件が実際に効く様子(デスクトップ機のため。既定値が true であることのみ実測)/system32 側への書き込みが通るかどうか(4 章)/StartWhenAvailable・WakeToRun の実挙動(10.3。XML に要素が無いことのみ実測)/UNC パス・マップドライブからの起動(共有サーバーを用意できず) |
UTF-8 保存の .bat が 0x2331 で落ち、cp932 保存で通ること・タスク履歴が既定で無効(IsEnabled: False)であること |
—— |
検証環境: 自宅の検証機(Windows 11 Pro ビルド 26200.9168 / 日本語環境・コードページ 932 / Python 3.12.10 / タスクはすべて自分のユーザーアカウント・管理者権限なしで作成)。会社支給の端末・業務用 PC は一切使用していません。実測日は 2026 年 9 月 3 日、一時タスクは削除済みです。なお実行結果は、実測時のフォルダ名・仮想環境名・ユーザー名を記事の構成へ同じ規則で置換して掲載しています。戻り値・エンコーディング・文字数・エラーメッセージは実測のままです。
結果が本記事と食い違ったら、それはあなたの環境での事実です。戻り値の表示は Windows のビルドや言語環境で差が出る可能性があります(確かめ方は 2.4)。
実機に適用する前に確認すること
⚠️ 本記事の手順と数値は当サイトの検証機での確認に基づくもので、定期実行はスクリプトの成功を保証する仕組みではありません。現場 PC に適用するときは、次の 4 点が前提です。
- 対象の PC と共有フォルダについて設備保全部門・情報システム部門の承認を得る(タスクの追加はサーバー・共有への定期アクセスを増やします)
- 設備からデータを読むタスクは読み出し専用のコマンドに限り、計画停止のタイミングで初回検証する
- 24 時間稼働中の PLC・制御機器に対して、試験的な書き込みを行うタスクを登録しない
- 失敗が続いても誰も気づかない前提で、成功したことを外から確認できる印(9.1)を必ず用意する
定期実行の仕組みは、安全インターロック・安全 PLC・法定の警報装置の代替にはなりません。適用による設備の停止・データ欠損・機会損失について、筆者・GenbaPy は責任を負いません。