概要
データ処理パイプラインで6つのプログラム(日次処理3種 + リアルタイム処理3種)をWindows Task Schedulerで定期実行しています。最初は手動でGUIから1つずつ登録していましたが、実行時刻の一括変更やPython環境パスの更新のたびにGUIで6タスクを修正するのは非現実的でした。
そこでTask SchedulerのXML定義をテンプレート化し、PowerShellスクリプトで一括生成・登録する仕組みを構築しました。さらにClaude Codeから一括更新できるワークフローを整備したことで、スケジュール変更が数分で完了するようになりました。
Task SchedulerのXML定義
GUIの限界
Windows Task Schedulerにはリッチなgui画面がありますが、以下の問題があります。
- タスクが増えると管理不能: 6タスクを手動管理するだけで修正漏れが発生する
- 一括変更ができない: Python環境パスの変更時に6タスク分の修正が必要
- バージョン管理できない: いつ誰が何を変更したか追跡できない
- 再現性がない: 環境を作り直す際にGUI操作を思い出す必要がある
XMLエクスポート
Task Schedulerに登録済みのタスクはXML形式でエクスポートできます。
1# 既存タスクをXMLとしてエクスポート2Export-ScheduledTask -TaskName "DailyBatchA" | Out-File -FilePath ".\DailyBatchA.xml" -Encoding UTF8エクスポートされたXMLの構造は以下のとおりです。
1<?xml version="1.0" encoding="UTF-16"?>2<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">3 <RegistrationInfo>4 <Description>日次集計バッチ 処理A</Description>5 <URI>\DataPipeline\DailyBatchA</URI>6 </RegistrationInfo>7 <Triggers>8 <CalendarTrigger>9 <StartBoundary>2025-01-01T18:00:00+09:00</StartBoundary>10 <Enabled>true</Enabled>11 <ScheduleByWeek>12 <DaysOfWeek>13 <Monday /><Tuesday /><Wednesday /><Thursday /><Friday />14 </DaysOfWeek>15 <WeeksInterval>1</WeeksInterval>21 collapsed lines
16 </ScheduleByWeek>17 </CalendarTrigger>18 </Triggers>19 <Actions Context="Author">20 <Exec>21 <Command>C:\anaconda3\envs\st312\python.exe</Command>22 <Arguments>-m _dailyBatchA.main</Arguments>23 <WorkingDirectory>C:\work\data-pipeline</WorkingDirectory>24 </Exec>25 </Actions>26 <Settings>27 <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>28 <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>29 <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>30 <AllowHardTerminate>true</AllowHardTerminate>31 <StartWhenAvailable>true</StartWhenAvailable>32 <RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>33 <ExecutionTimeLimit>PT4H</ExecutionTimeLimit>34 <AllowStartOnDemand>true</AllowStartOnDemand>35 </Settings>36</Task>テンプレート化
テンプレートXMLの設計
6タスクで異なるのは以下の4つだけです。
| 可変要素 | 例 |
|---|---|
| タスク名 | DailyBatchA, RealtimeB |
| 説明文 | 「日次集計バッチ 処理A」 |
| 実行時刻 | 18:00(日次処理)、毎時00分(リアルタイム処理) |
| 実行モジュール | _dailyBatchA.main, _realtimeB.main |
これらをプレースホルダーにしたテンプレートを作成します。
1<?xml version="1.0" encoding="UTF-16"?>2<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">3 <RegistrationInfo>4 <Description>{{DESCRIPTION}}</Description>5 <URI>\DataPipeline\{{TASK_NAME}}</URI>6 </RegistrationInfo>7 <Triggers>8 {{TRIGGER_BLOCK}}9 </Triggers>10 <Actions Context="Author">11 <Exec>12 <Command>{{PYTHON_PATH}}</Command>13 <Arguments>-m {{MODULE_NAME}}.main</Arguments>14 <WorkingDirectory>{{PROJECT_ROOT}}</WorkingDirectory>15 </Exec>12 collapsed lines
16 </Actions>17 <Settings>18 <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>19 <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>20 <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>21 <AllowHardTerminate>true</AllowHardTerminate>22 <StartWhenAvailable>true</StartWhenAvailable>23 <RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>24 <ExecutionTimeLimit>PT4H</ExecutionTimeLimit>25 <AllowStartOnDemand>true</AllowStartOnDemand>26 </Settings>27</Task>トリガーブロックのテンプレート
日次処理とリアルタイム処理で実行パターンが異なるため、トリガー部分は2種類用意します。
日次処理用(平日の指定時刻に1回実行):
1<CalendarTrigger>2 <StartBoundary>2025-01-01T{{EXEC_TIME}}:00+09:00</StartBoundary>3 <Enabled>true</Enabled>4 <ScheduleByWeek>5 <DaysOfWeek>6 <Monday /><Tuesday /><Wednesday /><Thursday /><Friday />7 </DaysOfWeek>8 <WeeksInterval>1</WeeksInterval>9 </ScheduleByWeek>10</CalendarTrigger>リアルタイム処理用(毎時実行 = 繰り返し):
1<CalendarTrigger>2 <StartBoundary>2025-01-01T00:00:00+09:00</StartBoundary>3 <Enabled>true</Enabled>4 <Repetition>5 <Interval>PT1H</Interval>6 <Duration>P1D</Duration>7 <StopAtDurationEnd>false</StopAtDurationEnd>8 </Repetition>9 <ScheduleByDay>10 <DaysInterval>1</DaysInterval>11 </ScheduleByDay>12</CalendarTrigger>PowerShellスクリプトによる一括生成
タスク定義
6タスクの設定値をハッシュテーブルの配列で定義します。
1$PythonPath = "C:\anaconda3\envs\st312\python.exe"2$ProjectRoot = "C:\work\data-pipeline"3$TemplateDir = "$ProjectRoot\task_scheduler\templates"4$OutputDir = "$ProjectRoot\task_scheduler\generated"5
6# 6タスクの定義7$Tasks = @(8 @{9 Name = "DailyBatchA"10 Module = "_dailyBatchA"11 Description = "日次集計バッチ 処理A"12 Type = "Daily"13 ExecTime = "18:00"14 },15 @{35 collapsed lines
16 Name = "DailyBatchB"17 Module = "_dailyBatchB"18 Description = "日次集計バッチ 処理B"19 Type = "Daily"20 ExecTime = "18:15"21 },22 @{23 Name = "DailyBatchC"24 Module = "_dailyBatchC"25 Description = "日次集計バッチ 処理C"26 Type = "Daily"27 ExecTime = "18:30"28 },29 @{30 Name = "RealtimeA"31 Module = "_realtimeA"32 Description = "リアルタイム処理A"33 Type = "Realtime"34 ExecTime = "00:00"35 },36 @{37 Name = "RealtimeB"38 Module = "_realtimeB"39 Description = "リアルタイム処理B"40 Type = "Realtime"41 ExecTime = "00:00"42 },43 @{44 Name = "RealtimeC"45 Module = "_realtimeC"46 Description = "リアルタイム処理C"47 Type = "Realtime"48 ExecTime = "00:00"49 }50)XML生成スクリプト
1function New-TaskXml {2 param(3 [hashtable]$TaskDef,4 [string]$PythonPath,5 [string]$ProjectRoot,6 [string]$TemplateDir,7 [string]$OutputDir8 )9
10 # テンプレート読み込み11 $template = Get-Content "$TemplateDir\task_template.xml" -Raw12
13 # トリガーブロックの選択14 $triggerFile = if ($TaskDef.Type -eq "Daily") {15 "$TemplateDir\trigger_daily.xml"39 collapsed lines
16 } else {17 "$TemplateDir\trigger_realtime.xml"18 }19 $triggerBlock = Get-Content $triggerFile -Raw20 $triggerBlock = $triggerBlock -replace '\{\{EXEC_TIME\}\}', $TaskDef.ExecTime21
22 # プレースホルダー置換23 $xml = $template `24 -replace '\{\{TASK_NAME\}\}', $TaskDef.Name `25 -replace '\{\{DESCRIPTION\}\}', $TaskDef.Description `26 -replace '\{\{MODULE_NAME\}\}', $TaskDef.Module `27 -replace '\{\{PYTHON_PATH\}\}', $PythonPath `28 -replace '\{\{PROJECT_ROOT\}\}', $ProjectRoot `29 -replace '\{\{TRIGGER_BLOCK\}\}', $triggerBlock30
31 # 出力32 $outputPath = Join-Path $OutputDir "$($TaskDef.Name).xml"33 $xml | Out-File -FilePath $outputPath -Encoding UTF834 Write-Host "生成完了: $outputPath"35
36 return $outputPath37}38
39# 全タスクのXML生成40if (-not (Test-Path $OutputDir)) {41 New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null42}43
44$generatedFiles = @()45foreach ($task in $Tasks) {46 $path = New-TaskXml -TaskDef $task `47 -PythonPath $PythonPath `48 -ProjectRoot $ProjectRoot `49 -TemplateDir $TemplateDir `50 -OutputDir $OutputDir51 $generatedFiles += $path52}53
54Write-Host "`n生成されたXMLファイル: $($generatedFiles.Count)件"Task Schedulerへの一括登録
1function Register-AllTasks {2 param(3 [string]$OutputDir,4 [array]$Tasks5 )6
7 foreach ($task in $Tasks) {8 $xmlPath = Join-Path $OutputDir "$($task.Name).xml"9
10 if (-not (Test-Path $xmlPath)) {11 Write-Warning "XMLファイルが見つかりません: $xmlPath"12 continue13 }14
15 # 既存タスクがあれば削除16 collapsed lines
16 $existingTask = Get-ScheduledTask -TaskName $task.Name -ErrorAction SilentlyContinue17 if ($existingTask) {18 Unregister-ScheduledTask -TaskName $task.Name -Confirm:$false19 Write-Host "既存タスク削除: $($task.Name)"20 }21
22 # XMLからタスク登録23 Register-ScheduledTask -TaskName $task.Name `24 -TaskPath "\DataPipeline\" `25 -Xml (Get-Content $xmlPath -Raw) `26 -Force27 Write-Host "タスク登録完了: $($task.Name)"28 }29}30
31Register-AllTasks -OutputDir $OutputDir -Tasks $TasksClaude Codeからの一括更新ワークフロー
Claude Codeを使うと、自然言語でスケジュール変更を指示できます。
典型的な変更パターン
パターン1: 実行時刻の一括変更
「日次処理の実行時刻を18:00から17:30に変更して」とClaude Codeに指示すると、以下が自動で行われます。
- PowerShellスクリプトの
$Tasks配列にあるExecTimeを修正 - XMLを再生成
- Task Schedulerに再登録
パターン2: Python環境パスの変更
Anacondaの環境を更新した場合、$PythonPathを1行変更してXMLを再生成するだけで6タスク全てに反映されます。
パターン3: 新しい処理パターンの追加
$Tasks配列にハッシュテーブルを1つ追加し、対応するモジュールを作成すれば、スケジューラ登録まで自動化できます。
Claude Codeとの連携のポイント
1重要なルール:21. テンプレートとスクリプトはGit管理する32. Claude Codeが変更するのは設定値($Tasks配列)のみ43. XML生成と登録はPowerShellスクリプト経由で行う(直接XML編集はしない)54. 変更後は必ず Get-ScheduledTask で登録状態を確認する1# 変更後の確認コマンド2Get-ScheduledTask -TaskPath "\DataPipeline\" | Format-Table TaskName, State, @{3 Name = "NextRunTime"4 Expression = {5 (Get-ScheduledTaskInfo -TaskName $_.TaskName).NextRunTime6 }7}出力例:
1TaskName State NextRunTime2-------- ----- -----------3DailyBatchA Ready 2026-04-09 17:30:004DailyBatchB Ready 2026-04-09 17:45:005DailyBatchC Ready 2026-04-09 18:00:006RealtimeA Ready 2026-04-08 13:00:007RealtimeB Ready 2026-04-08 13:00:008RealtimeC Ready 2026-04-08 13:00:00テンプレート化のメリット
Before(GUIで個別管理)
| 操作 | 時間 | リスク |
|---|---|---|
| 1タスクの時刻変更 | 2分 | 入力ミス |
| 6タスクの一括変更 | 12分 | 修正漏れ |
| Pythonパス変更 | 12分 | 修正漏れ |
| 環境再構築 | 30分以上 | 手順忘れ |
After(テンプレート + PowerShell)
| 操作 | 時間 | リスク |
|---|---|---|
| 1タスクの時刻変更 | 1分 | なし(スクリプト経由) |
| 6タスクの一括変更 | 1分 | なし(設定値1箇所変更) |
| Pythonパス変更 | 1分 | なし(変数1箇所変更) |
| 環境再構築 | 2分 | なし(スクリプト実行のみ) |
注意点
ExecutionTimeLimit
<ExecutionTimeLimit>PT4H</ExecutionTimeLimit>(最大実行時間4時間)を設定しています。この設定がないと、スクリプトがハングした場合にTask Schedulerがプロセスを終了しません。デフォルト値の72時間は長すぎるため、処理内容に合わせて適切な値を設定すべきです。
StartWhenAvailable
<StartWhenAvailable>true</StartWhenAvailable>を設定すると、予定時刻にPCがスリープ中だった場合、復帰後に遅延実行されます。ノートPCで運用する場合は必須の設定です。
RunOnlyIfNetworkAvailable
<RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>を設定しています。API経由でデータを取得するため、ネットワーク未接続時に実行しても意味がないためです。
まとめ
Windows Task SchedulerのXML定義をテンプレート化するメリットは以下の3点です。
- 一括変更が1箇所で完結: PowerShellスクリプトの設定値を1箇所変更するだけで、6タスク全てに反映される
- Git管理でバージョン追跡可能: テンプレートとスクリプトをGitで管理することで、変更履歴が残る
- Claude Codeとの親和性: テンプレートのプレースホルダー構造は、AIによるコード修正と相性が良い。自然言語で「実行時刻を変更して」と指示するだけで済む
手動のGUI操作を排除し、設定をコードとして管理する(Infrastructure as Code)アプローチは、タスクが少ないうちから導入しておくと、後からタスクが増えたときに効いてきます。