Tratamento de erros avançado no PowerShell
LidoNesta aula, você vai aprofundar seu conhecimento em tratamento de erros no PowerShell, explorando erros terminating e não-terminating, a variável automática $PSItem, a técnica de re-throw e boas práticas de logging de erros. Você aprenderá a controlar o fluxo de execução, capturar detalhes de exceções e registrar erros de forma eficiente em seus scripts.
Bem-vindo à aula sobre tratamento de erros avançado no PowerShell. Até agora, você provavelmente já usou blocos try/catch/finally e lidou com erros simples. Nesta aula, vamos explorar conceitos mais profundos, como a diferença entre erros terminating e não-terminating, a variável automática $PSItem, a técnica de re-throw e estratégias eficazes de logging de erros. Esses conhecimentos são essenciais para criar scripts robustos, depuráveis e profissionais.
O tratamento de erros em PowerShell vai muito além de capturar uma mensagem simples. Compreender como o PowerShell classifica erros e como você pode controlar o fluxo de execução é fundamental para escrever código resiliente. Vamos começar desvendando os erros terminating.
Erros terminating
No PowerShell, os erros são classificados em duas categorias: terminating (terminantes) e não-terminating (não terminantes). Um erro terminating interrompe a execução do cmdlet ou script atual imediatamente, e o controle é transferido para o bloco catch correspondente, se houver. Já um erro não-terminating é exibido no console, mas a execução continua com o próximo comando, a menos que você force a interrupção com -ErrorAction Stop ou defina a variável $ErrorActionPreference como Stop.
É crucial saber que muitos cmdlets do PowerShell, como Get-Item ou Remove-Item, geram erros não-terminating por padrão. Para tratá-los como erros fatais dentro de um try/catch, você precisa especificar -ErrorAction Stop ou configurar a preferência global. Vamos ver um exemplo:
# Exemplo de erro não-terminating sem try/catch
Get-Item -Path "C:\ArquivoInexistente.txt" # Não interrompe o script, apenas exibe erro
# Exemplo com -ErrorAction Stop para forçar erro terminante
try {
Get-Item -Path "C:\ArquivoInexistente.txt" -ErrorAction Stop
} catch {
Write-Host "Erro capturado: $($_.Exception.Message)"
}
Além disso, você pode gerar seus próprios erros terminantes usando throw. Isso é útil para validações personalizadas em funções. Por exemplo:
function Testar-Idade {
param([int]$idade)
if ($idade -lt 0 -or $idade -gt 120) {
throw "Idade inválida: $idade. Deve estar entre 0 e 120."
}
Write-Host "Idade válida: $idade"
}
try {
Testar-Idade -idade 150
} catch {
Write-Host "Erro capturado: $($_.Exception.Message)"
}
Entender essa distinção é o primeiro passo para dominar o tratamento de erros avançado.
$PSItem
Dentro do bloco catch, a variável automática $_ (ou $PSItem) representa o erro atual. Ela contém informações detalhadas sobre a exceção, incluindo a mensagem, o tipo, o stack trace, entre outros. Embora $_ seja mais comum, $PSItem é seu equivalente explícito e melhora a legibilidade. Vamos explorar os membros mais úteis dessa variável.
Ao capturar um erro, você pode acessar:
$PSItem.Exception: o objeto de exceção real, com propriedades comoMessage,StackTraceeInnerException.$PSItem.InvocationInfo: fornece informações sobre o comando que gerou o erro, como nome do script, linha e coluna.$PSItem.CategoryInfo: categoria do erro (ex.:ObjectNotFound,PermissionDenied).$PSItem.FullyQualifiedErrorId: identificador único do erro.$PSItem.ScriptStackTrace: rastreamento da pilha de chamadas no script.
Vejamos um exemplo prático que usa essas informações:
try {
# Simulando um erro de divisão por zero
$resultado = 1 / 0
} catch {
Write-Host "Mensagem: $($PSItem.Exception.Message)"
Write-Host "Tipo de Exceção: $($PSItem.Exception.GetType().FullName)"
Write-Host "Comando: $($PSItem.InvocationInfo.MyCommand)"
Write-Host "Linha: $($PSItem.InvocationInfo.ScriptLineNumber)"
Write-Host "Categoria: $($PSItem.CategoryInfo.Category)"
Write-Host "Stack Trace:"
Write-Host $PSItem.ScriptStackTrace
}
Dominar $PSItem permite que você crie mensagens de erro mais informativas e que auxiliem na depuração. Por exemplo, você pode registrar o comando e a linha exata onde o erro ocorreu, facilitando a correção.
Re-throw
Em algumas situações, você pode querer capturar um erro, executar alguma ação (como logar) e, em seguida, relançar o mesmo erro para que ele continue subindo na pilha de chamadas. Isso é chamado de re-throw. No PowerShell, você pode usar a palavra-chave throw dentro do bloco catch sem argumentos, o que relança a exceção atual. Alternativamente, pode usar throw $PSItem para relançar explicitamente.
Por que fazer re-throw? Em arquiteturas em camadas, você pode querer registrar o erro em um nível, mas deixar que o chamador original lide com a exceção. Por exemplo, em uma função que chama outra, você pode capturar, logar e relançar para que o script principal decida como proceder.
Aqui está um exemplo:
function Etapa1 {
throw "Falha na Etapa 1"
}
function Etapa2 {
try {
Etapa1
} catch {
Write-Host "Erro capturado na Etapa2. Relançando..."
throw # Re-throw sem argumentos
}
}
try {
Etapa2
} catch {
Write-Host "Erro final capturado: $($_.Exception.Message)"
}
O throw sem argumentos preserva o stack trace original e a mensagem. Se você usar throw "Nova mensagem", estará criando uma nova exceção, perdendo o contexto original. Portanto, para re-throw, sempre use throw vazio ou throw $PSItem.
Além disso, você pode relançar uma exceção com informações adicionais, mas isso é menos comum. A técnica de re-throw é valiosa para criar logs centralizados sem alterar o comportamento do erro.
Logging de erros
Registrar erros de forma adequada é crucial para monitorar a saúde de seus scripts e aplicações. Em vez de apenas exibir mensagens no console, você pode enviar logs para arquivos, Event Viewer do Windows ou até serviços externos. O PowerShell oferece várias maneiras de fazer logging, e a escolha depende do contexto.
Vamos ver algumas abordagens:
1. Log em arquivo de texto
Usar Out-File ou Add-Content para anexar mensagens a um arquivo de log é simples e eficaz. Você pode formatar a mensagem com data e hora, nível de severidade e detalhes do erro.
$logFile = "C:\Logs\meuScript.log"
function Write-Log {
param(
[string]$Message,
[ValidateSet("Info","Warning","Error")]
[string]$Level = "Info"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logLine = "[$timestamp] [$Level] $Message"
Add-Content -Path $logFile -Value $logLine
}
try {
# Comando que pode falhar
Get-Item -Path "C:\ArquivoInexistente.txt" -ErrorAction Stop
} catch {
$errorMsg = $PSItem.Exception.Message
Write-Log -Message "Erro ao acessar arquivo: $errorMsg" -Level "Error"
Write-Host "Falha. Verifique o log em $logFile" -ForegroundColor Red
}
2. Log estruturado com JSON
Para integração com ferramentas de análise, logs em JSON são mais fáceis de processar. Você pode converter um objeto de erro em JSON usando ConvertTo-Json.
$logFile = "C:\Logs\erros.json"
try {
# Operação arriscada
Remove-Item -Path "C:\Temp\ArquivoImportante.txt" -Force -ErrorAction Stop
} catch {
$errorObject = [PSCustomObject]@{
Timestamp = Get-Date -Format "o"
Level = "Error"
Message = $PSItem.Exception.Message
Script = $PSItem.InvocationInfo.ScriptName
Line = $PSItem.InvocationInfo.ScriptLineNumber
StackTrace = $PSItem.ScriptStackTrace
}
$errorObject | ConvertTo-Json | Add-Content -Path $logFile
}
3. Usando o módulo Microsoft.PowerShell.Utility - Write-Error
O cmdlet Write-Error grava um erro no fluxo de erros, que pode ser redirecionado para um arquivo ou registrado em logs do Windows. Por exemplo, você pode usar Write-Error com -ErrorAction Continue e depois redirecionar o fluxo de erro para um arquivo.
# Redirecionar o fluxo de erro para um arquivo
# Execute o script e capture o erro no arquivo
try {
Write-Error "Falha proposital" -ErrorAction Stop
} catch {
# A mensagem já foi registrada
}
# Para capturar todos os erros de um script em um arquivo, use:
# .\meuScript.ps1 2> erro.log
4. Log no Windows Event Log
Para scripts que rodam como serviços ou tarefas agendadas, registrar no Event Log pode ser apropriado. Use New-EventLog e Write-EventLog (compatível com Windows PowerShell) ou cmdlets do módulo Microsoft.PowerShell.Management.
$logName = "MeuScriptLog"
if (-not [System.Diagnostics.EventLog]::Exists($logName)) {
New-EventLog -LogName $logName -Source "MeuScript"
}
try {
# Ação que pode falhar
throw "Erro de teste"
} catch {
Write-EventLog -LogName $logName -Source "MeuScript" -EventId 1001 -EntryType Error -Message $PSItem.Exception.Message
}
Independente do método, é importante registrar o máximo de contexto possível: timestamp, nível de severidade, mensagem, comando que falhou, linha, stack trace e, se aplicável, dados do usuário. Isso facilita a correção e a auditoria.
Boas Práticas e Observações Finais
Ao implementar tratamento de erros avançado, tenha em mente as seguintes boas práticas:
- Seja consistente: defina uma política de tratamento de erros (como usar sempre
-ErrorAction Stopem cmdlets críticos) e siga-a. - Use
try/catch/finallypara liberar recursos: coloque a limpeza de recursos (como fechar conexões) no blocofinally. - Evite engolir erros silenciosamente: sempre registre ou relance o erro, para que falhas não passem despercebidas.
- Capture exceções específicas: se possível, capture tipos de exceção específicos (como
[System.IO.FileNotFoundException]) antes do genéricocatch, para tratar diferentes cenários de forma adequada. - Considere o uso de
trappara scripts simples: embora menos flexível quetry/catch, otrappode ser útil em scripts curtos. - Teste seu tratamento de erros: simule falhas para garantir que o comportamento seja o esperado.
Com essas técnicas, você estará preparado para criar scripts PowerShell robustos e profissionais, capazes de lidar com falhas de forma elegante e informativa.
Exercícios
- Explique a diferença entre erros terminating e não-terminating. Dê um exemplo de cada e como forçar um erro não-terminating a ser tratado como terminating.
- Escreva um trecho de código que capture um erro e exiba no console: a mensagem da exceção, o nome do comando que gerou o erro e o número da linha onde ocorreu. Use
$PSItem. - Diferencie
throwsem argumentos dethrow "mensagem"dentro de um blococatch. Quando você deve usar um ou outro? - Crie uma função chamada
Write-Logque aceite parâmetrosMessageeLevel, e que anexe uma linha formatada a um arquivo de log. Em seguida, use-a em um blococatchpara registrar um erro simulado. - Descreva três formas de registrar erros em PowerShell e cite uma situação em que cada uma seria mais adequada.
catch. Exemplo de não-terminating: Get-Item -Path "C:\ArquivoInexistente.txt". Exemplo de terminating: throw "Erro fatal". Para forçar um erro não-terminating a ser tratado como terminating, use -ErrorAction Stop ou defina $ErrorActionPreference = 'Stop'.
try {
# Comando que gera erro
Get-Item -Path "C:\ArquivoInexistente.txt" -ErrorAction Stop
} catch {
Write-Host "Mensagem: $($PSItem.Exception.Message)"
Write-Host "Comando: $($PSItem.InvocationInfo.MyCommand)"
Write-Host "Linha: $($PSItem.InvocationInfo.ScriptLineNumber)"
}
throw sem argumentos relança a exceção original preservando o stack trace e a mensagem. throw "mensagem" cria uma nova exceção com uma nova mensagem, perdendo o contexto original. Use throw vazio quando quiser apenas registrar o erro e deixar que o chamador lide com ele; use throw "mensagem" quando quiser substituir a mensagem original ou gerar um erro específico.
function Write-Log {
param(
[string]$Message,
[ValidateSet("Info","Warning","Error")]
[string]$Level = "Info"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logLine = "[$timestamp] [$Level] $Message"
Add-Content -Path "C:\Temp\meuScript.log" -Value $logLine
}
try {
throw "Falha simulada"
} catch {
Write-Log -Message $PSItem.Exception.Message -Level "Error"
}