본문 바로가기
빌드노트 검색

빌드노트

ErrorActionPreference를 Stop으로 했는데 빌드 실패 뒤에도 계속 실행된다

PowerShell의 Stop 설정만으로 네이티브 실패가 중단되지 않는 이유를 5.1과 7.6.6에서 비교했다. 종료 코드 0·7, stderr만 있는 정상 종료와 try/catch를 관찰하고, 7.4 이상 설정과 호출 직후 LASTEXITCODE 검사 위치를 구분한다.

PowerShell 배포 스크립트에 $ErrorActionPreference = 'Stop'을 넣었는데도 빌드 실패 뒤의 명령이 실행된다면, 실패한 프로그램의 종료 코드를 확인한다. 네이티브 실행 파일의 0이 아닌 종료 코드가 기본적으로 PowerShell의 중단 오류가 되는 것은 아니다. Windows PowerShell 5.1을 함께 지원해야 한다면 명령 직후 $LASTEXITCODE를 검사하고, PowerShell 7.4 이상으로 실행 환경을 고정했다면 네이티브 종료 코드를 오류로 바꾸는 설정을 사용할 수 있다.

기존 스크립트에서는 빌드 프로그램을 직접 호출한 줄과 다음 단계 사이가 검사 위치다. $LASTEXITCODE는 마지막 네이티브 프로그램 등의 종료 코드를 담는다. 다른 네이티브 프로그램을 실행한 뒤 검사하면 이미 그 프로그램의 값으로 바뀔 수 있다. LASTEXITCODE의 정의 (새 탭에서 열림)

다음은 Windows에서 이 처리 위치를 확인하는 짧은 예제다. 새 터미널에서 powershell.exe -NoProfile 또는 pwsh -NoProfile로 시작한다. Windows 기본 프로그램인 cmd.exe가 7로 끝나도록 했으며 실제 빌드나 배포를 실행하지 않는다. 자기 스크립트에 적용할 때는 cmd.exe 한 줄을 기존 네이티브 빌드 호출로 바꾸고, 그 바로 다음 두 줄에서 종료 코드를 저장하고 판정한다.

& {
    $ErrorActionPreference = 'Stop'
    $PSNativeCommandUseErrorActionPreference = $false
    cmd.exe /d /c 'exit 7'
    $rc = $LASTEXITCODE
    if ($rc -ne 0) { throw "native command failed: exit=$rc" }
    'NEXT_STEP'
}

두 셸에서 모두 native command failed: exit=7 오류가 발생했고 NEXT_STEP은 출력되지 않았다. 같은 입력을 새 PowerShell 자식 프로세스에서 실행했을 때 호출자가 받은 종료 코드는 1이었다. 원래 프로그램의 7과, 처리되지 않은 예외로 끝난 PowerShell의 1은 서로 다른 값이다. exit 7을 exit 0으로 바꾸면 NEXT_STEP이 출력됐고 자식 프로세스도 0으로 끝났다.

블록 안의 $false는 지원되는 엔진에서 자동 오류 변환을 끄고 직접 판정하기 위한 값이다. & { ... } 안에서 바꿔 바깥 범위의 설정을 유지한다. Windows PowerShell 5.1에서는 이 이름에 값을 넣어도 엔진 기능이 생기지 않는다. 이 예제에서 실패를 잡는 부분은 뒤의 명시적 검사와 throw다.

Stop만으로는 catch에 들어가지 않았다

공식 문서의 $ErrorActionPreference는 PowerShell 오류 처리 설정이다. 예를 들어 Write-Error의 비중단 오류는 Stop에서 중단 오류로 바뀐다. 반면 네이티브 프로그램은 종료 코드로 결과를 알린다. 이를 PowerShell 오류로 변환하지 않으면 try/catch로 감싸도 실패 코드 자체를 잡지 못한다. 오류 처리 설정 (새 탭에서 열림), try/catch가 처리하는 오류 (새 탭에서 열림)

새 셸에서 아래 입력으로 네이티브 호출 뒤의 줄과 catch 중 어디가 실행되는지 확인한다. 앞 예제와 별도로 실행한다.

$ErrorActionPreference = 'Stop'
try {
    cmd.exe /d /c 'exit 7'
    "NEXT exit=$LASTEXITCODE"
} catch {
    "CATCH exit=$LASTEXITCODE"
}
'AFTER_TRY'

Windows PowerShell 5.1.26100.9444와 PowerShell 7.6.6에서 기본 설정으로 관찰한 출력은 같았다.

NEXT exit=7
AFTER_TRY

7은 남아 있지만 catch는 실행되지 않았다. 별도 대조에서 네이티브 호출 대신 Write-Error 'LAB_PS_ERROR'를 넣자 두 셸 모두 catch로 이동했다. 따라서 이 결과는 Stop 전체가 작동하지 않는다는 뜻이 아니다. 두 종류의 실패를 같은 것으로 취급한 것이 문제다.

