Windows PowerShellInterceptando erros

Don Jones

Conteúdo

Definindo uma interceptação
Pare!
Está tudo no escopo
Dividindo
Por que se preocupar?

Na edição anterior desta coluna, mostrei a você como usar uma ferramenta avançada de inventário usando o Windows PowerShell. A ferramenta que eu criei oferecia várias opções em relação à saída – graças aos recursos embutidos de shell e ao uso de função de objetos.

Um aspecto reconhecidamente fraco da função que criei é que ele não pode lidar normalmente com quaisquer erros que possam ocorrer, como problemas de permissão ou conectividade. Isso é o que quero abordar no artigo deste mês da coluna do Windows PowerShell – vou analisar os recursos de tratamento de erros oferecidos pelo Windows PowerShell.

Definindo uma interceptação

A palavra-chave Trap (interceptação) no Windows PowerShell define um manipulador de erros. Quando ocorre uma exceção no seu script, o shell verifica se foi definida uma interceptação – isso significa que a interceptação deverá aparecer no seu script antes que ocorram quaisquer exceções. Para esta demonstração, reunirei um script de teste que eu sei que irá gerar um problema de conectividade: utilizarei o Get-WmiObject para conexão com um nome de computador que sei que não existe na rede. Meu objetivo é que a interceptação de erro grave o nome errado de computador em um arquivo, fornecendo-me um arquivo de nomes de computador que não funcionaram. Também incluirei conexões com dois computadores que podem ser acessados (utilizei o localhost). É possível ver o script na Figura 1.

Figura 1 Adicionando a interceptação

trap {
  write-host "Error connecting to $computer" -fore red
  "$computer" | out-file c:\demo\errors.txt -append 
  continue
}

$computer = "localhost"
get-wmiobject win32_operatingsystem -comp $computer 

$computer = "server2"
get-wmiobject win32_operatingsystem -comp $computer 

$computer = "localhost"
get-wmiobject win32_operatingsystem -comp $computer

A saída deste script, mostrada na Figura 2, não é exatamente o que eu buscava. Observe que a mensagem "Erro ao conectar-se com…" não foi exibida. O arquivo Errors.txt também não foi criado. Em outras palavras, minha interceptação não foi executada. O que aconteceu?

fig02.gif

Figura 2 Esta não é a saída que eu queria!

Pare!

O segredo é compreender que uma mensagem de erro de shell normal não é o mesmo que uma exceção. (Há erros de não finalização e finalização. Os erros de finalização interrompem a execução do pipeline e resultam em uma exceção.) Somente exceções podem ser interceptadas. Quando ocorre um erro, o shell verifica sua variável embutida $ErrorActionPreference para ver o que ele deve fazer. O padrão da variável é ter o valor "Continue", que significa "exibir uma mensagem de erro e avançar". Alterar essa variável para "Stop" fará com que ela exiba uma mensagem de erro e produza uma exceção interceptável. No entanto, isso significa que qualquer erro no seu script também o fará.

Uma técnica melhor é ter apenas o cmdlet que você acha que pode causar um problema de uso do comportamento "Stop". Não é possível fazer isso usando o parâmetro –ErrorAction (ou –EA), um parâmetro comum compatível com todos os cmdlets. A versão revisada do script é mostrada na Figura 3. Ela funciona como esperado, produzindo a saída que você verá na Figura 4.

Figura 3 Usando o -ErrorAction

trap {
  write-host "Error connecting to $computer" -fore red
    "$computer" | out-file c:\demo\errors.txt -append 
  continue
}

$computer = "localhost"
get-wmiobject win32_operatingsystem -comp $computer -ea stop

$computer = "server2"
get-wmiobject win32_operatingsystem  -comp $computer -ea stop

$computer = "localhost"
get-wmiobject win32_operatingsystem  -comp $computer -ea stop

Figura 4 Eu obtenho resultados mais úteis usando o parâmetro –ErrorAction

O uso de Continue no final da interceptação instrui o shell para retomar a execução na linha de código após a que produziu a exceção. A outra opção é usar a palavra-chave Break (discutirei isso a seguir). Observe também que a variável $computer, que é definida no script, ainda é válida na interceptação. Isso ocorre porque a interceptação é um escopo filho do próprio script, o que significa que a interceptação pode ver todas as variáveis no script (mais informações também serão fornecidas a seguir).

Está tudo no escopo

Um aspecto especialmente delicado da interceptação de erros no Windows PowerShell é o uso de escopo. O próprio shell representa o escopo global, que contém tudo inserido dentro do shell. Quando você executa um script, ele obtém seu próprio escopo de script. Se você definir uma função, o interior da função será seu próprio escopo e assim por diante. Isso cria um tipo de hierarquia pai/filho.

Quando ocorre uma exceção, o shell verifica se há uma interceptação no escopo atual. Isso significa que uma exceção dentro da função verificará a interceptação nessa função. Se o shell detectar uma interceptação, ele a executará. Se a interceptação for encerrada com Continue, o shell retomará a execução na linha de código após a linha que causou a exceção – permanecendo no mesmo escopo. Veja um pouco de pseudocódigo para ajudar a ilustrar isso:

