about_Error_Handling
about_Error_Handling (오류 처리)
PowerShell에서 발생하는 오류의 종류와 오류를 처리하는 메커니즘을 설명해 드릴게요.
출처: Microsoft Learn — about_Error_Handling https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_error_handling
본문
간단한 설명 (Short description)
PowerShell에서 발생하는 오류의 종류와, 그 오류를 처리하는 메커니즘에 대해 설명드릴게요.
자세한 설명 (Long description)
PowerShell은 오류를 크게 세 가지 범주로 구분해요.
- 비종료 오류 (Non-terminating errors)
- 문 종료 오류 (Statement-terminating errors)
- 스크립트 종료 오류 (Script-terminating errors)
이 차이를 이해하는 게 안정적인 스크립트와 모듈을 작성하는 데 아주 중요해요. 왜냐하면 각 범주마다 기본 동작이 다르고, 처리하는 방식도 달라야 하기 때문이에요.
여기에 더해서, 외부(네이티브) 프로그램은 종료 코드(exit code)로 실패를 알려줘요. PowerShell은 이 값을 자체 오류 체계와는 별도로 추적한답니다.
오류의 종류 (Types of errors)
비종료 오류 (Non-terminating errors)
비종료 오류는 문제를 보고하지만 파이프라인을 멈추지 않아요. 명령은 이후에 오는 입력 개체들을 계속 처리하지요. 비종료 오류는 이런 경우에 생겨요.
Write-Errorcmdlet- 고급 함수에서의
$PSCmdlet.WriteError()메서드 - 개별 입력 개체에서 복구 가능한 실패가 발생한 cmdlet
기본적으로 PowerShell은 오류 메시지를 화면에 보여주고 실행을 계속 진행해요.
# Non-terminating error: the pipeline continues after the failure
'file1.txt', 'noSuchFile.txt', 'file3.txt' | ForEach-Object {
Get-Content $_ -ErrorAction Continue
}
이 예시에서 Get-Content는 noSuchFile.txt에 대해 비종료 오류를 보고한 다음, file3.txt를 계속 처리해요.
비종료 오류는 기본적으로 catch나 trap을 촉발하지 않아요.
문 종료 오류 (Statement-terminating errors)
문 종료 오류는 현재 문(파이프라인)이 실행되는 걸 멈추지만, 스크립트의 다음 문은 이어서 실행돼요. 문 종료 오류는 이런 경우에 생겨요.
- 고급 함수와 컴파일된 cmdlet에서의
$PSCmdlet.ThrowTerminatingError()메서드 CommandNotFoundException(존재하지 않는 명령 호출),ParameterBindingException(잘못된 매개 변수 인수) 같은 엔진 오류- 예외를 던지는 .NET 메서드 호출, 예를 들어
[int]::Parse('abc')
# Statement-terminating error: Get-Item fails, but the next statement runs
Get-Item -Path 'C:\NoSuchFile.txt'
Write-Output 'This still runs'
문 종료 오류는 try/catch와 trap으로 잡아낼 수 있어요.
.ThrowTerminatingError()는 -ErrorAction 매개 변수를 참고하지 않아요(Break 값은 예외인데, 그건 디버거로 진입시키죠). 다만 $ErrorActionPreference는 엔진의 문 수준 핸들러를 통해 문 종료 오류에도 적용돼요. 예를 들어 $ErrorActionPreference = 'SilentlyContinue'로 설정하면 문 종료 오류를 억제해서 스크립트가 다음 문에서 이어지게 할 수 있어요. -ErrorAction 매개 변수는 이렇게 할 수 없어요. 자세한 내용은 The $ErrorActionPreference asymmetry를 참고해 주세요.
스크립트 종료 오류 (Script-terminating errors)
스크립트 종료 오류는 전체 호출 스택을 풀어버려요. try/catch 블록이나 trap 문으로 오류를 잡지 않는 한, 실행이 완전히 멈추지요. 스크립트 종료 오류는 이런 경우에 생겨요.
throw키워드- 구문 분석 오류(스크립트를 컴파일하지 못하게 하는 구문 오류)
- 비고급 컨텍스트에서
-ErrorAction Stop또는$ErrorActionPreference = 'Stop'에 의해 에스컬레이션된 비종료 오류. 자세한 내용은 How escalation works를 참고해 주세요. - 특정한 중요한 엔진 실패
# Script-terminating error: throw unwinds the call stack
function Test-Throw {
throw 'Critical failure'
Write-Output 'This never runs'
}
Test-Throw
Write-Output 'This never runs either (unless caught)'
throw 키워드는 기본적으로 스크립트 종료 오류를 만들어요. 하지만 $ErrorActionPreference가 SilentlyContinue나 Ignore로 설정되어 있으면 throw를 억제할 수도 있어요. 고급 함수를 -ErrorAction SilentlyContinue로 호출하면, 그 매개 변수가 스코프-로컬 $ErrorActionPreference 값으로 변환되기 때문에 그 함수 안의 throw도 억제되요.
$ErrorActionPreference = 'Ignore'로 설정해도 억제된 throw는 여전히 $Error에 항목을 기록해요. Ignore 값이 $Error 기록을 막는 건 비종료 오류에 한해서예요.
문 종료와 스크립트 종료라는 용어는 오류의 심각도를 뜻하는 게 아니라 영향 범위를 뜻해요. 문 종료 오류는 하나의 문을 멈추고, 스크립트 종료 오류는 스크립트 전체와 그 호출자를 멈추지요. 둘 다 try/catch로 잡을 수 있어요.
외부 프로그램 오류 (External program errors)
외부(네이티브) 프로그램은 PowerShell의 오류 체계에 직접 참여하지 않아요. 그들은 0이 아닌 종료 코드로 실패를 알리고, PowerShell은 그 값을 $LASTEXITCODE 자동 변수에 저장해요.
git clone https://example.com/nonexistent.git 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Error "git failed with exit code $LASTEXITCODE"
}
기본적으로 네이티브 프로그램의 0이 아닌 종료 코드는:
$?를$false로 설정해요$Error에ErrorRecord를 만들지 않아요catch나trap을 촉발하지 않아요
PowerShell 7.3은 실험용 기본 설정 변수 $PSNativeCommandUseErrorActionPreference를 추가했고, 이건 7.4에서 안정 기능이 됐어요. 이 변수를 $true로 설정하면 0이 아닌 종료 코드가 특정 종료 코드를 알려주는 비종료 오류(NativeCommandExitException)를 발생시켜요. 이 오류는 $ErrorActionPreference를 따르기 때문에, 이걸 Stop으로 설정하면 오류가 스크립트 종료 오류로 승격되어 try/catch로 잡을 수 있답니다.
오류 상태 변수 (Error state variables)
PowerShell은 현재 오류 상태를 반영하는 여러 자동 변수를 유지해요.
$?
마지막 작업이 성공하면 $true, 오류(비종료든 종료든)를 만들었다면 $false를 담아요. 네이티브 명령의 경우 $?는 종료 코드를 기준으로 설정돼요. 종료 코드 0이면 $true, 그 외에는 $false예요.
Get-Item -Path 'C:\NoSuchFile.txt' 2>$null
$? # False
$Error
가장 최근의 오류 기록을 저장하는 ArrayList로, 인덱스 0이 가장 최신 오류예요. 목록은 $MaximumErrorCount개(기본 256개)까지 담아요.
모든 종료 오류는 $Error에 추가돼요. 종료 오류의 경우 Ignore는 표시는 억제하지만 여전히 $Error에는 기록해요. 모든 비종료 오류도 $Error에 추가되지만, 비종료 오류에 -ErrorAction Ignore를 사용하면 표시와 기록을 모두 막아요.
$LASTEXITCODE
마지막으로 실행된 네이티브 프로그램의 종료 코드를 담아요. 0이면 관례적으로 성공, 0이 아니면 실패를 뜻해요. 이 변수는 PowerShell cmdlet 오류의 영향을 받지 않아요.
오류 동작 제어 (Control error behavior)
-ErrorAction 공용 매개 변수
-ErrorAction 공용 매개 변수는 단일 명령에 대해 $ErrorActionPreference를 덮어써요. 그 명령에서 발생하는 비종료 오류에 PowerShell이 어떻게 반응할지를 제어하지요.
-ErrorAction은 $PSCmdlet.ThrowTerminatingError()가 만들어낸 오류의 동작을 바꾸지 않아요. 그런 오류는 호출자의 기본 설정과 무관하게 항상 문 종료 오류이에요.
$ErrorActionPreference 변수
$ErrorActionPreference 기본 설정 변수는 현재 스코프와 하위 스코프의 모든 명령에 적용돼요. -ErrorAction과 같은 값을 받아들여요.
$ErrorActionPreference = 'Stop'
# All non-terminating errors in this scope now become terminating
Write-Error 'This now throws' # Generates ActionPreferenceStopException
명령에 -ErrorAction을 지정하면, 그 명령에 한해서 $ErrorActionPreference보다 우선해요.
에스컬레이션이 동작하는 방식 (How escalation works)
-ErrorAction Stop이나 $ErrorActionPreference = 'Stop'이 적용 중이면, PowerShell은 다음과 같은 메커니즘으로 비종료 오류를 종료 오류로 변환해요.
- cmdlet이 내부적으로
WriteError()를 호출해 비종료 오류를 발생시켜요. - 엔진이 그 명령에 적용되는
ErrorAction기본 설정을 확인해요. - 기본 설정이
Stop이므로, 엔진은 원래 오류 기록을 감싸는ActionPreferenceStopException을 만들어요. catch로 잡으면 원래 오류 정보를$_.Exception.ErrorRecord로 접근할 수 있어요.
에스컬레이션된 오류의 범위는 컨텍스트에 따라 달라져요.
- 비고급 스크립트, 함수, 스크립트 블록에서는
$ErrorActionPreference = 'Stop'으로 설정하면 스크립트 종료 오류로 에스컬레이션돼요. 오류가 호출 스택을 타고 위로 전파되요. - 고급 함수와 스크립트 블록(
[CmdletBinding()]이 있는 것들)에서는 오류가 문 종료 상태로 남아요. 호출 다음의 문부터 실행이 이어지지요. - 고급 함수에
-ErrorAction Stop을 넘기면 그 함수 안에서$ErrorActionPreference = 'Stop'을 설정하는 것과 같은 효과예요.-ErrorAction이 스코프-로컬$ErrorActionPreference값으로 변환되기 때문이에요.
에스컬레이션 예시 (Escalation examples)
- 비고급(non-advanced): 스크립트 종료('after'는 출력되지 않아요)
& {
param()
$ErrorActionPreference = 'Stop'
1/0 # Divide by zero error
} 2>$null
'after'
- 고급(advanced): 문 종료('after'는 출력돼요)
& {
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
1/0 # Divide by zero error
} 2>$null
'after'
-ErrorAction Stop없이: 비종료, catch는 실행되지 않아요
try {
Write-Error 'This is non-terminating'
Write-Output 'Execution continues'
} catch {
Write-Output "Caught: $_" # Not reached
}
-ErrorAction Stop사용 시: 종료로 에스컬레이션
try {
Write-Error 'This becomes terminating' -ErrorAction Stop
} catch {
Write-Output "Caught: $_" # Reached
}
에스컬레이션된 오류는 원래 예외 유형으로 잡을 수 있어요. 엔진이 ActionPreferenceStopException을 풀어서 내부 예외를 찾아내거든요.
try {
Get-Item -Path 'C:\NoSuchFile.txt' -ErrorAction Stop
} catch [System.Management.Automation.ItemNotFoundException] {
Write-Output "File not found: $($_.Exception.Message)"
}
$ErrorActionPreference의 비대칭성 (The $ErrorActionPreference asymmetry)
-ErrorAction 매개 변수와 $ErrorActionPreference 변수는 종료 오류를 대할 때 다르게 동작해요. 이 비대칭성을 이해하는 게 중요하답니다.
-ErrorAction은 비종료 오류에만 영향을 줘요. cmdlet이$PSCmdlet.ThrowTerminatingError()를 호출하면-ErrorAction매개 변수는 무시돼요(Break는 예외인데, 디버거로 진입시키죠). 오류는 항상 던져져요.$ErrorActionPreference는 비종료 오류와 문 종료 오류 모두에 영향을 줘요. 엔진의 문 수준 오류 핸들러가(-ErrorAction매개 변수가 아니라)$ErrorActionPreference를 읽어서, 값이SilentlyContinue나Ignore일 때 문 종료 오류를 억제할 수 있어요.
function Test-Asymmetry {
[CmdletBinding()]
param()
$er = [System.Management.Automation.ErrorRecord]::new(
[System.InvalidOperationException]::new('test error'),
'TestError',
[System.Management.Automation.ErrorCategory]::InvalidOperation,
$null
)
$PSCmdlet.ThrowTerminatingError($er)
}
# -ErrorAction SilentlyContinue does NOT suppress the error:
Test-Asymmetry -ErrorAction SilentlyContinue # Error is still thrown
# $ErrorActionPreference DOES suppress the error:
$ErrorActionPreference = 'SilentlyContinue'
Test-Asymmetry # Error is silently suppressed, script continues
$ErrorActionPreference = 'Continue'
$ErrorActionPreference는 SuppressPromptInInterpreter가 true로 설정된 오류는 억제할 수 없어요. 그런 오류는 기본 설정 변수와 무관하게 항상 전파되요. 이런 유형의 예시로는:
-ErrorAction Stop에스컬레이션에서 온ActionPreferenceStopException- PowerShell 클래스 메서드 안의 오류
PipelineStoppedException
오류 처리 (Handle errors)
try/catch/finally
try/catch/finally를 사용해 문 종료 및 스크립트 종료 오류를 처리해요. try 블록 안에서 오류가 발생하면 PowerShell이 맞는 catch 블록을 찾아요. finally 블록은 오류가 발생했든 안 했든 항상 실행돼요.
try {
$result = Get-Content -Path 'data.txt' -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Warning 'Data file not found, using defaults.'
$result = 'default'
}
catch {
Write-Warning "Unexpected error: $_"
}
finally {
Write-Verbose 'Cleanup complete.' -Verbose
}
try 블록 안에서 엔진은 내부 플래그를 설정해서, -ErrorAction Stop이나 $ErrorActionPreference = 'Stop'에 의해 에스컬레이션된 비종료 오류가 catch 블록으로 전파되게 해요. 이건 특별한 예외가 아니라 설계된 동작이에요.
전체 구문에 대한 자세한 내용은 about_Try_Catch_Finally를 참고해 주세요.
trap
trap 문은 스코프 수준에서 종료 오류를 처리해요. 둘러싼 스코프 어디에서든 오류가 발생하면 trap 블록이 실행돼요.
- 기본값(
break나continue없음): 오류가 표시되고, 오류를 일으킨 문 다음의 문부터 실행이 이어져요. - trap 안의
continue: 오류 메시지를 억제하고 다음 문에서 이어져요. - trap 안의
break: 오류가 부모 스코프로 전파돼요.
trap [System.Management.Automation.CommandNotFoundException] {
Write-Warning "Command not found: $($_.TargetObject)"
continue
}
NonsenseCommand # Trap fires, execution continues
Write-Output 'This runs because the trap used continue'
전체 구문에 대한 자세한 내용은 about_Trap를 참고해 주세요.
함수와 스크립트에서 오류 보고하기 (Reporting errors in functions and scripts)
함수와 스크립트를 작성할 때는 실패의 심각도에 맞는 오류 보고 메커니즘을 골라야 해요.
비종료 - Write-Error 사용하기
함수가 다른 입력을 계속 처리할 수 있을 때 Write-Error를 사용해요. 여러 개체를 처리하다가 개별 항목에서 실패가 발생하는 파이프라인 함수에 적합해요.
function Test-Path-Safe {
[CmdletBinding()]
param([Parameter(ValueFromPipeline)][string]$Path)
process {
if (-not (Test-Path $Path)) {
Write-Error "Path not found: $Path"
return
}
$Path
}
}
고급 함수([CmdletBinding()]이 있는 것들)에서는 Write-Error 대신 $PSCmdlet.WriteError()를 사용해 호출자 스코프에서 $?가 제대로 $false로 설정되게 하세요. Write-Error cmdlet은 $?를 항상 올바르게 설정하지는 못해요.
문 종료 - $PSCmdlet.ThrowTerminatingError() 사용하기
함수가 아예 계속할 수 없지만, 호출자가 그 실패를 어떻게 처리할지를 결정해야 한다면 $PSCmdlet.ThrowTerminatingError()를 사용해요. 고급 함수에서 권장하는 방식이에요.
function Get-Config {
[CmdletBinding()]
param([string]$Path)
if (-not (Test-Path $Path)) {
$er = [System.Management.Automation.ErrorRecord]::new(
[System.IO.FileNotFoundException]::new("Config file not found: $Path"),
'ConfigNotFound',
[System.Management.Automation.ErrorCategory]::ObjectNotFound,
$Path
)
$PSCmdlet.ThrowTerminatingError($er)
}
Get-Content $Path | ConvertFrom-Json
}
오류가 함수를 떠난 뒤에는 호출자가 기본적으로 비종료 오류로 취급해요. 호출자는 -ErrorAction Stop으로 에스컬레이션할 수 있어요.
스크립트 종료 - throw 사용하기
복구가 불가능하고 스크립트 전체가 멈춰야 할 때는 throw를 사용해요.
$config = Get-Content 'config.json' -ErrorAction SilentlyContinue |
ConvertFrom-Json
if (-not $config) {
throw 'Cannot proceed without a valid configuration file.'
}
어떤 메커니즘을 쓸까요?
- 여러 개의 입력을 처리하는데 그중 일부가 실패할 수 있다면,
Write-Error나$PSCmdlet.WriteError()를 사용해요. - 함수가 계속할 수 없다면
$PSCmdlet.ThrowTerminatingError()를 사용하고, 호출자가 처리 방식을 결정하게 해요. - 스크립트 전체를 즉시 멈춰야 한다면
throw를 사용해요.
오류 유형 요약 (Summary of error types)
아래 표들은 PowerShell에서 서로 다른 오류 유형의 속성과 동작을 요약해 줘요.
비종료 오류
비종료 오류(Non-terminating errors)는 Write-Error나 $PSCmdlet.WriteError()로 만들 수 있어요.
문 종료 오류
문 종료 오류(Statement-terminating errors)는 ThrowTerminatingError(), 엔진 오류, .NET 메서드 예외, 또는 고급 컨텍스트에서의 -ErrorAction Stop으로 만들 수 있어요.
스크립트 종료 오류
스크립트 종료 오류(Script-terminating errors)는 throw, 구문 분석 오류, 또는 비고급 컨텍스트에서의 -ErrorAction Stop으로 만들 수 있어요.