Windows PowerShellO poder da filtragem

Don Jones

Na coluna do mês passado, discuti o poder e a flexibilidade do pipeline do Windows PowerShell, que permite a passagem de um conjunto de dados — ou, para sermos mais exatos, de um fluxo de objetos — de um cmdlet para outro, refinando esse conjunto até que ele seja exatamente o que você quer. Nessa discussão, fiz alusão ao fato de que os seus próprios scripts,

e não somente os cmdlets, podem aproveitar o pipeline. Neste mês, gostaria de discutir esse tópico em mais detalhes.

Uma das coisas que mais faço no Windows PowerShell™ é escrever scripts que agem em vários computadores remotos, normalmente por meio do WMI (Windows® Management Instrumentation). Assim como acontece com qualquer tarefa que lide com computadores remotos, sempre há a possibilidade de um ou mais desses computadores não estarem disponíveis no momento da execução do script. Portanto, preciso que meus scripts sejam capazes de lidar com esse fato.

É claro que existem várias formas que poderiam dar a um script a capacidade de manipular o tempo limite de uma conexão WMI mas, particularmente, não gosto dessa abordagem porque o período do tempo limite é muito longo — cerca de 30 segundos, por padrão. Isso poderia fazer com que meu script fosse executado de forma muito mais lenta caso eu tivesse de aguardar por diversos tempos limites. Em vez disso, quero que o meu script execute uma verificação rápida para determinar se o computador está online antes de tentar se conectar a ele.

O paradigma do Windows PowerShell

Em outras linguagens de scripts, como o VBScript, normalmente seria necessário lidar com um computador por vez. Ou seja, recuperar o nome de um computador — talvez de uma lista de nomes armazenada em um arquivo de texto — e fazer um ping no sistema para descobrir se ele está disponível. Se o sistema estiver disponível, será necessário fazer a conexão WMI e executar qualquer outro trabalho necessário. Essa é um abordagem de scripts comum. Na verdade, provavelmente eu escreveria todo o meu código em um loop e repetiria esse loop uma vez para cada computador a ser conectado.

O Windows PowerShell, no entanto, é mais adequado para as operações em lote, graças à sua base em objetos e à sua capacidade de trabalhar diretamente com grupos ou coleções de objetos. O paradigma do Windows PowerShell não é trabalhar com objetos únicos ou com dados, mas trabalhar com grupos inteiros, refinando-os pouco a pouco até a conclusão da tarefa. Por exemplo, em vez de recuperar um nome de computador por vez de minha lista, eu leria toda a coleção de nomes de uma só vez. E, em vez de fazer ping em cada computador em um loop, escreveria uma única rotina para aceitar uma coleção de nomes, faria ping neles e exibiria aqueles que pudessem ser contatados. A próxima etapa do meu processo seria fazer minha conexão WMI com os nomes restantes — aqueles que puderam ser alcançados por meio do ping.

O Windows PowerShell usa essa mesma abordagem para diversas tarefas. Por exemplo, para obter uma lista de serviços em execução, posso usar algo semelhante a este código:

Get-Service | Where-Object { $_.Status –eq "Running" }

A Figura 1 mostra como fica a saída em meu computador. Em vez de verificar cada serviço por vez, recuperei todos eles usando Get-Service, canalizei-os para Where-Object e filtrei aqueles que não estavam em execução. Isso é mais ou menos o que eu queria fazer com o meu script: obter uma lista de nomes de computador, filtrar aqueles que não puderam ser alcançados e passar a lista dos computadores nos quais farei o ping na próxima etapa.

Figura 1 Obtendo uma lista de computadores que podem ser alcançados com ping

Figura 1** Obtendo uma lista de computadores que podem ser alcançados com ping **(Clique na imagem para aumentar a exibição)

Funções de filtragem

Embora possa escrever o meu próprio cmdlet para realizar essa tarefa, não quero fazer isso. A criação de um cmdlet requer o Visual Basic® ou o C# e uma boa dose de experiência em desenvolvimento no Microsoft® .NET Framework. E, mais importante, requer mais envolvimento do que desejo investir nessa tarefa. Felizmente, o Windows PowerShell permite que eu escreva um tipo especial de função, chamado filtro, que é perfeitamente capaz de atuar no pipeline. Esta é a estrutura de tópicos básica de uma função de filtragem:

function <name> {
 BEGIN {
  #<code>
 }
 PROCESS {
  #<code>
 }
 END {
  #<code>
 }
}

Como você pode ver, essa função contém três blocos de scripts independentes, chamados BEGIN, PROCESS e END. Um função de filtragem — ou seja, uma função criada para funcionar no pipeline para filtrar objetos — pode ter qualquer combinação desses três blocos de scripts, dependendo do que você deseja fazer. Eles funcionam desta forma:

  • O bloco BEGIN é executado somente uma vez na primeira chamada à sua função. Ele pode ser usado para uma instalação, se necessário.
  • O bloco PROCESS é executado uma vez para cada objeto de pipeline passado para a função. A variável $_ representa o objeto de entrada do pipeline atual. A presença desse bloco é obrigatória em uma função de filtragem.
  • O bloco END é executado uma vez após o processamento de todos os objetos do pipeline. Ele pode ser usado para qualquer tarefa de finalização, se necessário.

