그 배치 파일, PowerShell로 이전해야 할까요? ── cmd/bat 자산 조사와 이전 판단

· 업데이트: · · PowerShell, Windows, 배치 파일, cmd, VBScript, 기존 자산 활용, 운영 개선, 스크립트

수정 이력(5건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex P2에 따라 본문에 남은 일본어 잔여 표현을 한국어로 고쳤습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
글 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약, 그림, 상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응해 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
`-WhatIf`의 출력 예(공식 문서에 실린 것)와 자산 조사 CSV의 내용 이미지를 추가했습니다. `Select-String -Quiet`가 비일치 시 `$null`을 반환해 열이 빈칸이 되는 점, `0x1`로 끝나는 문제의 요약(작업 스케줄러 오류가 아니라 기동된 프로그램의 종료 코드라는 점)도 보강했습니다. VBScript 삭제 시기는 공식 문서에 연월이 나와 있지 않으므로, 순서만 공개되어 있다는 사실 기재에 그칩니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22174822)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「그 배치 파일, PowerShell로 이전해야 할까요? ── cmd/bat 자산 조사와 이전 판단」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/bat-to-powershell-migration-decision/

DOI(등록된 아카이브)
10.5281/zenodo.22174822
DOI(마지막 등록 버전)
10.5281/zenodo.22174823

「서버 이전 타이밍에 20년짜리 배치 파일이 산더미처럼 나왔다. 이거, 전부 PowerShell로 다시 작성해야 할까요」── 레거시 자산 상담에서 이 질문은 단골입니다. 복사와 백업, 앱 기동 래퍼, 야간 연계 작업. cmd의 배치(bat)는 지금도 Windows 운영 현장을 조용히 떠받치고 있습니다.

결론부터 말하면, 전부 다시 작성할 필요는 없습니다. bat가 당장 동작하지 않게 될 예정은 없고, 동작 중인 것을 재작성하는 일 자체가 장애 위험이기 때문입니다. 한편 「그대로」가 위험한 bat도 분명히 있습니다. VBScript를 호출하는 것, 오류를 삼키는 것, 앞으로 변경이 들어갈 것. 이 판별 없이 「전부 남긴다」도 「전부 이전한다」도, 어느 쪽이든 사고의 씨앗입니다.

이 글에서는 중소기업의 정보시스템·운영 담당자를 대상으로, bat 자산 조사와 이전 판단 기준, bat 특유의 함정, 이전 시기에 bat와 PowerShell을 공존시키는 작성법, 재작성 대응표까지 정리합니다.

1. 먼저 결론

  • cmd.exe와 bat에는 폐지 예정이 없지만, Microsoft는 Windows 자동화에 PowerShell 사용을 권장한다고 명확히 밝히고 있습니다. 새로 작성한다면 PowerShell, 이것이 공식 방향입니다.1
  • VBScript는 2023년 10월에 deprecated가 공식 발표되었고, 요청 시 설치(Feature on Demand)화를 거쳐 OS에서 삭제되는 단계적 계획이 공개되어 있습니다. bat에서 cscript / wscript로 VBScript를 호출하는 자산은 이전이 가장 필수인 그룹입니다.23 대체로는 PowerShell이 공식적으로 안내되어 있습니다.4
  • 이전 판단은 「전부」가 아니라 분류입니다. 안정 가동 중이고 변경 예정 없음→남긴다. 변경이 들어간다·오류 처리가 필요하다·로그가 필요하다→이전. VBScript 의존→우선적으로 이전. 4장의 판단표에서 정리합니다.
  • bat의 구조적 약점은 오류가 나도 멈추지 않는 것, %ERRORLEVEL%의 함정, 문자 코드입니다. if errorlevel 1은 「1 이상」 판정이며5, %errorlevel%은 같은 이름의 환경 변수를 정의하면 깨집니다.5
  • PowerShell로 이전해서 얻는 것은 객체 파이프라인, try/catch에 의한 오류 처리6, -WhatIf에 의한 예행 연습7, Pester에 의한 테스트입니다. 「실패를 알아챌 수 있다·안전하게 시험할 수 있다·자동으로 테스트할 수 있다」가 이전의 본질적 가치입니다.
  • 혼재기에는 bat에서 powershell.exe / pwsh를 -File로 호출하는 것이 현실적인 해법입니다. 스크립트의 exit이 반환하는 종료 코드는 bat 쪽의 %ERRORLEVEL%에 그대로 전달되므로, 기존 작업 관리를 유지한 채 내용만 바꿀 수 있습니다.89
  • robocopy 같은 우수한 외부 명령은 재작성하지 않고, PowerShell에서 그대로 호출합니다. 종료 코드는 0~8 이상이며 8 이상이 실패라는 독특한 체계이므로, $LASTEXITCODE로 판정만 정확히 작성합니다.1011
  • 배포 시에는 실행 정책을 설계에 넣습니다. 기본값은 Windows PowerShell 5.1의 클라이언트 OS에서는 Restricted(스크립트 실행 불가), Windows Server와 PowerShell 7에서는 RemoteSigned입니다. RemoteSigned라도 인터넷 출처 표시가 붙은 미서명 스크립트는 차단됩니다.12

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. 왜 지금 생각해야 하는가 ── cmd는 남고, VBScript는 사라진다

