PowerShell Try-Catch: 오류 처리 가이드
Aug 25, 2025
PowerShell 오류 처리는 Try-Catch-Finally 블록을 사용해 종료(terminating) 및 비종료(non-terminating) 오류를 관리함으로써, 무인 실행에서도 스크립트가 신뢰성을 유지하도록 합니다. Try는 위험한 코드를 감싸고, Catch는 예외에 응답하며, Finally는 리소스를 정리합니다. 예를 들어 -ErrorAction, $ErrorActionPreference, 그리고 $Error를 사용하면 관리자는 엄격한 처리 방침을 적용하고, 자세한 오류 메시지를 수집하며, 복구를 자동화할 수 있습니다. 이러한 방식은 가동 중지 시간을 줄이고 데이터 손실을 방지하며, 규모에 맞춰 견고한 스크립팅을 가능하게 합니다.
PowerShell에서의 오류 처리 소개
많은 PowerShell 스크립트는 무인으로 실행되도록 설계되어 있으므로 오류를 원활하게 처리할 수 있도록 보장하는 것이 매우 중요합니다. 적절한 오류 처리는 미완료 작업과 시스템 가동 중단부터 데이터 손실에 이르기까지 다양한 문제를 예방하는 데 도움이 됩니다. 오류 처리는 스크립트 실행을 중단하는 종료(terminating) 오류와, 스크립트는 계속 실행되지만 출력에 영향을 줄 수 있는 비종료(non-terminating) 오류 모두를 고려해야 합니다.
PowerShell에서 오류 처리를 위한 가장 유용한 메커니즘 중 하나는 Try-Catch-Finally 블록입니다. Try 블록에는 예외를 발생시킬 수 있는 코드가 들어 있습니다. 그런 일이 발생하면 제어가 Catch 블록으로 전달되며, 여기서는 문제가 있는 파일을 건너뛰거나 잘못된 입력을 처리하는 등의 조치를 수행하는 동시에 이벤트를 로깅하여 오류를 처리합니다. Finally 블록은 항상 실행되어 파일을 닫기, 리소스 해제, 정보 로깅 같은 정리 작업을 수행합니다. 예를 들어, PowerShell에서 Try Catch 를 위한 자세한 오류 메시지를 만들면 문제를 해결하기 위해 적절한 조치를 취할 수 있습니다.
이 문서는 Try-Catch-Finally 블록을 심층적으로 다룹니다. 또한 PowerShell에서의 다른 오류 처리 방법도 함께 알아볼 예정인데, 여기에는 ErrorAction 매개변수를 사용해 cmdlet이 오류에 어떻게 응답하는지 제어하는 방법과, $Error 변수를 사용해 오류 기록을 저장하는 방법이 포함됩니다.
PowerShell의 오류란 무엇인가요?
오류는 스크립트의 정상적인 실행 흐름에 영향을 주거나, 완전히 실행을 중단시킬 수 있는 이벤트입니다. 오류의 일반적인 원인으로는 잘못된 문법, 파일 누락, 권한 부족 또는 사용할 수 없는 리소스 등이 있습니다. 오류에는 두 가지 주요 유형이 있는데, 종료(terminating) 오류와 비종료(non-terminating) 오류입니다.
종료(terminating) 오류: 스크립트를 중단시키는 치명적 오류
종료(terminating) 오류는 적절하게 처리하지 않는 한 즉시 스크립트 실행을 중단시킵니다. 이러한 오류는 PowerShell이 복구할 수 없는 문제를 만났을 때 발생합니다. 예를 들어 구문(syntax) 오류, 존재하지 않는 변수 또는 파일을 호출하는 경우, 또는 요청된 작업을 수행할 만큼 권한이 충분하지 않은 경우 등이 있습니다.
예를 들어, 아래 스크립트는 users.txt 파일을 가져오려고 시도합니다:
Get-Content C:\users.txt | Get-ADUser -Properties name | Select-Object -Property SamAccountName,name
지정한 위치에 이 파일이 존재하지 않거나 스크립트를 실행하는 계정에 해당 파일에 접근할 권한이 없다면, 스크립트는 종료(terminating) 오류를 생성하여 아래와 같이 스크립트 실행을 중단합니다:
비종료 오류: 스크립트가 계속 실행되도록 허용하는 오류
PowerShell의 비종료 오류는 스크립트의 실행을 중단하지 않는 오류입니다. 이러한 오류는 여러 개의 개체를 처리하려는 cmdlet에서 흔히 발생하며, 하나의 실패가 반드시 전체 스크립트를 중단시키지는 않습니다.
예를 들어, 이전 예제에 사용한 스크립트를 실행하되 이번에는 파일이 실제로 존재하고 해당 파일에 접근할 수 있는 권한도 있다고 가정해 보겠습니다. 하지만 도메인 컨트롤러에 대한 연결이 없습니다. 이 경우 첫 번째 명령은 실행되어 파일의 내용을 가져옵니다. 그러나 다음 명령이 실패하면서 파일에 있는 세 개의 개체 각각에 대해 비종료 오류가 생성됩니다.
이제 도메인 컨트롤러에 대한 연결이 복구되었다고 가정해 보겠습니다. 하지만 파일 안의 한 개체에 잘못된 사용자 이름이 있습니다. 스크립트를 실행하면 해당 개체에 대해서는 아래에 표시된 비종료 오류가 생성되지만, 다른 개체들은 스크립트에 의해 계속 처리됩니다.
오류는 어디에 저장되는가: $Error 변수
$Error 는 스크립트 실행 중 발생한 최근 오류를 저장하는 자동 배열입니다. 이 기능 덕분에 스크립트를 디버깅하고 문제를 해결하는 데 매우 유용합니다. 가장 최근 오류는 인덱스 [0]에 저장됩니다.
예를 들어, 다음 스크립트가 존재하지 않는 파일을 가져오려고 하면, 그 결과 발생한 오류를 $Error 에서 가져와 친절한 메시지로 표시합니다:
# trying to get a file which doesn't exist, and an error is generated.
Get-Item "C:\file44.txt"
# Display a message along with the last error.
Write-Host "Oops! Something went wrong Here's the error: $($Error[0])"
Try, Catch, Finally 블록 이해하기
오류를 자연스럽게 처리하는 좋은 방법은 Try-Catch-Finally 블록을 구현하는 것입니다.
Active Directory란 무엇인가요?
전자책 다운로드
Try 블록: 오류가 발생할 수 있는 코드가 들어 있는 위치
Try 블록은 오류를 일으킬 수 있는 코드를 감싸는 역할을 합니다. 예를 들어, 아래 스크립트는 메모장(Notepad) 프로세스를 중지하도록 설계되어 있습니다. Try 블록은 해당 프로세스가 현재 실행 중이면 중지하지만, 실행 중이 아니라면 실행이 Catch 블록으로 넘어가 스크립트가 크래시(중단)되지 않도록 합니다.
try {
# Attempt to stop a process that may not be running
Stop-Process -Name "Notepad" -ErrorAction Stop
# If no error occurs, this line will execute
Write-Host "Notepad process stopped successfully."
}
catch {
# Handle the error gracefully
Write-Host "Oops! Could not stop the process. Error: $($_.Exception.Message)"
}
Catch 블록: 오류가 발생했을 때 처리하는 방법
Catch 블록은 Try 블록에서 발생하는 예외를 처리하는 데 사용됩니다. 아래의 PowerShell Try Catch 예제에서 스크립트는 사용자에게 숫자를 입력하라고 요청하고, 입력한 숫자로 10을 나눈 뒤 결과를 출력합니다. 하지만 입력 숫자가 0이면 0으로 나눌 수 없기 때문에 나누기 시도가 오류를 발생시킵니다. 이 경우 Catch 블록이 실행되어 숫자 대신 오류 메시지를 출력합니다.
try {
$number = [int](Read-Host "Enter a number")
Write-Output "Result: $(10 / $number)"
}
catch {
Write-Output "Error: Invalid input or division by zero!"
}
finally {
Write-Output "Program execution completed."
}
Finally 블록: 오류가 발생하든 발생하지 않든 항상 실행되는 코드
Finally 블록은 예외가 발생했는지와 관계없이 항상 실행됩니다.
예를 들어, 다음 스크립트는 요소가 세 개뿐인 배열을 정의하지만 다섯 번째 요소를 출력하려고 합니다. 이 예외는 Catch 블록에서 처리되며, 오류가 출력됩니다. 그러나 Finally 블록도 함께 실행되므로 추가 메시지가 출력됩니다.
try {
# Define an array
$numbers = @(1, 2, 3)
# Access an invalid index using a .NET method that throws an exception
$value = $numbers.GetValue(5)
Write-Host "Value: $value"
}
catch [System.IndexOutOfRangeException] {
# Handle the specific exception
Write-Host "Error: Index out of range exception caught."
}
finally {
# This block always executes
Write-Host "Execution completed."
}
고급 예제: 서로 다른 유형의 예외를 처리하기 위해 여러 Catch 블록 사용
스크립트에는 서로 다른 유형의 예외를 처리하기 위해 여러 Catch 블록을 포함할 수 있으며, 이를 통해 더 세밀한 오류 처리가 가능합니다. 실제로 .Net 프레임워크는 다음을 포함하여 PowerShell Try Catch 예외 메시지 유형을 풍부하게 제공합니다.
- System.DivideByZeroException — 어떤 수든 0으로 나누려고 할 때 발생합니다
- System.FormatException — 숫자가 아닌 입력을 숫자로 변환하려고 할 때 발생합니다
- System.ArgumentNullException — 전달된 인수가 null이지만 null이어서는 안 될 때 발생합니다
- System.IO.IOException — I/O 오류가 발생할 때 발생(throw)합니다
예를 들어, 다음 스크립트는 사용자에게 두 숫자를 입력하도록 요청하고, 이를 정수 형식으로 변환한 뒤 첫 번째 숫자를 두 번째 숫자로 나누고 결과를 출력합니다. 첫 번째 Catch 블록은 사용자의 입력을 정수 형식으로 변환할 수 없을 가능성을 처리하며, 두 번째 Catch 블록은 0으로 나누기를 시도할 가능성을 처리합니다.
try {
# Prompt user for first input
$num1 = Read-Host "Enter the first number"
$num1 = [int]$num1 # Convert to integer (may cause FormatException)
# Prompt user for second input
$num2 = Read-Host "Enter the second number"
$num2 = [int]$num2 # Convert to integer (may cause FormatException)
# Perform division (may cause DivideByZeroException)
$result = $num1 / $num2
# Print result
Write-Host "Result: $result"
}
catch [System.FormatException] {
# Handling invalid input error
Write-Host "Error: Please enter only numeric values!"
}
catch [System.DivideByZeroException] {
# Handling division by zero error
Write-Host "Error: Cannot divide by zero!"
}
catch {
# Handling unexpected errors
Write-Host "An unexpected error occurred: $_"
}
finally {
# Code that always runs
Write-Host "Execution completed."
}
고급 오류 처리 기법
더욱 견고하고 유지보수하기 쉬운 스크립트를 만들려면 PowerShell 과 함께 다음 오류 처리 기법을 사용하는 것을 고려하세요 Try and Catch 블록:
- 다음을 사용하세요 $ErrorActionPreference = “Stop” 모든 오류가 종료(terminating)로 처리되도록 하여, 더 쉽게 포착할 수 있게 합니다.
- 개별 명령에 -ErrorAction Stop 을 사용하면 전체 스크립트에 영향을 주지 않으면서 더 세밀한 제어를 할 수 있습니다.
- 다음을 사용하세요 $_.Exception.Message 및 $PSItem 을 사용해 오류 세부 정보를 확인합니다.
특정 예외 포착하기
다음 스크립트는 사용자 입력을 사용자로부터 정수로 변환한 다음 명령을 실행하기 위한 것입니다. 첫 번째 Catch 블록은 잘못된 사용자 입력이 들어올 가능성을 처리하고, 두 번째 블록은 잘못된 명령을 실행하려는 시도를 처리합니다. 둘 중 어떤 예외가 발생하더라도 스크립트는 사용자 지정 메시지를 출력합니다.
try {
# Attempt to convert user input to an integer (may throw FormatException)
$number = Read-Host "Enter a number"
$number = [int]$number
# Try running a non-existent command (may throw CommandNotFoundException)
NonExistent-Command
}
catch [System.FormatException] {
Write-Host "Error: Invalid input. Please enter a valid number."
}
catch [System.Management.Automation.CommandNotFoundException] {
Write-Host "Error: Command not found. Please check the command name."
}
catch {
Write-Host "An unexpected error occurred: $_"
}
finally {
Write-Host "Execution completed."
}
$_.Exception.Message 및 $PSItem을 사용해 오류 세부 정보에 액세스하기
다음 Catch 블록에서 오류 세부 정보에 액세스하려면 다음 변수를 사용할 수 있습니다:
- $_.Exception.Message 는 관련된 오류 부분만 포함하여, 사용자가 이해하기 쉬운 Try Catch PowerShell 오류 메시지를 제공합니다.
- $PSItem 은 오류 유형, 소스, 스택 트레이스를 포함한 전체 오류 개체를 보관합니다.
다음 스크립트는 이러한 변수들이 제공하는 출력의 차이를 보여 줍니다:
try {
# Attempt to open a non-existent file
Get-Content "C:\File1.txt" -ErrorAction Stop
}
catch {
# Using $_.Exception.Message to get detailed error information
Write-Host "Error occurred: $($_.Exception.Message)"
# Using $PSItem (same as $_) to display full error details
Write-Host "Full Error Details: $PSItem"
}
finally {
Write-Host "Execution completed."
}
Trap 문 사용
A Trap 문은 스크립트에서 오류가 발생하는 위치와 무관하게 특정 오류가 발생했을 때 실행될 코드를 정의합니다. 그 결과, 개별 PowerShell Try-Catch 블록보다 더 높은(전역) 수준에서 오류를 처리할 수 있는 메커니즘을 제공합니다.
다음 스크립트는 0으로 나누려는 시도가 발생할 가능성을 처리하기 위해 PowerShell에서 Try-Catch 를 사용합니다. Trap 문은 발생할 수 있는 다른 모든 오류를 포착하며, 이 경우에는 존재하지 않는 파일로 Get-Item cmdlet을 사용하는 것입니다.
# Global error handler using trap
trap {
Write-Host "Global Error Handler (trap): $_"
continue # Allows the script to continue execution
}
function Test-TryCatch {
try {
Write-Host "Inside Try Block"
1 / 0 # This will cause a division by zero error
Write-Host "This line will not execute due to the error above."
} catch {
Write-Host "Caught in Try-Catch: $_"
}
}
Write-Host "Before function call"
Test-TryCatch
Write-Host "After function call"
# Triggering another error outside try-catch to see if trap works
Write-Host "Triggering an error outside Try-Catch"
Get-Item "C:\NonExistentFile.txt" -ErrorAction Stop # Force a terminating error
Write-Host "Script completed"
PowerShell에서 종료되지 않는(Non-Terminating) 오류 처리
Catch 블록은 종료되는 오류(terminating errors)를 처리하도록 설계되었습니다. 비종료 오류(non-terminating errors)는 스크립트 실행을 중단하지 않으므로 Catch 블록은 비종료 오류를 종료되는 오류로 변환하기 위해 -ErrorAction Stop 매개 변수를 사용할 수 있습니다. 이 매개 변수는 스크립트가 비종료 오류를 종료되는 오류로 처리하도록 강제하며, 그러면 Catch 블록이 이를 적절히 처리할 수 있게 됩니다.
다음 스크립트를 실행한다고 가정하지만, 스크립트가 액세스하려는 폴더가 존재하지 않는 경우입니다. Try 블록은 -ErrorAction Stop 를 사용해 이 비종료 오류를 종료되는 오류로 변환하므로, Catch 블록에서 처리됩니다.
$directoryPath = "C:\NonExistentFolder"
try {
Write-Host "Checking if directory exists..."
Get-ChildItem -Path $directoryPath -ErrorAction Stop # Force a terminating error if the directory doesn't exist
Write-Host "Directory found."
} catch {
Write-Host "Error: The directory '$directoryPath' was not found."
}
PowerShell에서 Try-Catch를 사용하는 모범 사례
코드의 명확성, 효율성, 유지 관리성을 극대화하려면 오류 처리 블록을 신중하게 작성하세요. 특히 PowerShell Try-Catch 는 진짜 예외(genuine exceptions)만을 위해 사용해야 합니다. Try 블록의 코드를 예외를 발생시킬 수 있는 작업으로만 제한하면, 예외의 원인을 더 쉽게 파악하고 스크립트를 디버그할 수 있습니다. 다른 경우에는 if-else 또는 switch 문을 고려하세요.
그 밖의 주요 모범 사례는 다음과 같습니다.
- ‘Try’ 블록 안의 코드가 단일 책임 원칙(single responsibility principle)을 따르도록 하세요. 즉, 코드 한 조각이 특정 작업을 수행해야 합니다.Try 블록은 단일 책임 원칙을 따릅니다. 즉, 코드의 각 부분이 특정 작업을 수행해야 합니다.
- 서로 다른 예외 유형을 처리하려면 별도의 Catch 블록을 사용하세요.
- 코드를 정리하고 리소스를 해제하려면 항상 Finally 블록을 포함하세요.
다음 스크립트는 이러한 모범 사례 중 일부를 보여줍니다. 이 스크립트는 if 문과 함께 test-path 를 사용해 파일에 접근하기 전에 해당 파일이 존재하는지 확인합니다. 파일이 존재하지 않으면 else 문을 사용해 오류를 표시합니다. Try-Catch 블록은 파일의 내용을 읽기 위해서만 사용됩니다. 스크립트가 권한 부족 같은 오류를 만나면 Catch 블록이 예외를 처리하고 오류 메시지를 표시합니다.
# Define the path to the file
$filePath = "D:\NonExistentFile.txt"
# Check if the file exists
if (Test-Path -Path $filePath) {
try {
# Try to read the file contents
$fileContents = Get-Content -Path $filePath -ErrorAction Stop
Write-Host "File contents: $fileContents"
}
catch {
# Handle any exceptions that occur while reading the file
Write-Host "Error: Unable to read the file. Exception Message: $_"
}
} else {
# If the file does not exist, display an error message
Write-Host "Error: The file does not exist."
}
PowerShell Try-Catch 및 스크립트 계속 실행
Continue 문 사용하기
스크립트가 Try-Catch 블록 안에서 오류를 만나면, 기본 동작은 스크립트가 Catch 블록에서 예외를 처리한 뒤 실행을 중지하는 것입니다. 하지만 파일 여러 개를 일괄 처리하거나 여러 서버에 대한 연결을 테스트하는 경우처럼, 오류를 처리한 후에도 실행을 계속하고 싶은 상황이 있습니다.
단 하나의 실패로 인해 전체 반복이 중단되지 않도록 하려면 Continue 문을 사용하세요. 예를 들어, 아래 스크립트는 여러 서버의 연결을 테스트합니다. Continue 문은 한 서버에서 오류가 발생하더라도 실행이 계속되도록 보장합니다.
# List of server names (replace with actual server names or IPs)
$servers = @("lhehost9", "lhehost11", "lhehost10")
foreach ($server in $servers) {
try {
Write-Host "Attempting to connect to $server..."
# Simulating a remote connection (Replace with actual connection command)
Test-Connection -ComputerName $server -Count 2 -ErrorAction Stop
Write-Host "Successfully connected to $server.`n"
} catch {
Write-Host "Error: Failed to connect to $server. Skipping to next server...`n"
continue # Continue to the next server in the loop
}
}
Write-Host "Script execution completed."
$ErrorActionPreference 변수를 사용하기
오류가 발생한 뒤에도 스크립트 실행을 계속하도록 강제하는 또 다른 방법은 $ErrorActionPreference 변수를 사용하는 것입니다. Try-Catch 블록은 범위 내에서 오류 처리를 가능하게 하는 반면, $ErrorActionPreference 는 종료되지 않는(non-terminating) 오류에 대해 PowerShell이 어떻게 응답할지 제어하는 전역 설정입니다.
이미 $ErrorActionPreference를 $ErrorActionPreference 값으로 설정할 수 있다는 것을 보았습니다. “Stop” 이렇게 하면 모든 오류가 종료를 발생시키는 것으로 처리되어, 오류를 더 쉽게 포착할 수 있습니다. 하지만 이 변수에는 다음을 포함한 추가 설정이 있습니다: Continue (기본값), Silentlycontinue, Inquire 및 Ignore 입니다. 이 변수의 설정을 선택함으로써 스크립트 전반에서 오류를 어떻게 관리할지 정의할 수 있습니다.
PowerShell Finally 블록: 리소스 정리(클린업) 보장
앞에서 언급했듯이 Finally 블록은 스크립트 실행 중 오류가 발생했는지 여부와 관계없이 Try 및 Catch 블록 다음에 실행됩니다. 파일을 닫고, 연결을 종료하며, 리소스를 해제하는 등의 정리(클린업) 작업에 자주 사용됩니다.
예를 들어, 다음 스크립트는 파일을 열고 그 안에 “hello world”를 씁니다. 오류가 발생하면 Catch 블록이 메시지를 출력합니다. 어떤 경우든 Finally 블록은 리소스 누수가 발생하지 않도록 파일 스트림이 닫히도록 보장합니다.
try {
$filePath = "D:\Office\Backup\eventviewer_logs.txt"
$fileStream = [System.IO.StreamWriter]::new($filePath)
$fileStream.WriteLine("Hello, World!")
}
catch {
Write-Host "An error occurred: $_"
}
finally {
if ($fileStream) {
$fileStream.Close()
Write-Host "File stream closed successfully."
}
}
PowerShell에서 여러 오류 처리
오류를 저장하고 개수를 세는 변수
PowerShell은 여러 예외를 만나는 스크립트를 감사하고 디버깅하는 데 유용한 두 가지 변수를 제공합니다:
- $Error 는 세션 동안 발생한 오류 목록을 유지합니다.
- $Error.count 는 현재 세션에 기록된 오류의 총 개수를 제공합니다.
정확하게 추적하려면, 세션 중에 필요에 따라 때때로 오류 목록을 지워야 할 수 있습니다. $Error.clear() 를 사용하면 저장된 모든 오류가 제거됩니다.
여러 오류를 처리하기 위해 중첩 Try-Catch 블록 사용
다음 스크립트에는 0으로 나누기를 시도하고 존재하지 않는 파일에 액세스하려는 등 두 가지 서로 다른 잠재적 오류를 처리하기 위해 두 개의 중첩된 Try-Catch 블록이 포함되어 있습니다:
try {
Write-Host "Starting First try block..."
try {
Write-Host "Attempting to divide by zero..."
$result = 1 / 0 # This will cause a divide-by-zero exception
}
catch [System.DivideByZeroException] {
Write-Host "Inner catch: Division by zero error occurred"
throw "Re-throwing error to outer try block"
}
try {
Write-Host "Accessing a file that doesn't exist..."
Get-Content -Path "C:\File1.txt" -ErrorAction Stop
}
catch [System.IO.FileNotFoundException] {
Write-Host "Inner catch: File not found error occurred"
}
}
catch {
Write-Host "Outer catch: Handling an error from inner blocks - $($_.Exception.Message)"
}
finally {
Write-Host "Executing final block for cleanup..."
}
PowerShell의 오류 메시지와 디버깅
자세한 오류 정보에 액세스하기
스크립트 실행 중 오류가 발생하면 오류가 발생했다는 사실만 아는 것으로는 충분하지 않습니다. 문제를 진단하고 수정하려면 자세한 정보가 필요합니다. 앞서 언급했듯이 $Error 변수는 오류 기록의 배열을 유지하며, 가장 최근 오류는 $Error[0] 에 있습니다. 특정 오류 속성에는 Exception, CategoryInfo, InvocationInfo 및 ScriptStackTrace가 포함됩니다.
ScriptStackTrace 속성은 자세한 호출 스택을 제공하므로 매우 유용합니다. 이는 오류로 이어진 함수 호출의 순서와 스크립트의 위치를 보여줍니다. 가장 최근 오류에 대한 이 자세한 정보를 보려면 $Error[0].ScriptStackTrace 을 사용하세요.
사용자 지정 오류 메시지 제공
의미 있고 사용자 친화적인 오류 메시지를 표시하면 스크립트의 사용성과 디버깅 효율이 향상됩니다. 사용자 지정 메시지는 사용자가 무엇이 잘못되었는지 이해하는 데 도움을 주며, 때로는 잘못된 파일 경로, 잘못된 프로세스 이름 또는 권한 부족 같은 문제를 수정하여 오류를 바로잡는 데에도 도움이 될 수 있습니다.
사용자 지정 메시지를 표시하려면 Write-host 또는 write-error 를 Catch 블록에서 사용할 수 있습니다. 추가 정보를 제공하려면 $_.Exception.message 를 포함하세요.
예를 들어, 아래 스크립트를 실행하는 동안 오류(0으로 나누기)가 발생하면 자세한 알림이 콘솔에 출력될 뿐 아니라 관리자에게 이메일로 전송됩니다:
$SMTPServer = "lvkex.ca.lo"
$SMTPPort = 587
$From = "aadministrator@ca.lo"
$To = "administrator@ca.lo"
$Subject = "PowerShell Script Error Alert"
try {
Write-Host "Executing script..."
# Simulate an error (divide by zero)
$result = 1 / 0
}
catch {
# Capture actual error message details
$ErrorMessage = @"
An error occurred in the PowerShell script.
Message: $($_.Exception.Message)
Script: $($MyInvocation.ScriptName)
Line: $($_.InvocationInfo.ScriptLineNumber)
Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
"@
# Log error to console and send email
Write-Host "An error occurred. Sending email notification..."
Send-MailMessage -SmtpServer $SMTPServer -Port $SMTPPort -From $From -To $To -Subject $Subject -Body $ErrorMessage
}
}
PowerShell Try-Catch의 일반적인 사용 사례
데이터 입력 중 오류를 처리하면서 대량으로 사용자 프로필 업데이트
관리자는 종종 PowerShell을 사용해 csv 파일을 기반으로 Active Directory에서 사용자 정보를 자동으로 업데이트합니다. 입력 파일에 잘못된 데이터가 포함될 수 있으므로 Try-Catch를 사용해 오류를 처리하고 로그로 기록하는 것이 중요합니다.
다음 스크립트는 사용자 목록이 포함된 csv 파일을 가져온 뒤 반복을 통해 AD에서 각 사용자의 설명을 업데이트합니다. 결과는 콘솔에 출력하고, 발생한 오류는 로그 파일에 기록합니다.
# Define file paths
$CsvFile = "C:\Members (7).csv"
$LogFile = "C:\logs.txt"
# Import CSV
$Users = Import-Csv -Path $CsvFile
foreach ($User in $Users) {
try {
# Attempt to find the user in AD using CN or SamAccountName
$ADUser = Get-ADUser -LDAPFilter "(cn=$($User.cn))" -Properties SamAccountName, Description -ErrorAction SilentlyContinue
if ($ADUser -eq $null) {
# Log missing users
$ErrorMessage = "$(Get-Date): User '$($User.cn)' not found in AD."
Write-Host $ErrorMessage
Add-Content -Path $LogFile -Value $ErrorMessage
continue # Skip to the next user
}
# Define new description
$NewDescription = "Test update on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
# Update AD user description
Set-ADUser -Identity $ADUser.SamAccountName -Description $NewDescription -ErrorAction Stop
# Log success
Write-Host "Updated: $($ADUser.SamAccountName) - New Description: $NewDescription"
}
catch {
# Capture and log errors
$ErrorMessage = "$(Get-Date): Error updating $($User.cn) ($($ADUser.SamAccountName)) - $($_.Exception.Message)"
Write-Host $ErrorMessage
Add-Content -Path $LogFile -Value $ErrorMessage
}
}
서버 또는 네트워크 연결 문제 처리
Try-Catch 블록은 서버 및 네트워크 연결 문제를 처리하는 데도 유용합니다. 예를 들어, 다음 스크립트는 서버에 대한 연결을 올바른 이름과 잘못된 이름 각각으로 테스트하는 방법을 보여줍니다:
# Define server details
$Server = "ada1.adatum.local"
$LogFile = "C:\log.txt"
try {
# Check if the server responds to a ping
$PingTest = Test-NetConnection -ComputerName $Server
if (-not $PingTest.PingSucceeded) {
throw "Server $Server is not responding to ping."
}
# Check RDP connectivity (port 3389)
$RDPTest = Test-NetConnection -ComputerName $Server -Port 3389
if (-not $RDPTest.TcpTestSucceeded) {
throw "Server $Server is reachable but RDP port 3389 is not open."
}
# If both checks pass
Write-Host "Server $Server is reachable, and RDP is accessible."
}
catch {
# Capture and log the error
$ErrorMessage = "$(Get-Date): Error connecting to server $Server - $($_.Exception.Message)"
Write-Host $ErrorMessage
Add-Content -Path $LogFile -Value $ErrorMessage
}
결론
Try-Catch-Finally 블록은 PowerShell 스크립트에서 오류를 처리하는 데 매우 유용한 도구입니다. ErrorAction 파라미터와 $ErrorActionPreference 및 $Error 변수를 같은 방식으로 결합하면, 예상치 못한 문제가 발생하더라도 원활하게 실행되는 견고한 스크립트를 만들 수 있습니다. 그 결과, 미완료 작업, 시스템 가동 중단 또는 데이터 손실과 같은 문제를 겪지 않을 것이라는 확신을 가지고 작업을 자동화할 수 있습니다.
FAQ
내 PowerShell try-catch가 오류를 잡지 못하는 이유는 무엇인가요?
try-catch 블록이 작동하지 않는 가장 일반적인 이유는 종료되지 않는 오류( non-terminating errors )를 다루고 있기 때문입니다. PowerShell에는 두 가지 유형의 오류가 있습니다. 즉, 실행을 중지하는 terminating 오류와 메시지는 표시하지만 계속 실행되는 non-terminating 오류입니다. 기본적으로 try-catch는 종료되는 오류(terminating errors)만 포착합니다.
비종결 오류를 포착하려면 try 블록 안의 명령에 -ErrorAction Stop 를 추가하세요. 이렇게 하면 PowerShell이 해당 오류를 종결 오류로 처리하도록 강제됩니다:
try {
Get-ChildItem "C:\NonExistent" -ErrorAction Stop
} catch {
Write-Host "Caught the error!"
}
만약 -ErrorAction Stop 이 없다면 오류 메시지는 표시되지만 catch 블록은 절대 실행되지 않습니다.
또 다른 문제는 범위(scope)와 관련된 것입니다. try 블록 안에서 함수나 외부 스크립트를 호출하는 경우, 그로부터 발생한 오류가 올바르게 상위로 전달(bubble up)되지 않을 수 있습니다. 호출하는 함수나 스크립트에도 적절한 오류 처리(error handling)가 사용되는지 확인하거나, $ErrorActionPreference = "Stop" 스크립트 맨 처음에 설정해 기본 동작을 전역으로 변경하세요. 전역 기본 설정을 바꾸면 스크립트의 모든 명령에 영향을 주므로 운영 환경에서는 신중하게 사용해야 합니다.
PowerShell의 try-catch를 사용해도 여전히 오류 메시지가 표시되는 문제를 어떻게 해결하나요?
적절하게 try-catch를 구현했더라도 PowerShell이 catch 블록이 처리하기 전에 오류 메시지를 먼저 표시하기 때문에, 여전히 오류 메시지가 보일 수 있습니다. 이는 비종결 오류가 -ErrorAction Stop 를 통해 종결 오류로 바뀔 때 발생합니다. 오류는 콘솔에 표시된 다음, 핸들러에 의해 포착됩니다.
오류 표시를 완전히 숨기려면 다음을 사용하세요: -ErrorAction SilentlyContinue 그리고 $? (자동 성공 변수) 또는 $Error[0] 가 가장 최근 오류인지 확인합니다:
Get-ChildItem "C:\NonExistent" -ErrorAction SilentlyContinue
if (!$?) {
Write-Host "Command failed silently"
}
이 방식은 눈에 보이는 오류 메시지 없이도 오류 탐지를 제어할 수 있게 해줍니다.
더 깔끔한 해결책을 원한다면 catch 블록에서 오류 정보를 캡처하고 처리 방법을 결정하세요. $_.Exception.Message 를 사용해 오류 세부 정보를 가져온 다음, 필요에 따라 기록하거나 표시합니다. 이렇게 하면 사용자가 보게 될 내용을 정확히 제어할 수 있습니다:
try {
Get-ChildItem "C:\NonExistent" -ErrorAction Stop
} catch {
Write-Warning "Directory access failed: $($_.Exception.Message)"
}
이 방식은 오류 메시지가 사용자 친화적이어야 하고 적절하게 로깅되어야 하는 엔터프라이즈 환경에서 특히 중요합니다.
종료되지 않는(비종결) 오류가 발생할 때 PowerShell의 try-catch를 어떻게 처리하나요?
종료되지 않는 오류는 기본적으로 catch 블록을 트리거하지 않기 때문에 특별한 처리가 필요합니다. 가장 신뢰할 수 있는 방법은 오류를 catch하고 싶은 각 명령에 -ErrorAction Stop 을 사용하는 것입니다. 이렇게 하면 종료되지 않는 오류가 try-catch가 처리할 수 있는 종료 오류로 변환됩니다.
여러 개의 종료되지 않는 오류를 생성하는 함수의 경우, 함수 수준에서 $ErrorActionPreference = "Stop" 을 설정한 다음, 이후에 다시 재설정하는 방법을 고려하세요:
function Test-Function {
$ErrorActionPreference = "Stop"
try {
# your commands here
} catch {
# error handling
} finally {
$ErrorActionPreference = "Continue"
}
}
다른 방법은 -ErrorVariable 매개변수를 사용해 실행을 중단하지 않고 오류를 캡처하는 것입니다. 다음과 같이 명령을 실행하세요: -ErrorVariable myErrors -ErrorAction SilentlyContinue 그런 다음 $myErrors 에 내용이 있는지 확인합니다. 이 방법을 사용하면 흐름을 중단하지 않고 명령 완료 후에 오류를 처리할 수 있습니다:
Get-ChildItem "C:\*" -ErrorVariable problems -ErrorAction SilentlyContinue
if ($problems) {
Write-Host "Found $($problems.Count) errors to review"
}
이 패턴은 대량 작업에 특히 잘 맞습니다. 응답 방법을 결정하기 전에 모든 문제를 수집하려는 경우에 유용합니다.
try-catch에서 ErrorAction Stop과 SilentlyContinue의 차이점은 무엇인가요?
ErrorAction Stop 비종료(Non-terminating) 오류를 즉시 실행을 중단하고 catch 블록을 트리거하는 종료(Terminating) 오류로 변환합니다. 명령이 실패하면 계속할 수 없고, try-catch가 오류를 처리하길 원할 때 사용하세요. 실패 시 프로세스를 중단해야 하는 중요 작업에서 가장 일반적인 선택입니다.
ErrorAction SilentlyContinue 오류 메시지를 숨기고 실행을 계속 진행할 수 있게 하지만, 오류는 catch 블록을 트리거하지 않습니다. 오류는 여전히 발생하며 $Error 에 추가됩니다. 다만 화면에 표시되는 오류 메시지는 없고, 스크립트는 계속 실행됩니다. 이 옵션은 일부 작업이 실패할 것으로 예상하고, 예외 처리 대신 조건 로직을 통해 이를 처리하려는 경우에 유용합니다.
try-catch 시나리오에서 핵심 차이는 제어 흐름입니다. Stop을 사용하면 catch 블록이 실행되고, 다음에 어떤 일이 일어날지 결정할 수 있습니다. SilentlyContinue을 사용하면 $? , $Error 또는 -ErrorVariable 를 사용해 성공 여부를 수동으로 확인해야 합니다. 보안에 초점을 둔 작업에서는 Stop 이 더 나은 오류 가시성과 처리 기능을 제공하여, 접근 실패나 보안 위반이 눈에 띄지 않은 채 지나가지 않도록 보장합니다. 엔터프라이즈 환경에서는 ErrorAction Stop 으로 try-catch를 통해 명시적 오류 처리를 수행하면 더 나은 감사 추적과 예측 가능한 스크립트 동작을 만들 수 있습니다.
PowerShell 루프에서 try-catch를 사용하면서 실행을 계속하려면 어떻게 해야 하나요?
루프에서는 try-catch 동작이 오류가 발생하는 위치와 다음에 무엇이 일어나길 원하는지에 따라 달라집니다. 오류가 발생한 후에도 남은 항목 처리를 계속하려면 try-catch를 루프 안에 배치하고 continue 를 catch 블록에서 사용하세요:
foreach ($item in $items) {
try {
Process-Item $item -ErrorAction Stop
} catch {
Write-Warning "Failed to process $item"
continue
}
}
이 방식은 실패한 항목을 건너뛰고 다음 항목으로 이동합니다.
실패한 작업을 다시 시도하려면 try-catch와 재시도 횟수 카운터를 함께 사용하세요:
foreach ($item in $items) {
$retries = 0
do {
try {
Process-Item $item -ErrorAction Stop
break
} catch {
$retries++
if ($retries -ge 3) {
Write-Error "Failed after 3 attempts: $item"
break
}
Start-Sleep 1
}
} while ($retries -lt 3)
}
이 패턴은 네트워크 작업이나 일시적으로 실패할 수 있는 자원 집약적인 작업에 유용합니다.
성공과 실패를 모두 수집하려면 배열을 사용해 결과를 추적하세요:
$successful = @()
$failed = @()
foreach ($item in $items) {
try {
Process-Item $item -ErrorAction Stop
$successful += $item
} catch {
$failed += @{Item=$item; Error=$_.Exception.Message}
}
}
이 접근 방식은 대량 작업에 대한 완전한 가시성을 제공하며, 이해관계자에게 성공과 실패 모두를 보고해야 하는 데이터 처리, 사용자 관리 또는 시스템 관리 작업에 필수적입니다.
공유하기
더 알아보기
저자 소개
Tyler Reese
제품 관리 부사장, CISSP
소프트웨어 보안 업계에서 20년이 넘는 경력을 쌓아온 Tyler Reese는 오늘날 기업이 직면한 빠르게 변화하는 아이덴티티 및 보안 과제에 대해 깊이 잘 알고 있습니다. 현재 그는 Netwrix Identity and Access Management 포트폴리오의 제품 디렉터로 재직 중이며, 시장 동향을 평가하고 IAM 제품 라인의 방향을 설정하는 일을 포함해 궁극적으로는 엔드 유저의 요구를 충족하는 역할을 담당하고 있습니다. 그의 전문 경력은 Fortune 500 기업을 대상으로 한 IAM 컨설팅부터 대형 D2C(Direct-to-Consumer) 기업의 엔터프라이즈 아키텍트로 일한 경험까지 폭넓게 이어져 있습니다. 그는 현재 CISSP 자격증을 보유하고 있습니다.