更新履歴(3件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- `-WhatIf`の出力例(公式ドキュメント掲載のもの)と、棚卸しCSVの中身のイメージを追加しました。`Select-String -Quiet`が非一致時に`$null`を返すため列が空欄になること、`0x1`で終わる問題の要約(タスクスケジューラのエラーではなく起動プログラムの終了コードであること)も追記しました。VBScriptの削除時期は公式に年月が示されていないため、順序だけが公表されているという事実の記載にとどめています。
- 初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21590178)
以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。
小村 豪(2026)「そのバッチファイル、PowerShellに移行すべき? ── cmd/bat資産の棚卸しと移行判断」合同会社小村ソフト. https://comcomponent.com/blog/bat-to-powershell-migration-decision/
- DOI(登録済みアーカイブ)
- 10.5281/zenodo.21590178
- DOI(前回登録した版)
- 10.5281/zenodo.21733120
「サーバー移行で古いbatが大量に見つかった。全部PowerShellに書き直すべきだろうか」。この判断は、ファイルの古さだけでは決まりません。コピーやバックアップ、アプリ起動、夜間の連携ジョブでは、書き換えることで減るリスクと、書き換えによって増えるリスクを分けて考えます。
結論は、安定していて変更予定のない単純なbatは残し、変更・失敗検知・ログ整備が必要なものから移行することです。とくに、batからVBScriptを呼んでいる処理は優先して調べます。cmd/batとVBScriptでは、非推奨化・削除に関する扱いが違うためです。12
この記事は、中小企業の情シス・運用担当者向けに、先に仕分けの基準と棚卸し方法を示し、その後でPowerShellへの移し方と切替手順を説明します。製品の非推奨化・削除計画については、2026年7月時点の公式資料を基準に説明します。
1. まず判断:全部ではなく、価値が出るものから移行する
最初に、対象のbatを次の表で仕分けます。「安定稼働している」だけでなく、変更予定、失敗時の影響、呼び出し先まで見るのがポイントです。
| 論点 | 選択肢(推奨に「推奨」を明記) | 判断の目安 |
|---|---|---|
| 安定稼働中・変更予定なし・単純 | そのまま残す(推奨) / 移行 | アプリ起動ラッパーや数行のコピーは残してよい。書き換え自体が新規リスク |
| VBScript(cscript/wscript)を呼んでいる | 残す / 優先的に移行(推奨) | VBScriptは非推奨→FoD→削除の計画が公式発表済み。放置の先に廃止日が来る2 |
| これから仕様変更・機能追加が入る | batのまま改修 / 移行してから改修(推奨) | 変更のタイミングが移行の好機。改修とテスト整備を同時にやる |
| 失敗検知・ログ・リトライが必要 | batに判定を足す / 移行(推奨) | if errorlevelの網羅は保守困難。try/catchと構造化ログが効く領域3 |
| 夜間ジョブ(タスクスケジューラ起動) | 現状維持 / 移行を検討(推奨) | 無人実行こそ失敗の扱いが命。実行アカウントや「0x1で終わる」問題(7.2節で後述)も合わせて見直す |
| robocopy等の外部コマンドが主役 | コマンドレットで再実装 / 外部コマンドは残して呼ぶ(推奨) | 実績あるコマンドの再実装は損。呼び出しと判定だけPowerShellにする4 |
| 書いた本人が退職済み・仕様不明 | 触らない / 挙動を記録してから判断(推奨) | まず現状の入出力とスケジュールを文書化。不明なまま書き換えない |
単純な起動ラッパーと、失敗を検知できない夜間ジョブを同じ扱いにしないでください。仕様が分からないものは、まず挙動を記録します。移行は「古い順」ではなく「価値が出る順」、着手は「書き換え」ではなく「読み取りと記録」からです。
| いま知りたいこと | 読むところ |
|---|---|
| cmd/batも廃止されるのか、VBScript依存をなぜ急ぐのか | 2章:cmdとVBScriptの扱い |
| 社内のどのbatを調べればよいか | 3章:棚卸しと台帳 |
| PowerShellにすると、失敗の扱いがどう変わるか | 4章:エラー処理・予行演習・テスト |
| 既存のジョブ管理を変えずに移行したい | 5章:入口batと終了コード |
| コマンドをどう書き換えるか | 6章:robocopyと対応表 |
| 配布・定期実行・本番切替で失敗したくない | 7章:パイロットから切替まで |
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 前提を分ける:cmdは残る、VBScriptは削除へ向かう
2.1 PowerShellの推奨は、batの廃止とは別
Windowsには、cmd(コマンドシェル)とPowerShellという2系統のシェルがあります。MicrosoftはWindowsの自動化に、WindowsコマンドやWindows Script HostではなくPowerShellを使うことを推奨しています。新規に書くならPowerShellが公式の方向性です。1
ただし、これはcmdの廃止を意味しません。cmd.exeはWindowsの非推奨機能一覧に載っておらず、batが動かなくなるというアナウンスはありません。新規作成の選択と、安定稼働中の資産を書き換える判断は別です。2
2.2 優先して調べるのは、batが呼び出すVBScript
VBScriptは2023年10月に非推奨化が正式発表されました。要求時インストールの機能(Feature on Demand、FoD)として提供した後、将来のWindowsリリースでOSから削除する計画です。当初はプリインストールされるため直ちに止まるわけではありませんが、方向は削除へ向かっています。Windows Server 2025でもFoDとして提供され、代替にはPowerShellが案内されています。256
2026年7月時点では、参照した公式資料に削除の年月は示されていません。FoDを経て削除するという順序と、具体的な削除日は区別してください。「あと何年」と断定するのではなく、日付が公表されたときに影響範囲を答えられるよう、棚卸しを先に済ませます。25
注意したいのが、cscript //nologo convert.vbs のように、batがVBScriptを呼ぶ構成です。2000年代にはよく使われたパターンで、入口の拡張子が .bat でも、処理の存続はVBScriptに依存します。bat自身だけでなく、呼び出している先まで確認してください。
次の章では、調査対象を見つけるスクリプトを示します。VBScript・VBA資産全体の点検は「VBScript廃止に備えるVBA・社内ツール点検ガイド」で詳しく扱っています。
3. 最初の作業:所在・依存・起動元を台帳にする
3.1 書き換える前に、読み取りで調べる
まずbatとcmdファイルの所在、VBScriptを呼んでいる可能性を洗い出します。次のスクリプトは、調査対象のbatを変更せずに読み取り、結果をCSVと未調査一覧へ保存します。検索対象のパスを自社の環境に合わせ、出力先の C:\audit を用意してから使ってください。
# bat資産の棚卸し: 所在一覧と、VBScript呼び出しの検出(読み取りのみの安全な調査)
# 列挙できなかった場所は棚卸しの穴になるため、-ErrorVariableで記録して後で開示する
$targets = Get-ChildItem -Path 'C:\', 'D:\jobs', '\\fileserver\scripts' -Recurse `
-Include '*.bat', '*.cmd' -File -ErrorAction SilentlyContinue -ErrorVariable enumErrors
$readErrors = @()
$report = foreach ($file in $targets) {
# cscript/wscript/.vbsの呼び出しがあれば「VBScript依存」として要注意フラグ
# 列挙済みのパスは-LiteralPathで渡す([2026]job.batのような角かっこ入りの
# ファイル名を-Pathに渡すとワイルドカード解釈されて別ファイルを見てしまう)
# ロック中などで中身を読めなかったファイルも$readErrorsに記録する
$vbs = Select-String -LiteralPath $file.FullName -Pattern 'cscript|wscript|\.vbs' -Quiet `
-ErrorAction SilentlyContinue -ErrorVariable +readErrors
[pscustomobject]@{
Path = $file.FullName
LastWriteTime = $file.LastWriteTime
UsesVBScript = $vbs
}
}
$report | Sort-Object UsesVBScript -Descending | Export-Csv 'C:\audit\bat-inventory.csv' -NoTypeInformation -Encoding UTF8
# 調べられなかった場所を一覧に残す(空でなければ棚卸しは不完全)
# 列挙に失敗した場所と、列挙できたが中身を読めなかったファイルの両方を含める
@($enumErrors) + @($readErrors) | ForEach-Object { $_.TargetObject } |
Set-Content -Path 'C:\audit\bat-uninspected.txt'
この例は、cscript、wscript、.vbs という文字列を探す一次調査です。検出結果は「要確認の候補」であり、呼び出し関係の確認そのものではありません。2章のとおり実際の呼び出し先も読み、1章の判断表へ当てはめます。
3.2 CSVのTrue・Falseと、未調査一覧をセットで読む
bat-inventory.csv は3列の表です。UsesVBScript が True の行が、Sort-Object によって先頭にまとまります。中身のイメージは次のとおりです。パスと日時は例です。
| Path | LastWriteTime | UsesVBScript |
|---|---|---|
\\fileserver\scripts\nightly\convert.bat |
2009/04/13 18:22:31 | True |
D:\jobs\eod\export_shipping.bat |
2014/11/07 9:41:02 | True |
D:\jobs\backup\copy_master.bat |
2021/06/02 14:05:47 | False |
C:\tools\launch_viewer.bat |
2018/02/19 11:30:15 | False |
-Quiet の非一致時の結果は、入力方法を分けて読みます。この例のように -LiteralPath でファイルを検索し、正常に読めた場合は、一致が $true、非一致が $falseです。そのためCSVには True / False と出ます。Windows PowerShell 5.1とPowerShell 7でこの動作を確認しています。日時の表示形式は実行環境のカルチャに従います。7
一方、文字列をパイプラインや -InputObject で渡して一致しない場合は出力がなく、変数へ代入すると $null になります。公式リファレンスの「非一致は $null」という説明を、ファイル入力へ一律に当てはめないでください。[bool](Select-String ...) で真偽値にそろえても、読み取りエラーと正常な非一致を区別できるようになるわけではありません。7
ただし、Falseや空欄だけを見て「依存なし」と判断しないでください。ファイルを読めず、そのエラーを抑止した場合にもFalseになるためです。列挙できなかった場所は $enumErrors、列挙できても中身を読めなかったファイルは $readErrors に記録し、どちらも bat-uninspected.txt に残しています。この一覧が空でなければ調査に穴があります。読めなかった対象を確認してから、分類を確定します。
また、列挙済みのファイルには -LiteralPath を使っています。[2026]job.bat のような角かっこを含む名前を、ワイルドカードとして解釈させないためです。
3.3 起動元と担当者を足して、移行計画にする
サンプルの上2本はVBScript依存を調べる優先候補、3本目は変更予定や失敗時の影響を見て移行を判断する対象、最後の単純な起動ラッパーは残す候補です。実際の内容を確認したうえで仕分けます。
CSVに起動元(タスクスケジューラ・ジョブ管理ツール・人手)、担当者、失敗時の影響範囲を追記すると、そのまま移行台帳になります。仕様不明のbatは、現在の入出力とスケジュールも記録してください。LastWriteTime が10年以上前なら、作成者が既に不在である可能性も考え、読解の時間を先に確保します。
4. 移行で得るもの:失敗に気づき、安全に試し、テストで守る
4.1 batは、失敗しても次の行へ進む
次は、バックアップ処理で見かける危うい例です。
@echo off
rem 夜間バックアップ(ありがちな危ういバッチ)
xcopy C:\data \\backup01\share\data /E /Y
echo バックアップ完了 >> C:\logs\backup.log
xcopy が共有先に届かず失敗しても、cmdは既定で次の行へ進みます。その結果、実際にはコピーできていないのに、ログへ「バックアップ完了」と書かれます。失敗を検知するには、コマンドごとに判定を書く必要があります。
| 書き方 | 意味・注意点 |
|---|---|
if errorlevel 1 |
終了コードが1以上なら真。1との等値判定ではない |
%ERRORLEVEL% |
同名の環境変数がなければ、現在の終了コードを動的に展開する |
set ERRORLEVEL=0 |
同名の環境変数を作ってしまい、以後の %ERRORLEVEL% が本来の終了コードを隠す |
exit /b 数値 |
バッチから終了コードを返す。入口batでも結果を引き継ぐ必要がある |
もう1つの弱点は文字コードです。日本語環境のcmdは伝統的にShift_JIS系の扱いが中心で、batをUTF-8で保存し直しただけで文字化けしたり、記号入りのパスで誤動作したりすることがあります。文字コードと改行の全体像は「Windowsの文字コードと改行コード - 文字化けとCRLF/LFの基本」を参照してください。
4.2 PowerShellでは、エラーを捕捉して成否を返す
同じ意図をPowerShellで表すと、失敗時の経路をまとめて書けます。以下はエラー処理の骨格であり、既存のrobocopyを再実装するための例ではありません。robocopyは6章のように残して呼びます。
# 夜間バックアップ(PowerShell版の骨格)
$ErrorActionPreference = 'Stop' # エラーを「止まる」側に倒す
try {
# コピー元は「フォルダーの中身」を指す C:\data\* にする。'C:\data' を渡すと、
# コピー先フォルダーが既に存在する2回目以降は data\data と入れ子になってしまう
Copy-Item -Path 'C:\data\*' -Destination '\\backup01\share\data' -Recurse -Force
Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) バックアップ完了"
exit 0
}
catch {
# 何が・どこで失敗したかを構造化された情報のまま記録できる
Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) 失敗: $($_.Exception.Message)"
exit 1
}
$ErrorActionPreference = 'Stop' によってエラーを停止する側へ倒し、try/catch で捕捉します。try/catch が対象とするのは終了するエラーで、後始末は finally にまとめられます。外部コマンドの終了コードは別に判定する点は、6章の対応表でも区別しています。3
コード中のコピー元を C:\data\* としているのは、フォルダー自身ではなく中身を指すためです。C:\data を渡すと、コピー先が既にある2回目以降に data\data と入れ子になることがあります。成否のログだけでなく、繰り返し実行したときの出力も確認します。
4.3 -WhatIfで対象を確認し、Pesterで挙動を守る
変更を伴う対応コマンドレットでは、-WhatIf を使うと操作せずに影響を確認できます。公式資料の Remove-Item Date.csv -WhatIf の例では、ファイルは削除されず、次の説明が表示されます。10
What if: Performing operation "Remove File" on Target "C:\ps-test\date.csv".
Remove-Item C:\logs\*.log -WhatIf のようにワイルドカードを使っても、削除予定の対象ごとに説明が出ます。狙ったファイルだけが対象になるかを、削除前に確かめられます。表示言語は環境の設定によります。batにはこれに対応する共通の仕組みがなく、echo で組み立てたコマンドを目視する方法に頼りがちです。10
-WhatIf を付ければ、どんなスクリプトや外部コマンドでも変更しなくなる、という意味ではありません。対応する処理に適用し、対応しない処理は7章のように出力先を分ける・ログだけを記録する方法で確認します。
Pesterでテストを書けば、確認した挙動を自動で検査し続けられます。「PesterによるPowerShellのテスト整備 ── 運用スクリプトを壊しにくくする実務の型」と「PowerShellのエラー処理と再実行設計」で詳しく扱っています。
移行の価値は、構文が新しくなることよりも、失敗を検知できる・安全に試せる・自動テストで守れることです。単純で失敗がすぐ分かる起動ラッパーには、この利点が小さい場合があります。それが、全部移行しない理由です。
5. 混在期は、入口batを残して中身だけ移す
5.1 ジョブ管理との接点を変えない
一部をbat、一部をPowerShellにする混在期には、batを入口として残し、処理本体だけPowerShellへ移す方法が使えます。ジョブスケジューラや運用手順書が参照するbatのパスを変えずに、中身を置き換えられます。
@echo off
rem 入口bat: 同じフォルダのjob.ps1を呼び出し、終了コードを引き継ぐ
rem %~dp0はこのbat自身のあるフォルダ(公式ドキュメントにも載っている定石)
rem 誰かがset ERRORLEVEL=...した環境変数が残っていると%ERRORLEVEL%が
rem 本物の終了コードを隠すため、呼び出し前に消して動的な値に戻しておく
rem 実行ポリシーはここで上書きしない。-ExecutionPolicy指定は優先度の高い
rem Processスコープになり、管理者が構成したAllSigned等を黙って弱めてしまう
set "ERRORLEVEL="
powershell.exe -NoProfile -File "%~dp0job.ps1"
exit /b %ERRORLEVEL%
%~dp0 は、このbat自身があるフォルダーです。カレントディレクトリに依存せず、同じフォルダーの job.ps1 を -File で指定する書き方は、公式資料にも例があります。11
例はWindows PowerShell 5.1の powershell.exe を呼びます。PowerShell 7を使う場合は、7で動かす前提を確認して呼び出し先を pwsh に変えます。5.1と7は別々にインストールされ、共存できます。使い分けは「Windows PowerShell 5.1とPowerShell 7の違いと移行」を参照してください。
この入口batでは、実行ポリシーを上書きしていません。-ExecutionPolicy はProcessスコープの設定となり、CurrentUserやLocalMachineで設定した方針より優先するためです。グループポリシーより優先するわけではありません。環境側の方針に従わせ、配布設計を7.1節で確認します。12
5.2 終了コードを、外部コマンド・PowerShell・batでつなぐ
結果を受け取る場所は、どの境界を通ったかで変わります。
| 境界 | 受け渡すもの | 確認する点 |
|---|---|---|
| 外部コマンド → PowerShell | $LASTEXITCODE |
コマンド固有の成功・失敗の値で判定する |
| PowerShellスクリプト → プロセス | exit 数値 |
処理の成否を明示的なコードにする |
| PowerShellプロセス → 入口bat | %ERRORLEVEL% |
同名の環境変数で本来の値を隠さない |
| 入口bat → ジョブ管理 | exit /b %ERRORLEVEL% |
受け取った結果をそのまま返す |
たとえばスクリプトが exit 4 で終わると、プロセスの終了コードは4となり、batへ渡ります。PowerShellから外部コマンドを呼んだ結果は、$LASTEXITCODE で確認します。1314
-File で呼ぶ場合、未処理の例外で終了すれば1、正常終了なら0です。ただし、処理上の失敗を自分で判定せずに終われば「失敗したのに0」になり得ます。スクリプト側で成否を判定して exit し、入口batもその値を返すところまで揃えてください。1115
入口の set "ERRORLEVEL=" は、誰かが定義した同名の環境変数を消し、動的な終了コードを参照できるようにする処理です。if errorlevel の以上判定と、%ERRORLEVEL% の展開は、4.1節のとおり別の注意点として扱います。8
6. 書き換える部分と、外部コマンドのまま残す部分を分ける
6.1 robocopyは残し、判定とリトライを整える
robocopyは、リトライ、ミラーリング、ログ機能を備えた実績ある外部コマンドです。PowerShellへ移行するからといって、Copy-Itemで再実装する必要はありません。コマンドはそのまま使い、呼び出しと結果の扱いを整えます。4
注意するのは、終了コードが「0以外は失敗」ではないことです。0はコピー対象なし、1は正常なコピー、8以上は失敗です。また、リトライの既定は100万回、待機は30秒なので、無人ジョブでは待ち続けているように見える原因になります。例では回数と待機時間を絞っています。4
以下の /MIR はミラーリングの指定で、コピー元にないコピー先のファイルも削除対象になります。7章の並行稼働では、新旧が同じコピー先を変更しないようにしてください。4
# robocopyはそのまま使い、判定だけPowerShellで正しく行う
# /r:2 /w:5 ── 既定のリトライ100万回は無人ジョブでは事実上のハングになるため必ず絞る
robocopy 'C:\data' '\\backup01\share\data' /MIR /R:2 /W:5 /NP /LOG+:'C:\logs\robocopy.log'
if ($LASTEXITCODE -ge 8) {
Write-Error "robocopyが失敗しました(終了コード: $LASTEXITCODE)"
exit 1
}
Write-Host "同期完了(終了コード: $LASTEXITCODE)" # 0〜7は成功系
exit 0
この例は、robocopyの結果を $LASTEXITCODE で調べ、8以上ならジョブ全体の失敗として1、それ以外は0を返します。robocopyのコード体系と、ジョブ管理へ返すコードを分けています。414
6.2 対応表は、文字列置換ではなく処理の意図で読む
| batの書き方 | PowerShellでの定石 | 補足 |
|---|---|---|
copy / move / del / md |
Copy-Item / Move-Item / Remove-Item / New-Item |
破壊的操作はまず -WhatIf を付けて予行演習10 |
xcopy / robocopy |
robocopyをそのまま呼ぶ | 再実装しない。$LASTEXITCODE で8以上を失敗と判定414 |
for %%f in (*.csv) do ... |
Get-ChildItem *.csv \| ForEach-Object { ... } |
ファイル名の文字列ではなくオブジェクトが流れる |
if errorlevel 1 goto :error |
try/catch + $ErrorActionPreference = 'Stop' |
外部コマンドは従来どおり終了コード判定3 |
set VAR=value |
$var = 'value'(環境変数は $env:VAR) |
プロセス環境変数と変数を区別できる |
call :sub / goto |
function |
引数に型と検証を付けられる |
findstr |
Select-String |
一致行がオブジェクトで返り、後段で加工できる |
>> log.txt |
Start-Transcript / Add-Content |
実行記録の丸ごと採取はTranscriptが手軽 |
rem |
# |
─ |
PowerShellでは、ファイル名などの文字列だけでなく、オブジェクトをパイプラインで渡せます。変数と環境変数を区別でき、関数には引数の型や検証を付けられます。batの単語を置換するのではなく、同じ処理の意図を表すために対応表を使ってください。
基本語彙やコマンドレットの探し方は「PowerShellコマンドの基本 ── まず覚える操作と安全な使い方」にまとめています。
7. 配布・定期実行を確認してから、パイロットと並行稼働へ進む
7.1 実行ポリシーを、配布方法と一緒に決める
3章で棚卸しと分類を済ませたら、影響の小さい1本をパイロットに選び、5章の入口bat方式で移します。この段階で、どのPowerShellを、どのアカウントから、どのポリシーで起動するかも確認します。
既定値の違いにも注意してください。Windows PowerShell 5.1のクライアントOSがRestricted、Windows ServerとPowerShell 7がRemoteSignedです。Restrictedではスクリプト実行ができません。RemoteSignedではローカル作成のスクリプトは実行できますが、インターネット由来のマークがある未署名スクリプトはブロックされます。すべての環境を「既定はRemoteSigned」と一括りにしないでください。設定済みの環境では、既定値ではなく実際に有効なポリシーが判断対象です。12
共有フォルダー配布で止まる場合も、まず原因を調べます。恒久策は、署名運用やグループポリシーによる統一です。入口batへ無条件に -ExecutionPolicy Bypass を足して解決扱いにせず、組織が意図した方針か確認してください。実行ポリシー自体は、ユーザーの操作を完全に封じるセキュリティ境界ではありません。12
配布時の切り分けと署名運用は「PowerShellの実行ポリシーとスクリプト署名」で説明しています。
7.2 手動では動くのに0x1になるなら、実行環境を比べる
タスクの「前回の実行結果」に出る 0x1 は、起動されたプログラムが終了コード1を返したという意味です。この値だけで、タスクスケジューラ自身の障害とは判断できません。
手動実行は成功し、定期実行だけ失敗する場合は、まず環境差を調べます。典型例は、タスクの「開始(オプション)」が未指定でカレントディレクトリが C:\Windows\System32 になり、相対パス前提の処理が壊れるケースです。ほかにも、プロファイル由来の環境変数、マップされたネットワークドライブ、実行アカウント、実行ポリシーの違いがあります。
5章の %~dp0 はスクリプトの所在を解決しますが、定期実行のアカウントや環境まで同じにするものではありません。台帳に記録した起動条件で確認してください。切り分けの詳細は「タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計」を参照してください。
7.3 新旧の出力を比較し、切り戻せる状態で切り替える
パイロット後は、旧batと新スクリプトを一定期間並行して確認します。同じ本番データへ二重に書き込むのではなく、出力先を分けて比較します。対応する操作には -WhatIf を使い、新側は最初にログだけを記録する方法もあります。
切り戻しは、ジョブの向き先を旧batへ戻せるようにしておきます。入口batを残す場合も、旧処理へ戻す手段を確保したうえで切り替えます。出力の突合と復旧手順を用意してから旧batを退役させることで、移行そのものによる事故のリスクを抑えられます。
全体の順序は、棚卸し → リスク分類 → 小さなパイロット → 並行稼働と比較 → 切替です。実行ポリシー、失敗時の終了コード、ログ、定期実行の条件も、この流れの中で確認します。
8. まとめ
batが古いという理由だけで、全部をPowerShellへ移行する必要はありません。安定稼働していて変更予定のない単純な処理は残し、変更が入るもの、失敗検知・ログ・リトライが必要なものから移します。VBScriptを呼ぶbatは、入口の拡張子にかかわらず優先して依存を調べます。
移行するときも、全部を置き換える必要はありません。入口batはジョブ管理との接点として残せますし、robocopyも外部コマンドのまま利用できます。処理本体をPowerShellへ移し、try/catch、終了コード、対応操作の -WhatIf、Pesterによるテストで、失敗の検知と確認の仕組みを整えます。310134
最初の作業は、3章の読み取り調査です。未調査の場所を明示し、入出力と起動条件を記録してから、1章の判断表に当てはめてください。配布と切り戻しまで含めて準備し、価値のあるものから段階的に進めるのが実務の定石です。
関連記事
- VBScript廃止に備えるVBA・社内ツール点検ガイド
- PowerShellコマンドの基本 ── まず覚える操作と安全な使い方
- タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計
- Windowsの文字コードと改行コード - 文字化けとCRLF/LFの基本
- PowerShellの実行ポリシーとスクリプト署名
- PowerShellのエラー処理と再実行設計
関連する相談領域
合同会社小村ソフトでは、bat・VBScript・PowerShellが混在したレガシー資産の棚卸しと移行計画の策定、夜間ジョブのPowerShell化と運用設計、移行に伴う不具合の調査を扱っています。「書いた人がもういない」バッチの解読からでもご相談いただけます。
参考リンク
-
Microsoft Learn, Windows Commands. Windowsにコマンドシェル(cmd)とPowerShellの2つのシェルがあること、最も堅牢で最新のWindows自動化にはWindowsコマンドやWindows Script HostではなくPowerShellの使用を推奨するという公式記述について。 ↩ ↩2
-
Microsoft Learn, Deprecated features for Windows client. VBScriptの非推奨が2023年10月に発表されたこと、今後のWindowsリリースでFeature on Demandとして提供された後にOSから削除される計画であることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Try_Catch_Finally. try/catch/finallyブロックが文終了エラーとスクリプト終了エラーを処理すること、catchで例外型を指定できること、finallyがエラーの有無にかかわらず実行され後始末に使えることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, robocopy. robocopyの終了コード体系(0=コピー対象なし、1=全ファイルを正常コピー、8以上=1件以上の失敗)、リトライ既定値が/r:1000000(100万回)・待機既定値が/w:30秒であること、ミラーリングやログなどのオプションについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Resources for deprecated features. VBScriptのFeature on Demandが当初はプリインストールされ、退役準備の間は中断なく使用できることについて。 ↩ ↩2
-
Microsoft Learn, Features removed or no longer developed in Windows Server. Windows Server 2025でVBScriptがFoDとして提供され後のリリースで削除されること、タスク自動化やスクリプトの代替としてPowerShellの使用が案内されていることについて。 ↩
-
Microsoft Learn, Select-String. 正規表現による検索、-LiteralPath、-Quietの説明。-Quiet節は非一致を$nullと説明しているが、ファイル入力と文字列入力は区別が必要。PowerShellの公開実装 SelectStringCommand / MatchString.cs のProcessRecordでは、ファイル入力の非一致時にBoxed.Falseを出力する。2026年9月12日にWindows PowerShell 5.1.26100.33296とPowerShell 7.6.5で、ファイル入力の非一致・空ファイルがFalse、文字列入力の非一致が無出力で代入結果はnull、読めないファイルのエラー抑止時にもFalseになることを確認した。 ↩ ↩2
-
Microsoft Learn, if. if errorlevel
が「直前のプログラムの終了コードがnumber以上のときに真」という以上判定であること、%errorlevel%がERRORLEVELという環境変数が存在しない前提の展開であり、同名の環境変数が定義されているとその値が返ることについて。 ↩ ↩2 -
Microsoft Learn, exit. exit /b
がバッチスクリプトを終了しERRORLEVEL環境変数に指定値を設定すること、cmd自体を終了する場合はプロセス終了コードに設定されることについて。 ↩ -
Microsoft Learn, about_CommonParameters. システムやデータに変更を加えるコマンドレットが提供するリスク軽減パラメーター(-WhatIf/-Confirm)、-WhatIfがコマンドを実行せず影響の説明だけを表示することについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Pwsh. pwshの-Fileパラメーターの使い方、バッチスクリプトから呼ぶ際に%~dp0で実行ディレクトリを表す公式の例、-Fileでスクリプト終了エラーが起きた場合に終了コードが1になること、exitコマンドで終了した場合はその数値が終了コードになることについて。 ↩ ↩2
-
Microsoft Learn, about_Execution_Policies. Windows上のPowerShell 7の既定の実行ポリシーがRemoteSignedであること、RemoteSignedがインターネット由来のスクリプトに信頼された発行元の署名を要求し、ローカルで作成したスクリプトには署名を要求しないこと、各ポリシー(Restricted/AllSigned/Bypass等)の意味について。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Language_Keywords. exitキーワードが$LASTEXITCODEを設定し、cmd.exe側では%ERRORLEVEL%に対応すること、pwsh -Fileで実行したスクリプトのexit 4が呼び出し元の%ERRORLEVEL%で4として観測される実例、exit文が無い場合は正常終了で0・未処理例外で1になることについて。 ↩ ↩2
-
Microsoft Learn, about_Automatic_Variables. 自動変数$LASTEXITCODEにネイティブプログラムやスクリプトの終了コードが格納されること、-Fileでのスクリプト実行時の値の決まり方(例外で1、exit指定値、正常終了で0)について。 ↩ ↩2 ↩3
-
Microsoft Learn, about_PowerShell_exe. Windows PowerShell 5.1のpowershell.exeにおける-Fileパラメーターの仕様、cmd.exeから環境変数を渡す場合の%windir%構文、終了コードの扱いについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
PowerShellスクリプトの引数設計とモジュール化 ── 「動くスクリプト」から「人に渡せるスクリプト」へ
PowerShellスクリプトを他人に渡せる品質に引き上げる手順を整理します。paramブロックと[CmdletBinding()]、入力検証、パイプライン入力、-WhatIf対応、.psm1モジュール化、社内共有とGit管理の勘所まで解説します。
Windows PowerShell 5.1とPowerShell 7の違い ── 社内スクリプト移行の実務ガイド
Windows PowerShell 5.1とPowerShell 7の関係(共存とpwsh.exe)、5.1は新機能追加なしという公式方針、エンコーディング差による文字化け、#Requiresでの防御、タスクスケジューラ更新まで移行手順を整理します。
PowerShellスクリプトが遅いときに見るところ ── 配列・パイプライン・突合の勘所
PowerShellスクリプトが遅い原因の定番を整理します。配列の+=がO(n^2)になる理由、パイプラインとforeachの差、突合のハッシュテーブル化、ファイルI/Oの改善、そして正しい測り方までを実務目線で解説します。
Write-Hostをやめる ── PowerShellの出力ストリームとログ設計
PowerShellの6つの出力ストリームの使い分け、Write-Hostが抱える問題と正しい使いどころ、関数の戻り値が汚れる原因、-Verboseや-InformationVariableによる呼び出し側制御、構造化ログの残し方を整理します。
PowerShellの並列処理 ── ForEach-Object -Parallelとジョブの使い分け
ForEach-Object -Parallel・Start-ThreadJob・Start-Jobの違いと使い分け、$using:とスレッド安全性、ThrottleLimitの決め方、かえって遅くなるケースまでを実務目線で整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
既存資産活用・移行支援
COM / ActiveX / OCX、32bit / 64bit 制約を抱える既存資産の活用と移行を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 社内のバッチファイルはすべてPowerShellに移行すべきですか?
- 全部移行は推奨しません。何年も安定稼働していて変更予定のないバッチは、書き換え自体が新しい障害リスクになります。移行の優先度が高いのは、これから変更が入るもの、エラー処理やログが必要なもの、そしてVBScript(cscript/wscript)を呼んでいるものです。判断表で仕分けて、価値のあるものから段階的に移行するのが実務の定石です。
- cmd.exeやバッチファイルは廃止されるのですか?
- cmd.exeはWindowsの非推奨機能一覧に載っておらず、廃止のアナウンスはありません。ただしMicrosoftはWindowsの自動化にはコマンドやWSHではなくPowerShellの使用を推奨すると明言しています。一方VBScriptは2023年10月に非推奨が正式発表され、今後のWindowsでは要求時インストールの機能(Feature on Demand)になった後、OSから削除される段階的な計画が公開されています。batそのものは動き続けますが、batから呼んでいるVBScriptは確実に動かなくなる日が来ます。
- batファイルからPowerShellスクリプトを呼ぶにはどう書けばよいですか?
- powershell.exe(またはPowerShell 7のpwsh.exe)に-Fileパラメーターでスクリプトを渡します。batと同じフォルダのスクリプトなら「powershell.exe -NoProfile -File "%~dp0job.ps1"」のように%~dp0で場所を解決するのが公式ドキュメントにも載っている書き方です。なお-ExecutionPolicy指定は優先度の高いProcessスコープになり、管理者が構成したポリシーを弱めてしまうため、bat側では付けず環境側の実行ポリシー設計に従わせます。スクリプト側でexit 数値を返せば、bat側の%ERRORLEVEL%にそのまま渡るため、既存のジョブ管理の仕組みを保ったまま中身だけPowerShell化できます。
- batの%ERRORLEVEL%にはどんな罠がありますか?
- 代表的なのは「if errorlevel 1」が『終了コードが1以上なら真』という以上判定である点です(等値判定ではありません)。また%errorlevel%は環境変数ERRORLEVELが定義されていない前提の動的展開なので、set ERRORLEVEL=0のように自分で定義してしまうと以後は常にその値が返ります。さらにbatはコマンドが失敗しても既定では次の行へ進むため、判定を書き忘れた失敗は静かに握り潰されます。この種の罠の少なさが、エラー処理を持つPowerShellへ移行する動機になります。
- robocopyを使っているバッチはPowerShellでどう書き換えますか?
- 書き換えないのが定石です。robocopyはリトライ・ミラーリング・ログなど実績のある機能を持つ優秀な外部コマンドで、PowerShellからそのまま呼べます。Copy-Itemで再実装するのは手間の割に信頼性が下がります。注意点は終了コードの解釈で、robocopyは0〜8以上の値を返し、8以上が失敗、1は正常なコピー完了です。PowerShell側では$LASTEXITCODEを見て「8以上なら異常」と判定します。
- PowerShellスクリプトの配布で実行ポリシーに引っかかりました。どうすべきですか?
- 実行ポリシーがRemoteSignedの場合は、インターネット由来のマーク付きスクリプトに署名を要求します。共有フォルダ経由の配布でブロックされる場合、まず原因を確認し、恒久策としては署名運用か、グループポリシーでの統一が本筋です。バッチからの呼び出しで-ExecutionPolicy Bypassを付ける方法は手軽ですが、組織のポリシー設計として意図した状態か確認した上で使ってください。