먼저 전제가 되는 사실관계를 정리합니다. Windows의 명령줄 셸에는 cmd(명령 셸)와 PowerShell의 2계통이 있으며, Microsoft의 공식 문서는 「가장 견고하고 최신인 Windows 자동화에는 Windows 명령이나 Windows Script Host가 아니라 PowerShell 사용을 권장한다」고 명시하고 있습니다.1 다만 권장은 어디까지나 권장이며, cmd 자체는 Windows의 deprecated 기능 목록에 올라 있지 않습니다. 기존 bat가 동작하지 않게 될 예정은, 현시점에서는 없습니다.

대조적인 것이 VBScript입니다. 2023년 10월, VBScript의 deprecated(deprecation)가 공식 발표되었습니다. 앞으로의 Windows 릴리스에서는 요청 시 설치 기능(Feature on Demand)으로 제공되고, 그 후 OS에서 삭제되는 계획이 공개되어 있습니다.2 처음에는 사전 설치된 상태로 제공되므로 오늘내일 멈추는 것은 아니지만3, 방향은 「삭제」로 확정되어 있습니다. Windows Server 2025에서도 마찬가지로 FoD화되어 있으며, 대체로서 PowerShell로의 이전이 공식적으로 안내되어 있습니다.4

시기에 대해 정확히 적어 둡니다. 2026년 7월 시점에 Microsoft가 공식 문서에 적어 둔 것은 「FoD로 제공한 뒤, 장래 Windows 릴리스에서 삭제한다」는 순서뿐이며, 삭제 연월은 나와 있지 않습니다.23 즉 「앞으로 몇 년」에는 공식적으로 답을 낼 수 없는 상태입니다. 이는 「아직 멀었으니 방치해도 된다」가 아니라, 기한이 발표된 뒤에 조사를 시작하면 늦는다는 뜻으로 받아들이세요. 자산 조사(7장)만이라도 먼저 마쳐 두면, 삭제 시기가 발표된 시점에 「우리 쪽에서 영향받는 것은 이 3개」라고 즉시 답할 수 있습니다.

여기서 중요한 것은, bat 자산 안에 VBScript가 숨어 있다는 점입니다. bat에서 cscript //nologo convert.vbs처럼 VBScript를 호출하는 구성은 2000년대의 단골 패턴이었습니다. 이 구성의 bat는 「bat이니까 안전」이 아니라 「VBScript와 운명 공동체」입니다. 자산 조사에서는 bat 파일 자체에 더해, bat가 호출하는 대상을 반드시 확인하세요(7장에 스캔용 스크립트를 실었습니다). 사내 VBScript·VBA 자산 전체 점검은 「VBScript 폐지에 대비하는 VBA·사내 도구 점검 가이드」에서 자세히 다루고 있습니다.

3. bat의 함정과, PowerShell로 얻는 것

이전 판단의 재료로서, bat의 구조적 약점을 구체적으로 봅니다. 다음 배치는 백업 처리로 자주 보는 형태입니다.

@echo off
rem 야간 백업(흔히 보는 위험한 배치)
xcopy C:\data \\backup01\share\data /E /Y
echo 백업 완료 >> C:\logs\backup.log

