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

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

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

결론부터 말하면, 전부 다시 작성할 필요는 없습니다. bat가 당장 동작하지 않게 될 예정은 없으며, 동작 중인 것을 재작성하는 행위 자체가 장애 위험이 되기 때문입니다. 한편, “그대로 두면” 위험한 bat도 분명히 존재합니다. VBScript를 호출하는 것, 오류를 묻어버리는 것, 앞으로 변경이 들어갈 것. 이런 판별 없이 “전부 남긴다”도 “전부 이전한다”도 모두 사고의 원인이 됩니다.

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

1. 먼저 결론

  • cmd.exe와 bat는 폐지 예정이 없지만, Microsoft는 Windows 자동화에 PowerShell 사용을 권장한다고 명확히 밝히고 있습니다. 새로 작성한다면 PowerShell, 이것이 공식 방향성입니다.1
  • VBScript는 2023년 10월에 비권장이 공식 발표되었고, 요구 시 설치(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

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

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

대조적인 것이 VBScript입니다. 2023년 10월, VBScript의 비권장(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의 문자 코드와 개행 코드를 정리한다 - Shift_JIS / UTF-8 / UTF-16, 문자 깨짐, CRLF / LF, 왜 혼란스러운가“를 참고하십시오.

같은 처리를 PowerShell로 작성하면, 실패를 다루는 방식이 구조적으로 달라집니다.

# 야간 백업(PowerShell 버전의 골격)
$ErrorActionPreference = 'Stop'   # 오류를 "멈춘다" 쪽으로 기울인다
try {
    # 복사 원본은 "폴더의 내용물"을 가리키는 C:\data\* 로 지정한다. 'C:\data'를 넘기면
    # 복사 대상 폴더가 이미 존재하는 두 번째 실행부터는 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는 문(statement)을 종료시키는 오류를 포착하며, 뒤처리는 finally에 작성할 수 있습니다.6 게다가 PowerShell에는 파괴적인 작업을 실제로 실행하지 않고 “무슨 일이 일어났을 것인가”만 표시하는 -WhatIf가 공통 메커니즘으로 마련되어 있어7, 재작성한 스크립트를 운영 환경을 상대로 안전하게 예행연습할 수 있습니다.

-WhatIf는 말로 설명해서는 감이 잘 안 오므로, 실제로 어떻게 보이는지 예를 들어 두겠습니다. 공식 문서의 예에서는 Remove-Item Date.csv -WhatIf를 실행해도 파일은 삭제되지 않고, 다음 한 줄만 표시됩니다.7

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

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

게다가 Pester로 테스트를 작성하면 “동작할 것”이라는 기대를 자동으로 계속 확인할 수 있습니다(“Pester로 하는 PowerShell 테스트 정비 ── 운영 스크립트를 쉽게 망가지지 않게 만드는 실무 패턴” 참고). 오류 처리와 재실행 설계 자체는 함께 공개한 “PowerShell의 오류 처리와 재실행 설계“에서 더 깊이 다루고 있습니다.

즉 이전으로 얻는 것은 문법의 참신함이 아니라, 실패를 알아챌 수 있다・안전하게 시험할 수 있다・테스트로 지킬 수 있다는 운영 품질입니다. 반대로 말하면, 실패해도 곧바로 알 수 있는 단순한 실행 래퍼 같은 bat에는 이 가치가 그다지 효과가 없습니다. 그래서 선별이 필요해집니다.

4. 전부 이전하지 않는다 ── 선별을 위한 판단표

논점 선택지(권장에 “권장”을 명기) 판단 기준
안정적으로 동작 중・변경 예정 없음・단순 그대로 남긴다(권장) / 이전 앱 실행 래퍼나 몇 줄짜리 복사는 남겨도 됩니다. 재작성 자체가 새로운 위험
VBScript(cscript/wscript)를 호출하고 있음 남긴다 / 우선적으로 이전(권장) VBScript는 비권장→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 실행 정책과 스크립트 서명 ── ‘Bypass로 덮어버리는’ 운영에서 벗어나는 실무 가이드“에 정리해 두었습니다.

(4) 병행 운영. 기존 bat와 새 스크립트를 일정 기간 동시에 실행합니다. 출력 대상을 나누어 결과를 대조하기, 새로운 쪽은 처음에는 -WhatIf나 로그만으로 실행하기, 롤백은 작업의 실행 대상을 bat로 되돌리기만 하면 되도록 해 두기 ── 여기까지 준비한 뒤 기존 bat를 퇴역시키면, 이전 작업 자체가 사고로 이어지는 일은 거의 없습니다.

8. 정리

  • cmd와 bat는 폐지 예정이 없지만, 공식 권장은 PowerShell입니다. 반면 VBScript는 비권장→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의 비권장이 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월에 비권장이 공식 발표되었고, 앞으로 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 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기