PowerShell 스크립트에서 성가신 빨간 오류 메시지를 보는 것이 지겹지 않으신가요? 이 메시지들이 위협적으로 보일 수 있지만, 적절한 오류 처리는 신뢰할 수 있는 PowerShell 자동화를 구축하는 데 필수적입니다. 이 튜토리얼에서는 오류 유형을 이해하는 것부터 try/catch 블록을 마스터하는 것까지, 스크립트에서 강력한 오류 처리를 구현하는 방법을 배우게 됩니다.
전제 조건
이 튜토리얼은 다음을 가정합니다:
- Windows PowerShell 5.1 또는 PowerShell 7+ 설치됨
- PowerShell 스크립팅에 대한 기본적인 이해
- 오류를 학습 기회로 받아들일 의지!
PowerShell 오류 유형 이해하기
오류 처리를 다루기 전에, PowerShell이 발생시킬 수 있는 두 가지 주요 오류 유형을 이해해야 합니다:
종료 오류
이것들은 심각한 오류입니다 – 스크립트 실행을 완전히 중단시키는 오류입니다. 종료 오류는 다음과 같은 경우에 발생합니다:
- 스크립트에 구문 오류가 있어 파싱을 방해할 때
- .NET 메서드 호출에서 처리되지 않은 예외가 발생할 때
ErrorAction Stop
를 명시적으로 지정할 때- 치명적인 런타임 오류로 인해 계속 진행할 수 없을 때
비종료 오류
이것들은 스크립트를 중단시키지 않는 보다 일반적인 운영 오류입니다:
- 파일을 찾을 수 없는 오류
- 권한 거부 시나리오
- 네트워크 연결 문제
- 유효하지 않은 매개변수 값
오류 조치 매개변수: 당신의 첫 번째 방어선
실용적인 예제로 시작해 보겠습니다. 다음은 특정 날짜보다 오래된 파일을 제거하려고 시도하는 스크립트입니다:
param ( [Parameter(Mandatory)] [string]$FolderPath, [Parameter(Mandatory)] [int]$DaysOld ) $Now = Get-Date $LastWrite = $Now.AddDays(-$DaysOld) $oldFiles = (Get-ChildItem -Path $FolderPath -File -Recurse).Where{$_.LastWriteTime -le $LastWrite} foreach ($file in $oldFiles) { Remove-Item -Path $file.FullName Write-Verbose -Message "Successfully removed [$($file.FullName)]." }
기본적으로 Remove-Item
은 비종료 오류를 생성합니다. 이를 종료 오류로 생성하여 우리가 포착할 수 있도록 하려면 -ErrorAction Stop
을 추가하세요:
Remove-Item -Path $file.FullName -ErrorAction Stop
Try/Catch 블록: 오류 처리의 다목적 도구
이제 파일 제거를 try/catch 블록으로 감싸봅시다:
foreach ($file in $oldFiles) { try { Remove-Item -Path $file.FullName -ErrorAction Stop Write-Verbose -Message "Successfully removed [$($file.FullName)]." } catch { Write-Warning "Failed to remove file: $($file.FullName)" Write-Warning "Error: $($_.Exception.Message)" } }
try 블록에는 오류가 발생할 가능성이 있는 코드가 포함되어 있습니다. 오류가 발생하면 실행은 catch 블록으로 이동합니다(종료 오류인 경우) 여기서 다음을 수행할 수 있습니다:
- 오류 기록
- 수정 조치 취하기
- 관리자에게 알리기
- 스크립트 실행을 원활하게 계속하기
$Error와 함께 작업하기: 오류 조사 도구
PowerShell은 자동 $Error
변수에 오류 객체의 배열을 유지합니다. $Error
를 PowerShell의 “블랙 박스 레코더”라고 생각하세요. 이는 PowerShell 세션 중 발생하는 모든 오류를 기록하여 문제 해결 및 디버깅에 매우 유용합니다.
다음은 $Error
를 사용해야 할 때와 이유입니다:
-
과거 오류 문제 해결: 빨간 오류 메시지를 놓쳤더라도
$Error
는 이력을 유지합니다:# 가장 최근 오류 세부정보 보기 $Error[0] | Format-List * -Force # 마지막 5개의 오류 보기 $Error[0..4] | Select-Object CategoryInfo, Exception # 특정 유형의 오류 검색 $Error | Where-Object { $_.Exception -is [System.UnauthorizedAccessException] }
-
스크립트 디버깅:
$Error
를 사용하여 무엇이 잘못되었고 어디서 발생했는지 이해하세요:# 오류가 발생한 정확한 줄 번호와 스크립트 확인 $Error[0].InvocationInfo | Select-Object ScriptName, ScriptLineNumber, Line # 전체 오류 호출 스택 보기 $Error[0].Exception.StackTrace
-
오류 복구 및 보고: 상세 오류 보고서를 생성하는 데 적합합니다:
# 오류 보고서 생성 function Write-ErrorReport { param($ErrorRecord = $Error[0])
[PSCustomObject]@{ TimeStamp = Get-Date ErrorMessage = $ErrorRecord.Exception.Message ErrorType = $ErrorRecord.Exception.GetType().Name Command = $ErrorRecord.InvocationInfo.MyCommand ScriptLine = $ErrorRecord.InvocationInfo.Line ErrorLineNumber = $ErrorRecord.InvocationInfo.ScriptLineNumber StackTrace = $ErrorRecord.ScriptStackTrace }
}
-
세션 관리: 오류를 정리하거나 오류 상태를 확인하세요:
# 오류 기록 지우기 (스크립트 시작 시 유용함) $Error.Clear() # 총 오류 수 세기 (오류 기준 점검에 좋음) if ($Error.Count -gt 10) { Write-Warning "높은 오류 수가 감지되었습니다: $($Error.Count) 오류" }
이러한 개념을 결합한 실제 예:
function Test-DatabaseConnections { $Error.Clear() # Start fresh try { # Attempt database operations... } catch { # If something fails, analyze recent errors $dbErrors = $Error | Where-Object { $_.Exception.Message -like "*SQL*" -or $_.Exception.Message -like "*connection*" } if ($dbErrors) { Write-ErrorReport $dbErrors[0] | Export-Csv -Path "C:\\Logs\\DatabaseErrors.csv" -Append } } }
전문가 팁:
$Error
는 PowerShell 세션 당으로 유지됩니다- 기본 용량은 256개의 오류로 제한됩니다 (
$MaximumErrorCount
에 의해 제어됨) - 고정 크기 배열입니다 – 가득 차면 새로운 오류가 오래된 것을 밀어냅니다
- 항상 먼저
$Error[0]
를 확인하세요 – 가장 최근의 오류입니다 - 중요한 스크립트 시작 시
$Error
를 지우는 것을 고려하여 깨끗한 오류 추적을 유지
여러 개의 Catch 블록: 목표별 오류 처리
마치 모든 집 수리 작업에 같은 도구를 사용하지 않는 것처럼, PowerShell 오류를 동일한 방식으로 처리해서는 안 됩니다. 여러 catch 블록을 사용하면 다른 유형의 오류에 대해 다르게 응답할 수 있습니다.
작동 방식은 다음과 같습니다:
try { Remove-Item -Path $file.FullName -ErrorAction Stop } catch [System.UnauthorizedAccessException] { # This catches permission-related errors Write-Warning "Access denied to file: $($file.FullName)" Request-ElevatedPermissions -Path $file.FullName # Custom function } catch [System.IO.IOException] { # This catches file-in-use errors Write-Warning "File in use: $($file.FullName)" Add-ToRetryQueue -Path $file.FullName # Custom function } catch [System.Management.Automation.ItemNotFoundException] { # This catches file-not-found errors Write-Warning "File not found: $($file.FullName)" Update-FileInventory -RemovePath $file.FullName # Custom function } catch { # This catches any other errors Write-Warning "Unexpected error: $_" Write-EventLog -LogName Application -Source "MyScript" -EntryType Error -EventId 1001 -Message $_ }
마주칠 수 있는 일반 오류 유형:
[System.UnauthorizedAccessException]
– 권한 거부[System.IO.IOException]
– 파일 잠금/사용 중[System.Management.Automation.ItemNotFoundException]
– 파일/경로를 찾을 수 없음[System.ArgumentException]
– 잘못된 인수[System.Net.WebException]
– 네트워크/웹 문제
이를 실제로 적용해보는 실제 예제:
function Remove-StaleFiles { [CmdletBinding()] param( [string]$Path, [int]$RetryCount = 3, [int]$RetryDelaySeconds = 30 ) $retryQueue = @() foreach ($file in (Get-ChildItem -Path $Path -File)) { $attempt = 0 do { $attempt++ try { Remove-Item -Path $file.FullName -ErrorAction Stop Write-Verbose "Successfully removed $($file.FullName)" break # Exit the retry loop on success } catch [System.UnauthorizedAccessException] { if ($attempt -eq $RetryCount) { # Log to event log and notify admin $message = "Permission denied after $RetryCount attempts: $($file.FullName)" Write-EventLog -LogName Application -Source "FileCleanup" -EntryType Error -EventId 1001 -Message $message Send-AdminNotification -Message $message # Custom function } else { # Request elevated permissions and retry Request-ElevatedAccess -Path $file.FullName # Custom function Start-Sleep -Seconds $RetryDelaySeconds } } catch [System.IO.IOException] { if ($attempt -eq $RetryCount) { # Add to retry queue for later $retryQueue += $file.FullName Write-Warning "File locked, added to retry queue: $($file.FullName)" } else { # Wait and retry Write-Verbose "File in use, attempt $attempt of $RetryCount" Start-Sleep -Seconds $RetryDelaySeconds } } catch { # Unexpected error - log and move on $message = "Unexpected error with $($file.FullName): $_" Write-EventLog -LogName Application -Source "FileCleanup" -EntryType Error -EventId 1002 -Message $message break # Exit retry loop for unexpected errors } } while ($attempt -lt $RetryCount) } # Return retry queue for further processing if ($retryQueue) { return $retryQueue } }
여러 Catch 블록에 대한 전문가 팁:
- 순서가 중요합니다 – 더 구체적인 예외를 먼저 두세요
- 각 오류 유형을 일관되게 처리하기 위해 사용자 정의 함수를 사용하세요
- 일시적인 오류에 대한 재시도 로직을 고려하세요
- 다양한 오류 유형을 서로 다른 위치에 기록하세요
- 가능한 한 구체적인 예외 유형을 사용하세요
- 각 오류 유형을 의도적으로 발생시켜 각 catch 블록을 테스트하세요
Finally 블록 사용: 스스로 정리하기
finally 블록은 청소 팀과 같아서 오류가 발생하든 발생하지 않든 항상 실행됩니다. 이것은 다음과 같은 작업에 완벽합니다:
- 파일 핸들 닫기
- 데이터베이스 연결 끊기
- 시스템 리소스 해제
- 원래 설정 복원
실용적인 예는 다음과 같습니다:
try { $stream = [System.IO.File]::OpenRead($file.FullName) # Process file contents here... } catch { Write-Warning "Error processing file: $_" } finally { # This runs even if an error occurred if ($stream) { $stream.Dispose() Write-Verbose "File handle released" } }
finally를 책임감 있는 캠퍼의 규칙처럼 생각하세요: “여행 중 어떤 일이 있었든, 떠나기 전에 항상 캠프장을 정리하세요.”
오류 처리 모범 사례
-
오류 작업에 대해 구체적으로 지정하세요
ErrorAction Stop
을 무작위로 사용하는 대신, 오류를 포착해야 하는 명령에서 선택적으로 사용하세요. -
오류 변수를 사용하세요
Remove-Item $path -ErrorVariable removeError if ($removeError) { Write-Warning "항목 제거 실패: $($removeError[0].Exception.Message)" }
-
적절하게 오류 기록하기
- 회복 가능한 오류에는 Write-Warning을 사용하십시오
- 심각한 문제에는 Write-Error를 사용하십시오
- 중대한 실패에는 Windows 이벤트 로그에 기록하는 것을 고려하십시오
-
리소스 정리
파일 핸들이나 네트워크 연결과 같은 리소스를 정리하기 위해 반드시 finally 블록을 사용하십시오. -
오류 처리 테스트
오류를 의도적으로 발생시켜 오류 처리가 예상대로 작동하는지 확인하십시오.
모두 함께 넣기
다음은 이러한 모범 사례를 포함한 완전한 예제입니다:
function Remove-OldFiles { [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$FolderPath, [Parameter(Mandatory)] [int]$DaysOld, [string]$LogPath = "C:\\Logs\\file-cleanup.log" ) try { # Validate input if (-not (Test-Path -Path $FolderPath)) { throw "Folder path '$FolderPath' does not exist" } $Now = Get-Date $LastWrite = $Now.AddDays(-$DaysOld) # Find old files $oldFiles = Get-ChildItem -Path $FolderPath -File -Recurse | Where-Object {$_.LastWriteTime -le $LastWrite} foreach ($file in $oldFiles) { try { Remove-Item -Path $file.FullName -ErrorAction Stop Write-Verbose -Message "Successfully removed [$($file.FullName)]" # Log success "$(Get-Date) - Removed file: $($file.FullName)" | Add-Content -Path $LogPath } catch [System.UnauthorizedAccessException] { Write-Warning "Access denied to file: $($file.FullName)" "$ErrorActionPreference - Access denied: $($file.FullName)" | Add-Content -Path $LogPath } catch [System.IO.IOException] { Write-Warning "File in use: $($file.FullName)" "$(Get-Date) - File in use: $($file.FullName)" | Add-Content -Path $LogPath } catch { Write-Warning "Unexpected error removing file: $_" "$(Get-Date) - Error: $_ - File: $($file.FullName)" | Add-Content -Path $LogPath } } } catch { Write-Error "Critical error in Remove-OldFiles: $_" "$(Get-Date) - Critical Error: $_" | Add-Content -Path $LogPath throw # Re-throw error to calling script } }
이 구현은 다음을 제공합니다:
- 입력 매개변수를 유효성 검사합니다
- 일반 오류에 대해 특정 catch 블록을 사용합니다
- 성공과 실패 모두 기록합니다
- 문제 해결을 위한 자세한 출력을 제공합니다
- 중대한 오류는 호출 스크립트로 다시 throw합니다
결론
적절한 오류 처리는 신뢰할 수 있는 PowerShell 스크립트에 중요합니다. 오류 유형을 이해하고 try/catch 블록을 효과적으로 사용함으로써, 실패를 우아하게 처리하고 의미 있는 피드백을 제공하는 스크립트를 작성할 수 있습니다. 오류 처리를 철저히 테스트하는 것을 기억하세요 – 제품에서 문제를 해결할 때 미래의 자신이 감사할 것입니다!
자, 그 오류들을 잡아보세요! 기억하세요 – 처리되지 않은 오류만이 나쁜 오류입니다.
Source:
https://adamtheautomator.com/powershell-error-handling/