이 배치에는 알아채기 어려운 문제가 3가지 들어 있습니다.

  • 오류가 나도 멈추지 않습니다. xcopy가 공유 대상에 닿지 못해 실패해도, cmd는 기본값으로 다음 줄로 진행하고, 로그에는 「백업 완료」라고 기록됩니다. 실패 감지에는 매번 if errorlevel 판정을 직접 써야 합니다.
  • %ERRORLEVEL%에는 함정이 있습니다. if errorlevel 1은 「종료 코드가 1과 같다」가 아니라 「1 이상」 판정입니다.5 또한 %errorlevel%이라는 작성법은 「ERRORLEVEL이라는 환경 변수가 정의되어 있지 않으면 현재 종료 코드로 확장된다」는 사양이므로, 누군가가 set ERRORLEVEL=0이라고 쓰는 순간 이후의 판정은 모두 깨집니다.5 exit /b 숫자로 종료 코드를 반환하는 사양13과 맞물려, 정확히 쓰려면 상응하는 지식이 필요합니다.
  • 문자 코드 사고. 일본어 환경의 cmd는 전통적으로 Shift_JIS 계열의 세계이며, UTF-8로 다시 저장한 bat가 깨지거나 기호가 든 경로에서 오동작하는 사고는 지금도 일어납니다. 이 문제의 전체 그림은 「Windows의 문자 코드와 개행 코드 - 문자 깨짐과 CRLF/LF의 기본」을 참조하세요.

같은 처리를 PowerShell로 쓰면, 실패 다루기가 구조적으로 달라집니다.

# 야간 백업(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
}

try/catch는 문을 종료시키는 오류를 포착하고, 뒷정리는 finally에 쓸 수 있습니다.6 나아가 PowerShell에는 파괴적 조작을 실행하지 않고 「무엇이 일어났을지」만 표시하는 -WhatIf가 공통 메커니즘으로 마련되어 있어7, 재작성한 스크립트를 운영 대상을 상대로 안전하게 예행 연습할 수 있습니다.

-WhatIf는 말로 설명해도 감이 잘 오지 않으므로, 실제 보이는 모습을 듭니다. 공식 문서의 예에서는 Remove-Item Date.csv -WhatIf를 실행해도 파일은 삭제되지 않고, 다음 1줄만 표시됩니다.7

What if: Performing operation "Remove File" on Target "C:\ps-test\date.csv".

대상 1건당 1줄이 나오므로, Remove-Item C:\logs\*.log -WhatIf처럼 와일드카드로 썼을 때도 삭제될 예정의 파일이 실행 전에 전부 나열됩니다. 「이 조건으로 정말 노린 것만 대상이 되는지」를 운영 데이터를 깨뜨리지 않고 확인할 수 있다는 뜻입니다(표시 메시지의 언어는 환경 설정에 따릅니다). bat에는 대응하는 메커니즘이 없으므로, echo로 명령을 조립해 눈으로 보는 수작업에 의존할 수밖에 없습니다.

여기에 Pester로 테스트를 쓰면 「동작할 것」을 자동으로 계속 확인할 수 있습니다(「Pester로 PowerShell 테스트 정비 ── 운영 스크립트를 깨지기 어렵게 하는 실무의 형」 참조). 오류 처리와 재실행 설계 자체는 동시에 공개한 「PowerShell의 오류 처리와 재실행 설계」에서 깊게 다룹니다.

즉 이전으로 얻는 것은 구문의 새로움이 아니라, 실패를 알아챌 수 있다·안전하게 시험할 수 있다·테스트로 지킬 수 있다는 운영 품질입니다. 반대로 말하면, 실패해도 바로 알 수 있는 단순한 기동 래퍼 같은 bat에는 이 가치가 거의 없습니다. 그래서 분류가 필요해집니다.

4. 전부 이전하지 않는다 ── 분류 판단표

