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 como Message, StackTrace e InnerException.
  • $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 Stop em cmdlets críticos) e siga-a.
  • Use try/catch/finally para liberar recursos: coloque a limpeza de recursos (como fechar conexões) no bloco finally.
  • 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érico catch, para tratar diferentes cenários de forma adequada.
  • Considere o uso de trap para scripts simples: embora menos flexível que try/catch, o trap pode 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

  1. 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.
  2. ✓ Resposta: Erros não-terminating são exibidos no console, mas o script continua. Erros terminating interrompem a execução e são capturados por 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'.
  3. 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.
  4. ✓ Resposta: Exemplo:
    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)"
    }
    
  5. Diferencie throw sem argumentos de throw "mensagem" dentro de um bloco catch. Quando você deve usar um ou outro?
  6. ✓ Resposta: 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.
  7. Crie uma função chamada Write-Log que aceite parâmetros Message e Level, e que anexe uma linha formatada a um arquivo de log. Em seguida, use-a em um bloco catch para registrar um erro simulado.
  8. ✓ Resposta: Exemplo:
    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"
    }
    
  9. Descreva três formas de registrar erros em PowerShell e cite uma situação em que cada uma seria mais adequada.
  10. ✓ Resposta: 1. Arquivo de texto simples: adequado para scripts simples que precisam de um histórico legível. 2. Log estruturado em JSON: ideal para integração com ferramentas de análise como Elastic Stack. 3. Windows Event Log: apropriado para scripts executados como serviços ou tarefas agendadas, onde o log centralizado do Windows é necessário.

Referências