Windows PowerShellCriando sua própria ferramenta de inventário de software

Don Jones

Sumário

Encontrando as informações
Criando um protótipo
Lendo nomes de computador
Modularizando
Funções de pipeline

Nesta edição da coluna Windows Power-Shell, demonstrarei um uso bastante prático: criarei uma ferramenta que inventaria o número de compilação do sistema operacional (uma das melhores formas de determinar a versão do sistema operacional) e o número de versão do service pack em uma lista de computadores. Mas não darei a solução assim, facilmente. Percorrerei com você o processo que uso para desenvolver um script desse tipo.

Embora essa seja obviamente uma ferramenta importante, acho que o processo por meio do qual a desenvolvo é ainda mais importante. Depois de compreender e ser capaz de reutilizar esse processo de desenvolvimento em tarefas próprias, você estará na direção certa para resolver praticamente qualquer problema administrativo usando o Windows PowerShell. (Como diz aquele velho ditado, se você ensina alguém a pescar…) Comecemos.

Encontrando as informações

A primeira – e normalmente a mais difícil – tarefa é encontrar onde é possível localizar, de fato, os números de versão do sistema operacional e do service pack. Você pode ter a tentação de vasculhar o Registro. Às vezes, ele é a resposta para informações como essa, mas não costuma ser a minha última opção porque o Registro pode ser um pouco confuso para se trabalhar.

As palavras "recuperar" e "informações" ativam imediatamente uma resposta possível na minha cabeça: Instrumentação de Gerenciamento do Windows (WMI). Outra palavra que me lembra WMI é "remoto", porque na versão 1 do Windows PowerShell, o WMI é praticamente a única opção para realizar qualquer tipo de operação de inventário ou de gerenciamento de informações remotas.

Infelizmente, existem dezenas de milhares de classes WMI na maioria dos sistemas Windows, e isso pode dificultar a localização de uma que contenha as informações de que você precisa. Normalmente começo com um mecanismo de pesquisa da Web e uso uma frase como, por exemplo, "número de versão do service pack do wmi". Observe que, para gerar resultados mais específicos, você precisará de uma frase bem longa como essa.

Talvez você precise até mesmo testar alguns termos de pesquisa variados, mas não ceda à tentação de abreviar ("versão sp wmi" não fornecerá resultados muito úteis). Considere termos alternativos. Por exemplo, o que você pode chamar de "patches" outros chamam de "hotfixes" e outros ainda os chamam de "Quick Fix Engineering" ou de "patches qfe". Testar todos esses termos talvez seja necessário para localizar os resultados certos.

Caso você esteja pesquisando algo relacionado ao hardware do computador ou ao sistema operacional Windows básico, adicionar "Win32" à frase de pesquisa pode ajudar, porque a maioria das classes WMI relevantes começam com um prefixo "Win32_". Adicionar "Win32" à minha pesquisa atual definitivamente produz os melhores resultados. Vejo vários resultados com "Win32_OperatingSystem" nos títulos – ou seja, o nome de uma classe WMI.

Agora, o truque importante é evitar clicar nos resultados da pesquisa. (Isso leva à loucura.) Primeiro quero encontrar as páginas de documentação reais da classe; depois, recomeço com uma nova pesquisa usando apenas o nome da classe descoberta. Isso normalmente produzirá um link para o site msdn.microsoft.com nos primeiros resultados, devendo levá-lo diretamente para a página de documentação da classe.

A Figura 1 mostra uma parte dessa página. Rolei até uma tabela especialmente importante nessa página, que lista as versões do sistema operacional com as quais a classe trabalhará. Não posso lhe dizer quantas vezes trabalhei em algo, tentando fazê-lo funcionar, apenas para descobrir que o que estava tentando fazer, de fato, não existia na versão do Windows que estava usando. Então, agora tenho o hábito de verificar primeiro essa tabela.

fig01.gif

Figura 1 Procurando informações sobre a classe WMI (Clique na imagem para ampliá-la)

Rolando a página para cima um pouquinho, vejo que há duas propriedades nas quais tenho interesse: BuildNumber e ServicePackMajorVersion. Na verdade, ServicePackMinorVersion também pode ser útil, ainda que eu nunca tenha visto um número de service pack 2.1 da Microsoft. No entanto, ela provavelmente só mereça ser verificada caso seja completa.

Cmdlet do mês: Export-CliXML e Import-CliXML

O Windows PowerShell tem a possibilidade de armazenar um instantâneo de objetos em um formato XML especial, permitindo que as informações nesses objetos sejam mantidas em um arquivo e carregadas na memória para serem examinadas posteriormente. Basta associar objetos a Export-CliXML para salvá-los em um arquivo:

Get-Process | Export-CliXML c:\processes.xml

Ao importar esses objetos para o shell posteriormente, você pode examiná-los assim como acontece com qualquer outro objeto. Eles não são objetos "reais", mas possibilitam mais opções de relatórios. Suponhamos que você tenha programado um script que exporta todos os processos em um determinado servidor às 3h00, enquanto uma tarefa de manutenção está em execução. Ao chegar ao trabalho, você pode carregar esses processos e observá-los, talvez classificando-os de acordo com o consumo de memória virtual da seguinte forma:

Import-CliXML c:\processes.xml | Sort VM -descending

Criando um protótipo

Não quero avançar sem verificar se essas propriedades fazem o que espero. O Windows PowerShell torna isso muito fácil. Começo verificando estas informações no meu computador local:

Get-WmiObject Win32_OperatingSystem | Select
BuildNumber,ServicePackMajorVersion,ServicePack­MinorVersion

