No desenvolvimento de scripts, os erros são inevitáveis. Seja por entrada inválida do usuário, falha de rede ou arquivo inexistente, um bom script deve lidar com essas situações de forma elegante, sem interromper abruptamente a execução. No PowerShell, o tratamento de erros é uma habilidade essencial para criar automações confiáveis e profissionais. Nesta aula, vamos explorar as principais construções e conceitos que permitem capturar, analisar e responder a erros, tornando seus scripts mais robustos e fáceis de depurar.

Vamos começar entendendo os dois tipos de erros no PowerShell — terminantes e não terminantes — pois essa distinção é fundamental para escolher a estratégia de tratamento adequada. Em seguida, mergulharemos na estrutura try/catch/finally, na variável automática $Error e na geração de erros personalizados com throw. Ao final, você terá um arsenal completo para lidar com falhas de forma controlada.

try/catch/finally

O bloco try/catch/finally é a espinha dorsal do tratamento de erros no PowerShell. Ele permite que você execute um bloco de código (try) e, caso ocorra um erro, capture-o no bloco catch para tratá-lo. O bloco finally é opcional e sempre executa, independentemente de ocorrer erro ou não, sendo útil para limpeza de recursos (como fechar conexões ou arquivos).

A sintaxe básica é:

try {
    # código que pode gerar erro
} catch {
    # código para tratar o erro
} finally {
    # código que sempre executa
}

No PowerShell, o bloco catch pode ser específico para um tipo de erro, permitindo tratamentos diferenciados. Por exemplo:

try {
    Get-Content -Path "C:\arquivo_inexistente.txt" -ErrorAction Stop
} catch [System.IO.FileNotFoundException] {
    Write-Host "Arquivo não encontrado."
} catch {
    Write-Host "Erro genérico: $_"
}

No exemplo acima, se o arquivo não for encontrado, o primeiro catch captura a exceção específica. Caso seja outro tipo de erro, o segundo catch (genérico) assume. A variável $_ dentro do catch contém a representação do erro atual.

O bloco finally é útil para ações que devem ocorrer sempre, como liberar recursos. Exemplo:

$stream = [System.IO.StreamReader]::new("C:\dados.txt")
try {
    $content = $stream.ReadToEnd()
    Write-Host $content
} catch {
    Write-Host "Erro ao ler arquivo: $_"
} finally {
    $stream.Dispose()
}

Observe que, mesmo que ocorra um erro na leitura, o finally garante que o Dispose() seja chamado, evitando vazamento de recursos.

Erros terminating vs non-terminating

No PowerShell, é crucial distinguir entre erros terminantes (terminating errors) e não terminantes (non-terminating errors). Um erro terminante interrompe a execução do comando ou script, enquanto um erro não terminante apenas registra o erro e continua com o próximo item, se houver.

Cmdlets como Get-Content, Get-Process e Remove-Item geram erros não terminantes por padrão quando encontram um problema (por exemplo, arquivo não encontrado). Isso significa que, se você passar uma lista de caminhos e um deles não existir, o cmdlet continua processando os demais. Para capturar esses erros com try/catch, é necessário usar o parâmetro comum -ErrorAction Stop, que converte o erro não terminante em terminante.

Exemplo de erro não terminante:

Get-Content -Path "C:\inexistente.txt"  # sem -ErrorAction Stop
Write-Host "Continuando..."

O comando acima exibirá uma mensagem de erro (em vermelho) mas continuará com o Write-Host. Para capturar com try/catch, você precisa forçar o erro a ser terminante:

try {
    Get-Content -Path "C:\inexistente.txt" -ErrorAction Stop
} catch {
    Write-Host "Erro capturado: $_"
}
Write-Host "Continuando..."

Nesse caso, o erro é capturado e o script continua.

Por outro lado, erros terminantes são gerados por exceções de runtime (como acesso a propriedades de um objeto nulo) ou pelo uso explícito da palavra-chave throw. Eles são capturados diretamente pelo bloco catch sem a necessidade de -ErrorAction.

Entender essa diferença é fundamental para escrever scripts que se comportem de forma previsível. Você pode controlar o comportamento globalmente com a variável $ErrorActionPreference, que define a ação padrão para erros não terminantes. Por exemplo:

$ErrorActionPreference = "Stop"  # a partir daqui, todos os erros são terminantes

Essa configuração é útil em scripts longos, mas deve ser usada com cautela, pois pode interromper a execução em pontos inesperados.

$Error

A variável automática $Error é uma lista que contém todos os erros ocorridos na sessão atual, sendo o índice 0 o erro mais recente. Ela é extremamente útil para depuração e para inspecionar detalhes do erro após a ocorrência.

Cada item em $Error é um objeto ErrorRecord que contém propriedades como Exception, CategoryInfo, FullyQualifiedErrorId, InvocationInfo, entre outras. Exemplo:

try {
    Get-Content -Path "C:\nao_existe.txt" -ErrorAction Stop
} catch {
    Write-Host "Mensagem: $($_.Exception.Message)"
    Write-Host "ID: $($_.FullyQualifiedErrorId)"
    Write-Host "Linha: $($_.InvocationInfo.ScriptLineNumber)"
}

Você também pode acessar o erro mais recente fora do bloco catch usando $Error[0]. No entanto, lembre-se de que $Error acumula todos os erros da sessão, então é uma boa prática limpá-la periodicamente com $Error.Clear(), especialmente em scripts longos, para evitar consumo de memória e confusão ao inspecionar erros antigos.

Para exibir todos os erros, você pode iterar sobre a coleção:

foreach ($err in $Error) {
    Write-Host $err.Exception.Message
}