Em meu exemplo, quero produzir uma função de filtragem que aceitará uma coleção de nomes como objetos de entrada e depois tentará fazer ping em cada um deles. Cada um que puder ser alcançado com êxito será enviado para o pipeline; os sistemas que não puderem ser alcançados pelo ping serão simplesmente ignorados. Uma vez que a funcionalidade do ping não requer qualquer instalação ou finalização especial, usarei somente o bloco de scripts PROCESS. O código da Figura 2 mostra o script completo.

Figure 2 Ping-Address e Restart-Computer

1 function Ping-Address {
2  PROCESS {
3   $ping = $false
4   $results = Get-WmiObject -query `
5   "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6   foreach ($result in $results) {
7    if ($results.StatusCode -eq 0) {
8     $ping = $true
9    }
10   }
11   if ($ping -eq $true) {
12    Write-Output $_
13   }
14  }
15 }
16
17 function Restart-Computer {
18  PROCESS {
19   $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20   $computer.Reboot()
21  }
22 }
23
24 Get-Content c:\computers.txt | Ping-Address | Restart-Computer

Observe que defini duas funções: Ping-Address e Restart-Computer. No Windows PowerShell, as funções devem ser definidas antes que possam ser chamadas. Como resultado, a primeira linha executável do meu script é a linha 24, que utiliza o cmdlet Get-Content para obter uma lista de nomes de computador de um arquivo (um nome de computador por linha). Essa lista — tecnicamente, uma coleção de objetos de cadeias de caracteres — é canalizada para a função Ping-Address, que filtra quaisquer computadores que não possam ser alcançados pelo ping. Os resultados são canalizados para Restart-Computer, que usa o WMI para reiniciar remotamente cada computador que foi alcançado pelo ping.

A função Ping-Address implementa um bloco de scripts PROCESS, o que significa que a função está esperando uma coleção de objetos de entrada. O bloco de scripts PROCESS lida com essa entrada automaticamente — não precisei definir quaisquer argumentos de entrada para armazená-la. O processo é iniciado na linha 3 com a configuração da variável $ping como $false, uma variável interna do Windows PowerShell que representa o valor Booleano False.

Em seguida, utilizo a classe WMI Win32_PingStatus local para fazer ping no computador especificado. Observe que a variável $_ da linha 5, que representa o objeto atual do pipeline, foi incorporada à cadeia de caracteres da consulta WMI. Quando uma cadeia de caracteres for exibida entre aspas, o Windows PowerShell sempre tentará substituir variáveis como $_ por seu conteúdo, para que você não mexa na concatenação de cadeias de caracteres. Estou usando esse recurso na linha 5.

A linha 6 é um loop que verifica os resultados do meu ping. Se qualquer um dos resultados for retornado com êxito (ou seja, com um StatusCode zero), $ping será definido como $true para indicar isso. Na linha 11, verifico se $ping foi definido como $true. Se foi, o objeto de saída original será enviado para o fluxo de saída padrão. O Windows PowerShell gerencia o fluxo de saída padrão automaticamente. Se essa função estiver no final do pipeline, o fluxo de saída será transformado em uma representação de texto. Se houver mais comandos no pipeline, os objetos do fluxo de saída — nesse caso, os objetos de cadeia de caracteres contendo os nomes de computador — serão passados para o próximo comando de pipeline.

De alguma forma, a função Restart-Computer é mais simples, mas também utiliza um bloco PROCESS para poder participar do pipeline com facilidade. Na linha 19, a função se conecta ao computador nomeado e recupera sua classe WMI Win32_OperatingSystem. A linha 20 executa o método Reboot da classe para reiniciar o computador remoto.

Novamente, a linha 24 é o local onde tudo é realmente executado. É claro que você deve ter cuidado ao executar esse script — ele foi criado para reiniciar todos os computadores nomeados em c:\computers.txt, o que, definitivamente, poderá ter resultados desastrosos se você não prestar atenção nos nomes contidos no arquivo de texto!

Próximas etapas

Esse script não é totalmente à prova de balas. Devo reconhecer também a possibilidade de acontecer um erro de WMI não relacionado à conectividade básica, como um firewall bloqueando as portas necessárias no computador remoto ou um erro de segurança de WMI. Posso lidar com esses problemas implementando um manipulador de interceptação — o que soa como um tópico perfeito para uma próxima coluna.

Além disso, este script está executando uma ação bastante séria — reiniciar um computador remoto. Qualquer script que tente uma ação potencialmente perigosa como essa deve implementar dois parâmetros comuns do Windows PowerShell, –Confirm e –WhatIf. A explicação sobre como fazer isso requer mais espaço do que tenho aqui, mas também seria um ótimo tópico para uma próxima coluna. Enquanto isso, dê uma olhada no blog da equipe do Windows PowerShell; o arquiteto Jeffrey Snover fala sobre esse assunto.

Mesmo sem se aprofundar sobre esses recursos, você pode ter uma idéia do que as funções de filtragem são capazes. Refinarei essa técnica nas próximas colunas, mostrando como as funções podem oferecer uma ótima maneira de modularizar código reutilizável, assim como aprimorar a funcionalidade de seus scripts em geral.

Don Jones é o guru de scripts da SAPIEN Technologies e co-autor do Windows PowerShell: TFM (SAPIEN Press, 2007). Entre em contato com ele em www.ScriptingAnswers.com.

© 2008 Microsoft Corporation e CMP Media, LLC. Todos os direitos reservados. A reprodução parcial ou completa sem autorização é proibida..