논점 선택지(권장에 「권장」을 명시) 판단 기준
안정 가동 중·변경 예정 없음·단순 그대로 남긴다(권장) / 이전 앱 기동 래퍼나 몇 줄짜리 복사는 남겨도 된다. 재작성 자체가 신규 위험
VBScript(cscript/wscript)를 호출한다 남긴다 / 우선적으로 이전(권장) VBScript는 deprecated→FoD→삭제 계획이 공식 발표됨. 방치의 끝에 폐지일이 온다2
앞으로 사양 변경·기능 추가가 들어간다 bat 그대로 수정 / 이전한 뒤 수정(권장) 변경 타이밍이 이전하기 좋은 때. 수정과 테스트 정비를 동시에 한다
실패 감지·로그·재시도가 필요하다 bat에 판정을 더한다 / 이전(권장) if errorlevel을 빠짐없이 쓰는 것은 유지보수가 어렵다. try/catch와 구조화 로그가 효과를 보는 영역6
야간 작업(작업 스케줄러 기동) 현상 유지 / 이전을 검토(권장) 무인 실행이야말로 실패 다루기가 생명. 실행 계정이나 「0x1로 끝난다」 문제(7장에서 후술)도 함께 다시 본다
robocopy 등 외부 명령이 주역 cmdlet으로 재구현 / 외부 명령은 남겨 호출(권장) 실적 있는 명령의 재구현은 손해. 호출과 판정만 PowerShell로 한다10
작성자 본인이 퇴직·사양 불명 손대지 않는다 / 동작을 기록한 뒤 판단(권장) 먼저 현재 입출력과 일정을 문서화. 불명한 채로 재작성하지 않는다

막혔을 때의 원칙은 두 가지입니다. 첫째, 이전은 「가치가 나오는 순」이며 「오래된 순」이 아니다. 둘째, 손댈 거면 읽기(자산 조사와 기록)부터 시작한다.

5. 혼재기의 현실적 해법 ── bat에서 PowerShell을 호출하고, 종료 코드를 주고받는다

분류 결과, 많은 현장은 「일부는 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에서의 호출 예로 제시하는 것입니다.9 PowerShell 7로 돌리려면 powershell.exepwsh로 바꾸기만 하면 됩니다(5.1과 7은 따로 설치되어 공존할 수 있습니다. 쓰임새 구분은 동시에 공개한 「Windows PowerShell 5.1과 PowerShell 7의 차이와 이전」 참조).

종료 코드 전달은 다음과 같이 정리할 수 있습니다.

  • 스크립트가 exit 4로 끝나면 프로세스 종료 코드가 4가 되고, bat 쪽의 %ERRORLEVEL%로 받을 수 있습니다. 이 연계는 공식 문서에 실례와 함께 기재되어 있습니다.8
  • -File로 호출한 경우, 스크립트가 처리되지 않은 예외로 죽으면 종료 코드는 1, 정상 종료면 0입니다. 「실패했는데 0」을 피하려면, 스크립트 쪽에서 성패를 판정해 명시적으로 exit하는 것이 안전합니다.914
  • PowerShell에서 외부 명령을 호출한 결과는 자동 변수 $LASTEXITCODE에 들어갑니다.11

robocopy를 주역으로 한 bat는 이 형태의 좋은 예입니다. robocopy는 재시도(기본값으로 무려 100만 회·대기 30초)나 미러링, 로그 기능을 갖춘 실적 있는 명령이며10, Copy-Item으로 재구현할 이유는 없습니다. 다만 종료 코드 체계가 독특해서, 0은 「복사 대상 없음」, 1이 「정상 복사」, 8 이상이 실패입니다.10 「0 이외는 이상」이라는 일반 규칙으로 판정하면, 정상적인 복사를 이상으로 오판합니다.

# 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

6. 재작성 대응표 ── bat의 어휘를 PowerShell로

실제로 재작성할 때의 대응표입니다. 기계적인 치환이 아니라 「같은 의도를 어떻게 표현하는가」의 대조표로 사용하세요.

bat의 작성법 PowerShell에서의 정석 보충
copy / move / del / md Copy-Item / Move-Item / Remove-Item / New-Item 파괴적 조작은 먼저 -WhatIf를 붙여 예행 연습7
xcopy / robocopy robocopy를 그대로 호출 재구현하지 않는다. $LASTEXITCODE로 8 이상을 실패로 판정1011
for %%f in (*.csv) do ... Get-ChildItem *.csv \| ForEach-Object { ... } 파일 이름 문자열이 아니라 객체가 흐른다
if errorlevel 1 goto :error try/catch + $ErrorActionPreference = 'Stop' 외부 명령은 종래대로 종료 코드 판정6
set VAR=value $var = 'value'(환경 변수는 $env:VAR) 프로세스 환경 변수와 변수를 구분할 수 있다
call :sub / goto function 인수에 형과 검증을 붙일 수 있다
findstr Select-String 일치 행이 객체로 반환되어, 후단에서 가공할 수 있다
>> log.txt Start-Transcript / Add-Content 실행 기록 통째 채취는 Transcript가 간편
rem #