Outra propriedade útil é CategoryInfo, que fornece uma categoria padronizada do erro (por exemplo, ObjectNotFound, PermissionDenied). Isso pode ser usado para decisões lógicas no tratamento.

throw

O comando throw é usado para gerar um erro terminante deliberadamente. Ele pode ser usado para validar parâmetros, sinalizar condições inesperadas ou criar exceções personalizadas. Quando throw é executado, ele cria um ErrorRecord que pode ser capturado por um bloco catch no mesmo script ou em um chamador.

Sintaxe básica:

throw "Mensagem do erro"

Ou você pode lançar uma exceção mais estruturada:

throw [System.ArgumentException]::new("Parâmetro inválido: $nome")

Exemplo de uso em uma função de validação:

function Test-Idade {
    param([int]$Idade)
    if ($Idade -lt 0 -or $Idade -gt 120) {
        throw "Idade inválida: $Idade"
    }
    Write-Host "Idade válida: $Idade"
}

try {
    Test-Idade -Idade 150
} catch {
    Write-Host "Erro: $_"
}

Note que, ao usar throw dentro de uma função, o erro pode ser capturado pelo chamador, permitindo que a função indique falhas de forma clara.

Você também pode usar throw para relançar um erro capturado, preservando a pilha de chamadas original:

try {
    # algum código
} catch {
    # registra o erro
    throw  # relança o erro atual
}

Essa técnica é útil quando você quer logar o erro e ainda deixar que o chamador o trate.

Boas práticas e observações finais

Ao trabalhar com tratamento de erros, considere as seguintes boas práticas:

  • Use -ErrorAction Stop em cmdlets que você sabe que podem falhar e deseja capturar com try/catch.
  • Seja específico nos catch: capture tipos de exceção específicos para tratar diferentes cenários de forma adequada.
  • Não ignore erros silenciosamente: sempre registre ou trate de alguma forma, mesmo que seja apenas exibir uma mensagem.
  • Use finally para limpeza: libere recursos como arquivos, conexões ou objetos COM.
  • Evite usar $ErrorActionPreference = "Stop" globalmente em scripts grandes, pois isso pode causar interrupções inesperadas em cmdlets que você não esperava que fossem terminantes. Prefira configurar localmente com -ErrorAction.
  • Documente os erros: ao capturar um erro, inclua informações contextuais (como valores de variáveis) para facilitar a depuração.

O tratamento de erros é uma parte crucial do desenvolvimento de scripts robustos. Com as técnicas apresentadas, você poderá criar automações que lidam com falhas de forma controlada, melhorando a experiência do usuário e a confiabilidade do sistema.

Referências

Exercícios

  1. Escreva um script que tenta ler um arquivo cujo caminho é passado como parâmetro. Use try/catch para capturar o erro caso o arquivo não exista (use -ErrorAction Stop) e exiba uma mensagem amigável. Teste com um caminho inválido.

    ✓ Resposta:
    param([string]$Path)
    try {
        Get-Content -Path $Path -ErrorAction Stop
    } catch [System.IO.FileNotFoundException] {
        Write-Host "O arquivo não foi encontrado: $Path"
    } catch {
        Write-Host "Erro inesperado: $_"
    }
  2. Diferencie erros terminantes e não terminantes no PowerShell. Dê um exemplo de cada e explique como o try/catch pode capturar apenas erros terminantes.

    ✓ Resposta: Erros não terminantes não interrompem a execução, como um arquivo não encontrado em Get-Content sem -ErrorAction Stop. Erros terminantes interrompem, como uma exceção de divisão por zero. Para capturar um erro não terminante no try/catch, é preciso convertê-lo em terminante usando -ErrorAction Stop ou definindo $ErrorActionPreference = "Stop". Exemplo:
    try { Get-Content -Path "C:\nao_existe.txt" -ErrorAction Stop } catch { "Capturado" }
  3. Explique a função da variável automática $Error e como você pode usá-la para inspecionar o erro mais recente. Mostre um exemplo de acesso às propriedades Exception.Message e InvocationInfo.ScriptLineNumber.

    ✓ Resposta: $Error é uma lista de todos os erros da sessão. O erro mais recente é $Error[0]. Exemplo:
    try { Get-Content -Path "C:\nao_existe.txt" -ErrorAction Stop } catch { }
    Write-Host "Mensagem: $($Error[0].Exception.Message)"
    Write-Host "Linha: $($Error[0].InvocationInfo.ScriptLineNumber)"
  4. Crie uma função chamada Divide-Numbers que recebe dois números e retorna a divisão. Se o divisor for zero, a função deve lançar um erro terminante com a mensagem "Divisão por zero não permitida". Use throw e capture o erro no chamador.

    ✓ Resposta:
    function Divide-Numbers {
        param([double]$a, [double]$b)
        if ($b -eq 0) {
            throw "Divisão por zero não permitida"
        }
        return $a / $b
    }
    
    try {
        Divide-Numbers -a 10 -b 0
    } catch {
        Write-Host "Erro: $_"
    }
  5. Escreva um exemplo que usa o bloco finally para garantir que um recurso seja liberado, mesmo que ocorra um erro. Simule a abertura de um arquivo com [System.IO.StreamReader] e force um erro durante a leitura.

    ✓ Resposta:
    $reader = [System.IO.StreamReader]::new("C:\dados.txt")
    try {
        # Força um erro durante a leitura, por exemplo, lendo além do fim
        $null = $reader.ReadToEnd()
        throw "Erro simulado"
    } catch {
        Write-Host "Erro: $_"
    } finally {
        $reader.Dispose()
        Write-Host "Recurso liberado."
    }