バッチファイルで外部プログラムを実行した後、「終了するまで待ってから次の処理に進みたい」場面は頻繁にあります。インストーラの完了後に設定ファイルを配置する、ビルド完了後にテストを走らせる、といったケースです。
この記事では、start /wait、tasklist 監視、timeout、waitfor など、バッチファイルで使えるプロセス待機の全方法を比較し、場面ごとの使い分けを解説します。
待機方法の全体比較
まず、それぞれの方法の特徴を整理します。
| 方法 | 待機の対象 | ERRORLEVEL | 最適な場面 |
|---|---|---|---|
| 直接実行 | プロセス終了 | 取得可 | CUI アプリ・bat の順次実行 |
| start /wait | プロセス終了 | 取得可 | GUI アプリ・インストーラの待機 |
| tasklist 監視 | プロセス終了 | 取得不可 | start で起動済みのプロセスを後から監視 |
| timeout | 指定秒数 | ― | 固定時間の待機 |
| waitfor | シグナル受信 | ― | バッチ間・PC間の連携 |
「プログラムの終了を待つ」なら start /wait、「起動済みプロセスを監視する」なら tasklist、「一定時間だけ待つ」なら timeout が最適です。
方法1:start /wait(最も基本かつ確実)
start /wait は、プログラムが終了するまでバッチの実行を一時停止します。
基本の使い方
@echo off echo メモ帳を起動します... start "" /wait notepad.exe echo メモ帳を閉じました。次の処理に進みます。 pause
メモ帳を閉じるまで「メモ帳を閉じました」は表示されません。
ERRORLEVEL で終了コードを取得する
start /wait の最大の利点は、プログラムの終了コードを取得できることです。
@echo off
start "" /wait "C:MyAppsetup.exe" /S
if %ERRORLEVEL% equ 0 (
echo インストール成功
) else if %ERRORLEVEL% equ 3010 (
echo インストール成功(要再起動)
) else (
echo インストール失敗(コード: %ERRORLEVEL%)
exit /b %ERRORLEVEL%
)
注意:ERRORLEVEL は直後に確認してください。間に echo 等を挟むと値がリセットされます。
パスにスペースが含まれる場合の注意
start コマンドは、最初のダブルクォーテーションをウィンドウタイトルとして解釈します。
rem NG: パスがタイトルとして解釈され、アプリが起動しない start /wait "C:Program FilesMyAppapp.exe" rem OK: 空のタイトル "" を先に指定する start "" /wait "C:Program FilesMyAppapp.exe"
start コマンドでは常に最初に "" を書く習慣にすると安全です。
start /wait が効かないケース
start /wait でもプログラムの終了を待てないケースがあります。
| ケース | 原因 | 対処法 |
|---|---|---|
| Microsoft Store アプリ | ランチャーがすぐ終了し、本体は別プロセスで起動する | PowerShell の Start-Process -Wait を使う |
| 自己解凍型 exe | 解凍後に別プロセスでインストーラを起動する | msiexec で直接 .msi を指定する |
| ランチャー経由のアプリ | ランチャーが終了しても本体が残る | tasklist で本体プロセスを監視する |
| タスクスケジューラ経由 | セッション分離で待機が効かない場合がある | 直接実行(start を使わない)に変更する |
Store アプリ等で start /wait が効かない場合は、バッチ内から PowerShell を呼びます。
rem PowerShell の Start-Process -Wait で待機する powershell -command "Start-Process 'notepad' -Wait"
方法2:直接実行と call(同期実行)
exe を直接書くだけでも、そのプログラムが終了するまでバッチは待機します。
@echo off echo ビルド開始... "C:MyAppuild.exe" --config release echo ビルド完了(ERRORLEVEL: %ERRORLEVEL%) echo テスト開始... "C:MyApp est.exe" --all echo テスト完了(ERRORLEVEL: %ERRORLEVEL%)
call はバッチ→バッチ専用
別のバッチファイルを呼ぶ場合は call が必須です。call なしだと呼び出し元に戻りません。
@echo off rem bat → bat は call 必須 call build.bat echo build.bat が終了しました(ERRORLEVEL: %ERRORLEVEL%) rem bat → exe は call 不要(直接実行で待機する) "C:MyAppdeploy.exe" echo deploy.exe が終了しました
直接実行 vs start /wait の使い分け
| 項目 | 直接実行 | start /wait |
|---|---|---|
| ウィンドウ | 同じコマンドプロンプトで実行 | 新しいウィンドウで実行 |
| 最小化 / 最大化 | 不可 | /min /max で指定可 |
| 作業ディレクトリ | カレントディレクトリ | /d で指定可 |
| プロセス優先度 | 変更不可 | /low /high 等で指定可 |
| 主な用途 | CUI アプリ・bat | GUI アプリ・インストーラ |
CUI アプリなら直接実行、GUI アプリで起動オプションを指定したい場合は start /wait が適しています。
方法3:tasklist でプロセスの終了を監視する
start(/wait なし)で起動したプロセスや、既に起動済みのプロセスの終了を待つには、tasklist でポーリング監視します。
@echo off echo Excel を起動して終了を待ちます... start "" "C:Program FilesMicrosoft Office ootOffice16EXCEL.EXE" :WAIT_LOOP timeout /t 2 /nobreak >nul tasklist /fi "imagename eq EXCEL.EXE" 2>nul | find "EXCEL.EXE" >nul if %ERRORLEVEL% equ 0 goto WAIT_LOOP echo Excel が終了しました。次の処理に進みます。
仕組み:
tasklistで指定プロセスの存在を確認findが見つかれば ERRORLEVEL=0 → まだ動いているのでループfindが見つからなければ ERRORLEVEL=1 → 終了したのでループ脱出
複数プロセスの完了を待つ
並列で起動した全プロセスが終了するまで待つパターンです。
@echo off echo 3つのタスクを並列実行します... start "" "C:MyApp ask1.exe" start "" "C:MyApp ask2.exe" start "" "C:MyApp ask3.exe" :WAIT_ALL timeout /t 2 /nobreak >nul tasklist /fi "imagename eq task1.exe" 2>nul | find "task1.exe" >nul && goto WAIT_ALL tasklist /fi "imagename eq task2.exe" 2>nul | find "task2.exe" >nul && goto WAIT_ALL tasklist /fi "imagename eq task3.exe" 2>nul | find "task3.exe" >nul && goto WAIT_ALL echo 全タスクが終了しました
いずれか1つでもまだ動いていれば && goto WAIT_ALL でループに戻ります。
タイムアウト付きの監視
プロセスが固まった場合に備えて、タイムアウトを設けることもできます。
@echo off
setlocal enabledelayedexpansion
set TIMEOUT_SEC=120
set ELAPSED=0
start "" "C:MyAppheavy_task.exe"
:WAIT_TIMEOUT
timeout /t 5 /nobreak >nul
set /a ELAPSED+=5
tasklist /fi "imagename eq heavy_task.exe" 2>nul | find "heavy_task.exe" >nul
if !ERRORLEVEL! neq 0 (
echo 処理が完了しました(%ELAPSED%秒)
goto :DONE
)
if !ELAPSED! geq %TIMEOUT_SEC% (
echo タイムアウト(%TIMEOUT_SEC%秒): プロセスを強制終了します
taskkill /f /im heavy_task.exe >nul 2>&1
goto :DONE
)
goto WAIT_TIMEOUT
:DONE
endlocal
注意:ループ内で ERRORLEVEL を正しく取得するには、setlocal enabledelayedexpansion と !ERRORLEVEL!(! で囲む形式)を使う必要があります。%ERRORLEVEL% はループ突入時の値のまま更新されません。
方法4:timeout で指定時間だけ待機する
timeout はプロセスの終了を検知するのではなく、指定した秒数だけ一時停止するコマンドです。
@echo off echo サービスを再起動します... net stop MyService timeout /t 5 /nobreak >nul net start MyService echo サービスを再起動しました
timeout の主要オプション
| オプション | 意味 | 例 |
|---|---|---|
/t 秒数 |
待機する秒数(0〜99999) | timeout /t 10 |
/nobreak |
キー入力でスキップ不可にする | timeout /t 10 /nobreak |
/t -1 |
キーが押されるまで無制限に待機 | timeout /t -1 |
> nul を付けると「あと○秒」のカウントダウン表示を抑制できます。
timeout が使えない場合の代替手段
timeout は Windows Vista 以降のコマンドです。また、タスクスケジューラ経由で実行した場合に正しく動作しないことがあります。
代替1:ping コマンド
rem 10秒待機(ping 11回 = 約10秒) ping -n 11 localhost >nul
ping は1秒間隔で送信するため、待ちたい秒数 + 1 を -n に指定します。
代替2:choice コマンド
rem 10秒待機 choice /n /t 10 /d Y >nul
代替3:waitfor コマンド
rem 10秒待機(存在しないシグナルを待つことで時間稼ぎ) waitfor DummySignal /t 10 >nul 2>&1
方法5:waitfor でシグナルを待つ
waitfor は、別のバッチファイルや別のPCから送られるシグナルを待機するコマンドです。
基本の使い方
待機側と送信側の2つのバッチで連携します。
待機側(wait.bat):
@echo off
echo コピー完了シグナルを待っています...
waitfor CopyDone /t 300
if %ERRORLEVEL% equ 0 (
echo シグナルを受信しました。後続処理を開始します。
) else (
echo タイムアウトしました(300秒)
exit /b 1
)
送信側(send.bat):
@echo off echo ファイルコピー中... xcopy /s /e "C:Source*" "D:Dest" >nul echo コピー完了。シグナルを送信します。 waitfor /si CopyDone
/t でタイムアウト秒数を指定できます。省略すると無制限に待機します。
waitfor の制約
- シグナル名は英数字のみ(日本語不可)
- 同時に待機できるシグナルは1つだけ
- ネットワーク経由の場合は同じドメイン内のPCのみ送受信可能
サイレントインストーラの待機
キッティング(PCセットアップ自動化)で最も多い用途が、インストーラの完了待ちです。
msiexec の場合
rem MSI パッケージのサイレントインストール start "" /wait msiexec /i "C:Installersapp.msi" /quiet /norestart echo msiexec 終了コード: %ERRORLEVEL%
exe インストーラの場合
サイレントオプションはインストーラの種類によって異なります。
| インストーラ種類 | サイレントオプション | 実行例 |
|---|---|---|
| NSIS | /S |
start "" /wait setup.exe /S |
| Inno Setup | /VERYSILENT |
start "" /wait setup.exe /VERYSILENT |
| InstallShield | /s /v/qn |
start "" /wait setup.exe /s /v/qn |
msiexec の主要エラーコード
| コード | 意味 |
|---|---|
| 0 | 正常終了 |
| 1602 | ユーザーがキャンセル |
| 1603 | 致命的なエラー |
| 1618 | 他のインストールが実行中 |
| 3010 | 成功(再起動が必要) |
実践テンプレート
テンプレート1:ビルド → テスト → デプロイの順次実行
@echo off
setlocal
cd /d "%~dp0"
echo === Step 1: ビルド ===
"C:Toolsuild.exe" --config release
if %ERRORLEVEL% neq 0 (
echo [エラー] ビルドに失敗しました(コード: %ERRORLEVEL%)
exit /b 1
)
echo === Step 2: テスト ===
"C:Tools est.exe" --all
if %ERRORLEVEL% neq 0 (
echo [エラー] テストに失敗しました(コード: %ERRORLEVEL%)
exit /b 2
)
echo === Step 3: デプロイ ===
start "" /wait "C:Toolsdeploy.exe" --target production
if %ERRORLEVEL% neq 0 (
echo [エラー] デプロイに失敗しました(コード: %ERRORLEVEL%)
exit /b 3
)
echo === 全ステップ完了 ===
endlocal
exit /b 0
テンプレート2:キッティング用の連続インストール
@echo off
setlocal
cd /d "%~dp0"
set LOG=%~dp0install.log
echo [%date% %time%] キッティング開始 >> "%LOG%"
rem --- Chrome ---
echo Chrome をインストール中...
start "" /wait msiexec /i "%~dp0installersGoogleChrome.msi" /quiet /norestart
call :CHECK_RESULT "Chrome" %ERRORLEVEL%
rem --- 7-Zip ---
echo 7-Zip をインストール中...
start "" /wait "%~dp0installers7z-setup.exe" /S
call :CHECK_RESULT "7-Zip" %ERRORLEVEL%
rem --- VS Code ---
echo VS Code をインストール中...
start "" /wait "%~dp0installersVSCodeSetup.exe" /VERYSILENT /NORESTART
call :CHECK_RESULT "VS Code" %ERRORLEVEL%
echo [%date% %time%] キッティング完了 >> "%LOG%"
echo.
echo ===========================
echo 全ソフトのインストール完了
echo ===========================
pause
endlocal
exit /b 0
:CHECK_RESULT
if %2 equ 0 (
echo → %~1 : 成功
echo [%date% %time%] %~1 : 成功(コード: %2) >> "%LOG%"
) else if %2 equ 3010 (
echo → %~1 : 成功(要再起動)
echo [%date% %time%] %~1 : 成功-要再起動(コード: %2) >> "%LOG%"
) else (
echo → %~1 : 失敗(コード: %2)
echo [%date% %time%] %~1 : 失敗(コード: %2) >> "%LOG%"
)
exit /b 0
テンプレート3:並列実行して全完了を待つ(タイムアウト付き)
@echo off
setlocal enabledelayedexpansion
cd /d "%~dp0"
set MAX_WAIT=300
set INTERVAL=5
set ELAPSED=0
echo 3つの処理を並列実行します...
start "" "C:MyAppprocess_a.exe"
start "" "C:MyAppprocess_b.exe"
start "" "C:MyAppprocess_c.exe"
:POLL
timeout /t %INTERVAL% /nobreak >nul
set /a ELAPSED+=%INTERVAL%
set RUNNING=0
for %%P in (process_a.exe process_b.exe process_c.exe) do (
tasklist /fi "imagename eq %%P" 2>nul | find "%%P" >nul
if !ERRORLEVEL! equ 0 set RUNNING=1
)
if !RUNNING! equ 1 (
if !ELAPSED! geq %MAX_WAIT% (
echo [警告] タイムアウト(%MAX_WAIT%秒)
taskkill /f /im process_a.exe >nul 2>&1
taskkill /f /im process_b.exe >nul 2>&1
taskkill /f /im process_c.exe >nul 2>&1
exit /b 1
)
goto POLL
)
echo 全処理が完了しました(%ELAPSED%秒)
endlocal
exit /b 0
まとめ
| やりたいこと | 方法 |
|---|---|
| exe / インストーラの終了を待つ | start "" /wait app.exe |
| bat から別の bat を呼んで待つ | call sub.bat |
| 起動済みプロセスの終了を待つ | tasklist + ループ監視 |
| 並列実行して全完了を待つ | start 複数 + tasklist 監視 |
| 固定秒数だけ待機する | timeout /t 秒数 /nobreak |
| 別のバッチからの合図を待つ | waitfor シグナル名 |
| start /wait が効かないアプリ | PowerShell Start-Process -Wait |
ポイント:
start /waitが最も基本的で確実。パスを指定するときは先頭に空タイトル""を付けるstart /wait直後に%ERRORLEVEL%を確認すればプログラムの成否を判定できる- ループ内で ERRORLEVEL を使う場合は
enabledelayedexpansionと!ERRORLEVEL!が必須 timeoutはプロセス終了の検知ではなく時間待機。タスクスケジューラ経由では動作しないことがある- タスクスケジューラや古い環境では
ping -n 秒数+1 localhost >nulで代替できる