PowerShell 쪽의 기본 어휘(cmdlet 찾는 법, 파이프라인, 확인하는 법)는 「PowerShell 명령의 기본 ── 먼저 익히는 조작과 안전한 사용법」에 정리해 두었습니다.

7. 단계적 이전 절차 ── 자산 조사→분류→파일럿→병행 가동

마지막으로 진행 방식을 4단계로 정리합니다.

(1) 자산 조사. 먼저 읽기 전용 조사부터 시작합니다. bat의 소재와 VBScript 의존 여부를 기계적으로 가려냅니다.

# 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'

완성되는 bat-inventory.csv는 3열뿐인 소박한 표입니다. UsesVBScriptTrue인 행이 우선 이전 후보이며, 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 (빈칸)
C:\tools\launch_viewer.bat 2018/02/19 11:30:15 (빈칸)

UsesVBScript 열이 빈칸이 되는 것은 결함이 아닙니다. Select-String-Quiet는 일치했을 때 $true를 반환하고, 일치하지 않았을 때는 $false가 아니라 $null을 반환하는 사양이기 때문입니다.15 「True인가 빈칸인가」로 정렬하면 실용상 곤란하지 않지만, False로 명시하고 싶다면 $vbs = [bool](Select-String ...)처럼 진릿값으로 변환한 뒤 저장하세요(일시 표시 형식은 실행 환경의 컬처를 따릅니다).

이 4행만으로도 판단은 상당히 진행됩니다. 위 2개는 4장의 「우선적으로 이전」, 3번째는 「변경 예정이 있으면 이전」, 마지막 기동 래퍼는 「그대로 남긴다」. 실무에서는 여기에 기동 원(작업 스케줄러/작업 관리 도구/수작업)과 담당자 열을 손으로 더해, 그대로 이전 계획 대장으로 씁니다. LastWriteTime이 10년 이상 전인 것은 「작성자 본인이 이미 없다」 후보로, 먼저 해독 시간을 확보해 두면 안전합니다.

(2) 위험 분류. 자산 조사 결과를 4장의 판단표에 대입해 「남긴다」「이전」「우선 이전(VBScript 의존)」으로 나눕니다. 더불어 각 bat의 기동 원(작업 스케줄러, 작업 관리 도구, 수작업)과 실패 시 영향 범위를 기록합니다. 여기서 짚어 둘 것이 「0x1로 끝난다」 문제입니다. 작업의 「이전 실행 결과」에 나오는 0x1작업 스케줄러 자신의 오류가 아니라, 기동된 프로그램이 종료 코드 1을 반환했다는 뜻이며, 원인은 거의 「수동 실행과 정기 실행의 환경 차」에 있습니다. 전형은 작업 쪽에서 「시작(옵션)」을 지정하지 않아 현재 디렉터리가 C:\Windows\System32가 되고, 상대 경로 전제의 bat가 깨지는 패턴입니다. 그 외에 프로필 유래의 환경 변수나 매핑된 네트워크 드라이브가 무인 세션에 존재하지 않음, 실행 정책이 다름, 같은 원인이 있습니다. 원인을 나누는 절차는 「작업 스케줄러의 작업이 실행되지 않음·0x1로 끝남 ── 원인 분리와 안전한 운영 설계」를 참조하세요.

(3) 파일럿. 영향이 작은 1개를 골라 5장의 입구 bat 방식으로 재작성합니다. 이때 실행 정책을 설계에 넣습니다. Windows의 기본값은 RemoteSigned이며, 로컬에서 만든 스크립트는 동작하지만 인터넷 출처 표시가 붙은 미서명 스크립트는 차단됩니다.12 공유 폴더 배포에서 걸리는 경우의 원인 분리와, 서명 운영까지 포함한 본래의 설계는 동시에 공개한 「PowerShell의 실행 정책과 스크립트 서명」에 정리되어 있습니다.