01  Trap {
02    # Log error to a file
03    Continue
04  }
05  Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
06  Get-Process

Se ocorrer uma exceção na linha 5, a interceptação na linha 1 será executada. A interceptação será encerrada com Continue, de modo que a execução seja retomada na linha 6.

Agora considere este exemplo ligeiramente diferente de escopo:

01  Trap {
02    # Log error to a file
03    Continue
04  }
05   
06  Function MyFunction {
07    Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
08    Get-Process
09  }
10   
11  MyFunction
12  Write-Host "Testing!"

Se ocorrer um erro na linha 7, o shell analisará se há interceptação no escopo de função. Se não houver, então o shell existe no escopo de função e verifica se há interceptação no escopo pai. Há uma interceptação e, portanto, ela é executada na linha 1. Nesse caso, Continue será retomado na linha de código no mesmo escopo após a exceção – ou seja, linha 12, não linha 8. Em outras palavras, o shell não irá inserir a função novamente depois que ele tiver saído.

Agora, compare este comportamento com este exemplo:

01  Function MyFunction {
02    Trap {
03      # Log error to a file
04      Continue
05    }
06    Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
07    Get-Process
08  }
09   
10  MyFunction
11  Write-Host "Testing!"

Nesse caso, o erro na linha 6 executará a interceptação na linha 2, permanecendo no escopo de função. A palavra-chave Continue permanecerá nesse escopo, retomando a execução na linha 7. Essa é a vantagem de incluir uma interceptação no escopo em que você esperaria que o erro ocorresse – você permanece no escopo e pode retomar a execução nele. Mas e se esse método não funcionar no seu cenário?

Cmdlet do mês: Compare-Object

Esta é uma ferramenta interessante para o gerenciamento de linhas de base de configuração. O Compare-Object, ou seu alias Diff, foi desenvolvido para comparar dois conjuntos de objetos um com o outro. Por padrão, ele compara cada propriedade de cada objeto e quaisquer diferenças são a saída do comando. Então, imagine que consiga que os serviços de um servidor sejam configurados exatamente como você o faria. Basta executar isto para criar uma linha de base:

Get-Service | Export-CliXML c:\baseline.xml

Praticamente qualquer objeto pode ser enviado por pipe para Export-CliXML, que transformará os objetos em um arquivo XML. Posteriormente, você pode executar o mesmo comando – como Get-Service – e comparar os resultados com esse XML salvo. Assim:

Compare-Object (Get-Service) (Import-CliXML 
  c:\baseline.xml) –property name

Adicionar o parâmetro –property força a comparação com apenas essa propriedade, em vez de com o objeto inteiro. Nesse caso, você obterá uma lista dos nomes de serviço que sejam diferentes da sua linha de base original, permitindo que você saiba se algum serviço foi adicionado ou removido desde a criação desse linha de base.

Dividindo

Anteriormente, eu mencionei a palavra-chave Break. A Figura 5 mostra um exemplo de como você pode colocar a palavra-chave Break em ação.

Figura 5 Usando a palavra-chave Break

01  Trap {
02    # Handle the error
03    Continue
04  }
05   
06  Function MyFunction {
07    Trap {
08      # Log error to a file
09      If ($condition) {
10        Continue
11      } Else {
12        Break
13      }
14    }
15    Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
16    Get-Process
17  }
18   
19  MyFunction
20  Write-Host "Testing!"

Veja a seguir uma visão geral da cadeia de execução. A linha 19 é executada primeiro, chamando a função na linha 6. A linha 15 é executada e gera uma exceção. Essa exceção é interceptada na linha 7 e então, na linha 9, a interceptação precisa tomar uma decisão. Supondo que $condition seja True, a interceptação continuará a execução na linha 16.

No entanto, se $condition for False, a interceptação executará Break. Isso sai do escopo atual e passa a exceção original para o escopo pai. Da perspectiva do shell, isso significa que a linha 19 produziu uma exceção, que é interceptada pela linha 1. A palavra-chave Continue força o shell a retomar na linha 20.

Na realidade, ambas as interceptações teriam um pouco mais de código para lidar com o erro, registrá-lo e assim por diante. Eu simplesmente omiti esse código funcional neste exemplo para tornar o fluxo real mais fácil de ser visualizado.

Por que se preocupar?

Quando você precisa implementar a interceptação de erros? Somente quando você prevê que um erro pode ocorrer e quando você quer algum comportamento que não seja a simples mensagem de erro – como registrar o arquivo em um arquivo ou exibir uma mensagem de erro mais útil.

Eu geralmente incluo o tratamento de erros em scripts mais complicados, a fim de ajudar a lidar com erros cuja ocorrência posso prever. Esses incluem, mas não se limitam, a erros como problemas de permissões ou conectividade ruim.

A interceptação de erros definitivamente requer um pouco mais de tempo e esforço para ser compreendida. No entanto, à medida que você progride em tarefas mais complicadas no Windows PowerShell, a interceptação de erros compensa o investimento para ajudá-lo a criar uma ferramenta mais profissional e refinada.

Don Jones é co-autor de Windows PowerShell: TFM e autor de dezenas de outros livros sobre TI. Entre em contato com ele por meio do seu blog em concentratedtech.com.