Tratamento de erros e logging em scripts PowerShell

1. Introdução ao tratamento de erros no PowerShell

O PowerShell possui dois tipos fundamentais de erros: terminantes (terminating) e não terminantes (non-terminating). Erros terminantes interrompem imediatamente a execução do script, enquanto erros não terminantes permitem que o script continue processando, apenas registrando o problema.

Por padrão, o PowerShell exibe erros não terminantes em vermelho no console e continua a execução. A variável automática $Error armazena um ArrayList com todos os erros ocorridos na sessão, sendo $Error[0] o erro mais recente. Gerenciar essa lista é essencial para scripts robustos.

2. Estruturas de tratamento de erros

O bloco try/catch/finally é a principal estrutura para capturar e tratar erros terminantes:

try {
    Get-Content "C:\arquivo_inexistente.txt"
}
catch [System.IO.FileNotFoundException] {
    Write-Host "Arquivo não encontrado: $_" -ForegroundColor Yellow
}
catch {
    Write-Host "Erro genérico: $_" -ForegroundColor Red
}
finally {
    Write-Host "Bloco finally sempre executado" -ForegroundColor Cyan
}

Para gerar erros personalizados, utilize throw:

function Validar-Idade {
    param([int]$idade)
    if ($idade -lt 0 -or $idade -gt 120) {
        throw [System.ArgumentOutOfRangeException]::new("idade", "Idade deve estar entre 0 e 120")
    }
    return $true
}

3. Controlando a ação diante de erros não terminantes

O parâmetro -ErrorAction permite definir o comportamento para comandos específicos:

Get-ChildItem "C:\pasta_inexistente" -ErrorAction Stop  # Transforma erro não terminante em terminante
Get-ChildItem "C:\outra_pasta" -ErrorAction SilentlyContinue  # Suprime a mensagem de erro
Get-ChildItem "C:\terceira_pasta" -ErrorAction Inquire  # Pergunta ao usuário como proceder

A variável $ErrorActionPreference define o comportamento global:

$ErrorActionPreference = "Stop"  # Todos os erros se tornam terminantes
$ErrorActionPreference = "Continue"  # Padrão: exibe erro e continua

Para capturar erros em variáveis específicas, use -ErrorVariable:

Get-ChildItem "C:\pasta_teste" -ErrorVariable errosCapturados -ErrorAction SilentlyContinue
if ($errosCapturados) {
    Write-Host "Ocorreram $($errosCapturados.Count) erros durante a operação"
}

4. Estratégias de logging em scripts PowerShell

Comandos nativos para logging básico:

Write-Host "Mensagem visível no console" -ForegroundColor Green
Write-Output "Mensagem enviada para pipeline"
Write-Verbose "Mensagem detalhada" -Verbose
Write-Debug "Mensagem de depuração" -Debug
Write-Warning "Aviso importante" 

Criação de função personalizada de logging:

function Write-Log {
    param(
        [string]$Mensagem,
        [ValidateSet("INFO", "WARN", "ERROR")]
        [string]$Nivel = "INFO",
        [string]$ArquivoLog = ".\script.log"
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $linhaLog = "[$timestamp] [$Nivel] $Mensagem"

    Add-Content -Path $ArquivoLog -Value $linhaLog

    switch ($Nivel) {
        "ERROR" { Write-Host $linhaLog -ForegroundColor Red }
        "WARN"  { Write-Host $linhaLog -ForegroundColor Yellow }
        default { Write-Host $linhaLog -ForegroundColor Gray }
    }
}

Write-Log "Script iniciado" -Nivel INFO
Write-Log "Arquivo não encontrado" -Nivel WARN
Write-Log "Falha crítica de conexão" -Nivel ERROR

5. Técnicas avançadas de logging

Para registrar toda a saída de uma sessão, use Start-Transcript:

Start-Transcript -Path "C:\logs\sessao_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
# Comandos do script aqui
Stop-Transcript

Integração com o Log de Eventos do Windows:

Write-EventLog -LogName Application -Source "MeuScript" -EntryType Error `
    -EventId 1001 -Message "Erro ao processar arquivo: $erro"

Módulo reutilizável de logging:

function New-Logger {
    param([string]$LogPath, [string]$LogLevel = "INFO")

    return [PSCustomObject]@{
        Path = $LogPath
        Level = $LogLevel
        Log = {
            param($message, $level)
            if ($level -in $this.Level, "ERROR") {
                $entry = "[$(Get-Date)] [$level] $message"
                Add-Content -Path $this.Path -Value $entry
            }
        }.GetNewClosure()
    }
}

$logger = New-Logger -LogPath "C:\logs\app.log" -LogLevel "WARN"
& $logger.Log "Mensagem de teste" "INFO"  # Não registra (nível abaixo do configurado)
& $logger.Log "Aviso importante" "WARN"   # Registra

6. Boas práticas de tratamento de erros e logging

Centralize o tratamento de erros em funções modulares:

function Invoke-ComandoSeguro {
    param([scriptblock]$Comando)

    try {
        & $Comando
    }
    catch {
        Write-Log -Mensagem "Falha ao executar comando: $_" -Nivel ERROR
        throw  # Re-lança o erro para tratamento superior
    }
}

Use blocos trap para captura global de erros:

trap {
    Write-Log -Mensagem "Erro global capturado: $_" -Nivel ERROR
    continue  # ou break para interromper
}

Para logging estruturado, utilize formato JSON:

$logEntry = [PSCustomObject]@{
    Timestamp = Get-Date -Format "o"
    Level = "ERROR"
    Message = "Falha na conexão com banco de dados"
    Hostname = $env:COMPUTERNAME
    User = $env:USERNAME
} | ConvertTo-Json

Add-Content -Path "C:\logs\estruturado.json" -Value $logEntry

7. Exemplo prático: script robusto com logging e tratamento de erros

# Configuração inicial
$script:LogPath = "C:\logs\meuscript_$(Get-Date -Format 'yyyyMMdd').log"
$script:MaxLogSize = 10MB
$ErrorActionPreference = "Stop"

function Initialize-Log {
    if (Test-Path $script:LogPath) {
        if ((Get-Item $script:LogPath).Length -gt $script:MaxLogSize) {
            Rename-Item $script:LogPath "$script:LogPath.old" -Force
        }
    }
    Write-Log "=== Início da execução ===" -Nivel INFO
}

function Write-Log {
    param($Mensagem, $Nivel = "INFO")
    $entry = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Nivel] $Mensagem"
    Add-Content -Path $script:LogPath -Value $entry
    if ($Nivel -eq "ERROR") { Write-Host $entry -ForegroundColor Red }
}

function Processar-Arquivo {
    param([string]$Arquivo)

    try {
        Write-Log "Processando arquivo: $Arquivo" -Nivel INFO

        if (-not (Test-Path $Arquivo)) {
            throw [System.IO.FileNotFoundException]::new("Arquivo não encontrado: $Arquivo")
        }

        $conteudo = Get-Content $Arquivo -ErrorAction Stop
        Write-Log "Arquivo lido com sucesso. Linhas: $($conteudo.Count)" -Nivel INFO

        # Processamento simulado
        Start-Sleep -Seconds 2
        Write-Log "Processamento concluído para: $Arquivo" -Nivel INFO
    }
    catch [System.IO.FileNotFoundException] {
        Write-Log "Erro de arquivo: $_" -Nivel ERROR
        throw  # Re-lança para tratamento superior
    }
    catch [System.UnauthorizedAccessException] {
        Write-Log "Permissão negada: $_" -Nivel ERROR
    }
    catch {
        Write-Log "Erro inesperado: $_" -Nivel ERROR
    }
}

function Main {
    Initialize-Log

    $arquivos = @(
        "C:\dados\relatorio.txt",
        "C:\dados\inventario.csv",
        "C:\dados\backup.log"
    )

    foreach ($arquivo in $arquivos) {
        try {
            Processar-Arquivo $arquivo
        }
        catch {
            Write-Log "Falha ao processar $arquivo. Continuando com próximo..." -Nivel WARN
        }
    }

    Write-Log "=== Fim da execução ===" -Nivel INFO
}

# Execução principal
Main

Este script demonstra:
- Rotação automática de logs baseada em tamanho
- Tratamento específico para diferentes tipos de erro
- Captura de exceções com logging estruturado
- Continuidade mesmo após falhas em itens individuais

Referências