(4) 병행 가동. 옛 bat와 새 스크립트를 일정 기간 나란히 돌립니다. 쓰기 대상을 나눠 출력을 맞춰 보고, 새 쪽은 처음 -WhatIf나 로그만으로 돌리고, 되돌리기는 작업이 가리키는 대상만 bat로 되돌리게 해 둔다 ── 여기까지 준비한 뒤에 옛 bat를 퇴역시키면, 이전 자체가 사고가 되는 일은 거의 없습니다.

8. 정리

  • cmd와 bat에는 폐지 예정이 없지만, 공식 권장은 PowerShell입니다. 한편 VBScript는 deprecated→FoD→삭제 계획이 발표되어 있으며, bat에서 호출되는 VBScript가 최우선 이전 대상입니다.
  • 이전은 「전부」가 아니라 분류입니다. 안정 가동·변경 없는 bat는 남기고, 변경이 들어가는 것·오류 처리와 로그가 필요한 것·VBScript 의존인 것부터 이전합니다.
  • bat는 오류가 나도 멈추지 않으며, if errorlevel 1은 1 이상 판정, %errorlevel%은 같은 이름 환경 변수로 깨집니다. PowerShell은 try/catch·-WhatIf·Pester로 「알아챌 수 있다·시험할 수 있다·지킬 수 있다」를 제공합니다.
  • 혼재기에는 입구 bat에서 powershell.exe -File(또는 pwsh -File)을 호출하고, exit%ERRORLEVEL%로 종료 코드를 주고받는 것이 현실적인 해법입니다.
  • robocopy 같은 우수한 외부 명령은 재작성하지 않고, $LASTEXITCODE 판정(8 이상이 실패)만 정확히 작성합니다.
  • 절차는 자산 조사(읽기부터)→위험 분류→파일럿→병행 가동. 실행 정책과 배포 설계도 빠뜨리지 마세요.

관련 글

관련 상담 영역

합동회사 고무라소프트에서는 bat·VBScript·PowerShell이 섞인 레거시 자산의 조사와 이전 계획 수립, 야간 작업의 PowerShell화와 운영 설계, 이전에 따른 장애 조사를 다룹니다. 「작성한 사람이 이미 없는」 배치의 해독부터도 상담받습니다.