PowerShell 7.4 이상에서는 두 설정을 함께 쓴다

자동 처리 기능은 PowerShell 7.4에서 정식 기능이 됐다. 7.4 변경 문서 (새 탭에서 열림)는 PSNativeCommandErrorActionPreference를 정식 편입된 기능으로 나열한다. 이 기능 이름과 실제 설정 변수인 $PSNativeCommandUseErrorActionPreference를 구분한다.

실제 배포 스크립트를 시작하는 셸에서 $PSVersionTable.PSVersion을 확인한다. 실행기를 7.4 이상으로 고정할 수 있다면 .ps1 파일의 처음에 아래처럼 버전 조건과 두 설정을 둔다. 그러고 나서 기존 빌드 호출을 실행한다.

#Requires -Version 7.4
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true

버전 조건은 .ps1 실행 전에 검사된다. 같은 조건을 둔 합성 파일을 Windows PowerShell 5.1로 실행하자 7.4가 필요하다는 오류로 종료됐다. 설정 변수에 값을 넣기 전에 실행 환경부터 제한하는 방법이다. Requires의 버전 조건 (새 탭에서 열림)

$PSNativeCommandUseErrorActionPreference의 기본값은 $false다. $true이면 0이 아닌 네이티브 종료 코드가 $ErrorActionPreference에 따라 처리된다. 설정의 기본값과 적용 조건 (새 탭에서 열림)

PowerShell 7.6.6에서 $true를 설정한 뒤 앞의 try/catch 입력을 다시 실행하자 다음 출력으로 바뀌었다.

CATCH exit=7
AFTER_TRY

별도로 오류 형식까지 기록했을 때 예외는 NativeCommandExitException이었다. $true를 유지하고 $ErrorActionPreference만 Continue로 바꾸면 오류 메시지는 나왔지만 NEXT exit=7에 해당하는 후속 줄도 실행됐다. 두 설정이 함께 필요하다.

Windows PowerShell 5.1에는 이 엔진 설정이 없다. 실제로 같은 이름에 $true를 대입해도 종료 코드 7 뒤의 줄이 실행됐다. 변수 대입이 성공했다는 사실로 지원 여부를 판단하지 않는다. 또한 예제의 catch는 관찰값을 출력하고 오류를 처리했으므로 AFTER_TRY까지 실행됐고 자식 프로세스 종료 코드도 0이었다. 배포 실패를 호출자에게 전달해야 한다면 catch에서 기록만 남기고 끝내지 말고 throw로 다시 전파하거나, 진입 스크립트에서 정한 실패 코드로 종료해야 한다.

stderr 출력과 실패 코드는 따로 본다

같은 비교에서 cmd.exe의 입력을 'echo LAB_STDERR 1>&2 & exit 0'으로 바꿨다. LAB_STDERR를 표준 오류에 출력하지만 종료 코드는 0인 합성 명령이다. 두 셸의 기본 설정과 PowerShell 7.6.6의 자동 오류 변환 설정 모두에서 catch로 이동하지 않았고 후속 줄이 실행됐다. 반대로 앞의 exit 7은 stderr 출력이 전혀 없어도 자동 변환을 켜면 잡혔다.

따라서 로그가 stderr에 찍혔는지만으로 실패를 결정하지 않는다. 이 비교는 PowerShell 쪽의 2>&1 병합 없이 실행했다. 명령 문자열 안의 1>&2는 cmd.exe가 출력을 stderr로 보내는 부분이다. PowerShell의 스트림 병합·리다이렉션까지 추가한 동작은 여기서 검증하지 않았다.

종료 코드 검사도 늦추지 않는다. 별도 실행에서 7 직후 저장한 $rc는 7을 유지했지만, 이어서 정상 종료하는 다른 cmd.exe를 호출하자 $LASTEXITCODE는 0이 됐다. 중간의 Get-Date 호출은 이 값을 바꾸지 않았다. 모든 PowerShell 명령이 덮는다는 뜻이 아니라, 다음 네이티브 호출 전에 현재 결과를 보관해야 한다는 뜻이다.

앞의 수동 검사와 자동 변환은 0만 성공인 빌드 도구를 전제로 한다. 공식 설정 문서도 일부 도구는 0이 아닌 값을 정상 상태 정보로 쓴다고 설명한다. 그런 도구에는 종료 코드 계약에 맞는 허용 범위를 정하고 수동 검사하는 편이 맞다. 실패를 0으로 바꿔 반환하는 래퍼까지 이 설정이 알아내 주지는 않는다.

직접 실행한 범위는 Windows 10.0.26200의 새 PowerShell 자식 프로세스와 합성 cmd.exe 호출이다. 7.4에서 정식 편입됐다는 사실은 문서로 확인했고, 실제 실행은 5.1과 7.6.6에서 했다. 7.3·7.4 런타임, Linux·macOS, 파이프라인·백그라운드 실행과 실제 빌드·배포는 확인하지 않았다.

← 전체 글 보기