Tudo bem, a versão secundária era zero, o que eu esperava, logo, deixarei isso para lá. As demais informações também são o que espero – um número de versão igual a 6001 para o meu computador com Windows Server 2008 e uma versão de service pack igual a 1.

Agora farei um teste semelhante em um computador remoto – um em que eu saiba ser um administrador:

Get-WmiObject Win32_OperatingSystem –computer Server2 | 
Select BuildNumber,ServicePackMajorVersion

Caso esse teste não funcione, preciso parar e imaginar o porquê antes de continuar. É provável que todos os problemas estejam relacionados à conectividade, a firewalls ou permissões, que estão fora do escopo do próprio Windows PowerShell. Depois que tudo estiver funcionando corretamente, posso passar à próxima etapa do meu problema: como obter vários nomes de computadores de um arquivo.

Lendo nomes de computador

Pressupondo que a minha lista dos nomes de computadores esteja em um arquivo de texto e haja um nome de computador listado em cada uma das linhas, a forma mais simples de fazer isso é usando o cmdlet Get-Content. (Não se preocupe caso os nomes de computadores não estejam em um arquivo de texto ou listado um por linha – abordarei as técnicas sobre como lidar com circunstâncias diferentes em uma próxima coluna.)

Cada nome é retornado como um objeto de cadeia de caracteres independente. E um recurso prático do cmdlet Get-WmiObject é que seu parâmetro –computerName aceita uma coleção de nomes de computador, logo, isso deve fazer o truque:

$names = Get-Content c:\computernames.txt
Get-WmiObject Win32_OperatingSystem –comp $names | Select
BuildNumber,ServicePackMajorVersion

Agora o problema é que a minha saída é uma lista de números, sem nenhuma indicação de qual número acompanha que computador. Felizmente, a classe Win32_OperatingSystem acaba tendo outra propriedade, CSName, que inclui o nome do computador. Dessa forma, posso adicionar essa propriedade à minha saída e obter um bom inventário:

$names = Get-Content c:\computernames.txt
Get-WmiObject Win32_OperatingSystem –comp $names | Select
CSName,BuildNumber,ServicePackMajorVersion

Modularizando

Como tudo isso talvez seja muito para um técnico menos experiente usar, a etapa final é modularizar isso aqui em uma função. Uma forma é escrever uma função que aceite um nome de arquivo e deixe-a fazer todo o trabalho:

Function Get-SPInventory ([string]$filename) {
  $names = Get-Content $filename
  Get-WmiObject Win32_OperatingSystem –comp  $names | Select   CSName,BuildNumber,ServicePackMajorVersion
}

Como se pode ver, apenas encapsulei o meu código funcional em uma função chamada Get-SPInventory. Como o defini com um parâmetro de entrada chamado $filename, ele só pode ser chamado de algo assim:

Get-SPInventory c:\computernames.txt

Os resultados ainda podem ser inseridos em um arquivo CSV, convertidos em HTML ou formatados de maneira diferente usando comandos normais do Windows PowerShell como:

Get-SPInventory c:\computernames.txt | 
Export-CSV SPInventory.csv

Mas isso ainda não está perfeito. E se também quisesse inventariar algumas informações que não estavam incluídas na classe Win32_OperatingSystem como, por exemplo, o número de série do BIOS de cada computador? Isso seria útil porque muitos bancos de dados de gerenciamento de configuração (CMDBs) usam o número de série do BIOS como identificador exclusivo dos computadores.

Talvez também queira que a saída da função seja um pouco mais flexível, o que pode me permitir classificar ou filtrar os resultados com mais facilidade. Em seguida, por exemplo, poderia optar por incluir apenas computadores com Windows Server 2003 com uma versão do service pack anterior na saída final para criar uma lista de computadores que preciso atualizar.

Funções de pipeline

Agora quero reescrever a minha função para que ela seja melhor no pipeline do Windows PowerShell. Especificamente, quero que a função aceite diretamente nomes de computador do pipeline – ou seja, posso decidir onde quero obter os nomes de computador sempre que uso a função. Os nomes de computador podem estar em um arquivo ou no Active Directory, e quero que a função apresente um bom desempenho de qualquer forma. Logo, eis a função reescrita para o pipeline:

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_OperatingSystem       –comp $_ | Select     CSName,BuildNumber,ServicePackMajorVersion
    Write-Output $wmi
  }
}

Esse tipo especial de função usa um bloco de script PROCESS, que será executado uma vez para cada objeto de pipeline inserido na função. (Os leitores atentos se lembrarão que abordei o bloco de script PROCESS na edição de julho de 2008 desta coluna, disponível em technet.microsoft.com/magazine/cc644947.aspx.) A variável especial $_ será preenchida automaticamente com a entrada do pipeline – desde que eu insira os nomes de computador, ela funcionará bem. A nova função é usada da seguinte forma:

Get-Content c:\computernames.txt | Get-SPInventory

Como se pode ver, isso proporciona mais flexibilidade para obter nomes de computador de origens diferentes. Acabei de substituir a parte Get-Content do comando por qualquer outro comando necessário à recuperação dos nomes do computador. Observe que também a configurei para que seja possível criar uma saída mais eficiente consultando classes WMI diferentes (ou quaisquer outras fontes de dados) antes de criar a minha saída.

Mas não se trata do fim da discussão. No mês que vem, irei expandir essa função para incluir o número de série do BIOS na saída.

Don Jones é co-fundador da Concentrated Technology e autor de vários livros sobre TI. Leia suas dicas semanais sobre o Windows PowerShell ou entre em contato com ele em www.ConcentratedTech.com.