참고 링크

  1. Microsoft Learn, Windows Commands. Windows에 명령 셸(cmd)과 PowerShell 두 셸이 있다는 점, 가장 견고하고 최신인 Windows 자동화에는 Windows 명령이나 Windows Script Host가 아니라 PowerShell 사용을 권장한다는 공식 기술에 대해.  2

  2. Microsoft Learn, Deprecated features for Windows client. VBScript의 deprecated가 2023년 10월에 발표된 점, 앞으로의 Windows 릴리스에서 Feature on Demand로 제공된 뒤 OS에서 삭제되는 계획이 있다는 점에 대해.  2 3 4

  3. Microsoft Learn, Resources for deprecated features. VBScript의 Feature on Demand가 처음에는 사전 설치되어, 퇴역 준비 동안 중단 없이 사용할 수 있다는 점에 대해.  2 3

  4. Microsoft Learn, Features removed or no longer developed in Windows Server. Windows Server 2025에서 VBScript가 FoD로 제공되고 이후 릴리스에서 삭제된다는 점, 작업 자동화나 스크립트의 대체로서 PowerShell 사용이 안내되어 있다는 점에 대해.  2

  5. Microsoft Learn, if. if errorlevel 가 「직전 프로그램의 종료 코드가 number 이상일 때 참」인 점, %errorlevel%이 ERRORLEVEL이라는 환경 변수가 존재하지 않는다는 전제의 확장이며 같은 이름 환경 변수가 정의되어 있으면 그 값이 반환된다는 점에 대해.  2 3 4

  6. Microsoft Learn, about_Try_Catch_Finally. try/catch/finally 블록이 문 종료 오류와 스크립트 종료 오류를 처리하는 점, catch에서 예외 형을 지정할 수 있는 점, finally가 오류 유무와 관계없이 실행되어 뒷정리에 쓸 수 있다는 점에 대해.  2 3 4

  7. Microsoft Learn, about_CommonParameters. 시스템이나 데이터에 변경을 가하는 cmdlet이 제공하는 위험 완화 매개변수(-WhatIf/-Confirm), -WhatIf가 명령을 실행하지 않고 영향의 설명만 표시한다는 점에 대해.  2 3 4

  8. Microsoft Learn, about_Language_Keywords. exit 키워드가 $LASTEXITCODE를 설정하고 cmd.exe 쪽에서는 %ERRORLEVEL%에 대응하는 점, pwsh -File로 실행한 스크립트의 exit 4가 호출 원의 %ERRORLEVEL%에서 4로 관측되는 실례, exit 문이 없는 경우 정상 종료로 0·미처리 예외로 1이 되는 점에 대해.  2

  9. Microsoft Learn, about_Pwsh. pwsh의 -File 매개변수 사용법, 배치 스크립트에서 호출할 때 %~dp0으로 실행 디렉터리를 나타내는 공식 예, -File에서 스크립트 종료 오류가 일어난 경우 종료 코드가 1이 되는 점, exit 명령으로 종료한 경우 그 숫자가 종료 코드가 되는 점에 대해.  2 3

  10. Microsoft Learn, robocopy. robocopy의 종료 코드 체계(0=복사 대상 없음, 1=모든 파일을 정상 복사, 8 이상=1건 이상의 실패), 재시도 기본값이 /r:1000000(100만 회)·대기 기본값이 /w:30초인 점, 미러링이나 로그 등의 옵션에 대해.  2 3 4 5

  11. Microsoft Learn, about_Automatic_Variables. 자동 변수 $LASTEXITCODE에 네이티브 프로그램이나 스크립트의 종료 코드가 저장되는 점, -File로 스크립트 실행 시 값의 결정 방식(예외로 1, exit 지정값, 정상 종료로 0)에 대해.  2 3

  12. Microsoft Learn, about_Execution_Policies. Windows의 기본 실행 정책이 RemoteSigned인 점, RemoteSigned가 인터넷 출처 스크립트에 신뢰된 게시자의 서명을 요구하고 로컬에서 만든 스크립트에는 서명을 요구하지 않는 점, 각 정책(Restricted/AllSigned/Bypass 등)의 의미에 대해.  2

  13. Microsoft Learn, exit. exit /b 가 배치 스크립트를 종료하고 ERRORLEVEL 환경 변수에 지정값을 설정하는 점, cmd 자체를 종료하는 경우에는 프로세스 종료 코드에 설정된다는 점에 대해. 

  14. Microsoft Learn, about_PowerShell_exe. Windows PowerShell 5.1의 powershell.exe에서 -File 매개변수의 사양, cmd.exe에서 환경 변수를 넘기는 경우의 %windir% 구문, 종료 코드 다루기에 대해. 

  15. Microsoft Learn, Select-String. Select-String이 정규 표현으로 텍스트를 검색하는 cmdlet인 점, -Quiet를 지정한 경우 반환값이 MatchInfo 객체가 아니라 패턴이 있으면 $true·없으면 $null이 되는 점에 대해. 

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

사내 배치 파일은 모두 PowerShell로 이전해야 하나요?
전부 이전하는 것은 권장하지 않습니다. 몇 년 동안 안정적으로 동작해 왔고 변경 예정이 없는 배치는, 재작성 자체가 새로운 장애 위험이 됩니다. 이전 우선순위가 높은 것은 앞으로 변경이 들어갈 것, 오류 처리나 로그가 필요한 것, 그리고 VBScript(cscript/wscript)를 호출하는 것입니다. 판단표로 분류해서 가치가 있는 것부터 단계적으로 이전하는 것이 실무의 정석입니다.
cmd.exe나 배치 파일은 폐지되나요?
cmd.exe는 Windows의 deprecated 기능 목록에 올라 있지 않으며, 폐지 발표도 없습니다. 다만 Microsoft는 Windows 자동화에는 명령이나 WSH가 아니라 PowerShell 사용을 권장한다고 명확히 밝히고 있습니다. 반면 VBScript는 2023년 10월에 deprecated가 공식 발표되었고, 앞으로의 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를 붙이는 방법은 간편하지만, 조직의 정책 설계로서 의도한 상태인지 확인한 뒤에 사용하세요.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기