Windows PowerShellCriando uma ferramenta de inventário melhor

Don Jones

Sumário

Os objetos são sempre os mesmos
Saída flexível
Entrada flexível
E quanto aos erros?
Tornando isso realmente útil

Na edição anterior desta coluna, criei uma função que poderia recuperar informações de inventário de service pack de vários computadores remotos. Embora seja uma ferramenta muito prática que pode ser bastante útil, eu também estava preocupado com o processo que usei para desenvolvê-la. Nesta edição, quero demonstrar o processo que usei, e assim você estará mais bem preparado para criar suas próprias funções.

A função que criei na edição anterior é parecida com esta:

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

E aqui está como você pode usá-la:

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

Ela funcionará bem se o arquivo de texto tiver um nome de computador por linha.

Um ponto fraco em específico desta função é que ela se limita a retornar dados de uma única classe WMI (Instrumentação de Gerenciamento do Windows). E se eu também quiser que ela retorne o número de série do BIOS?

Os objetos são sempre os mesmos

O problema é que a minha função está recuperando a classe Win32_OperatingSystem do WMI e simplesmente gerando esse objeto. A classe Win32_OperatingSystem, como todos os objetos, é fixa nos dados que contém — ela não inclui o número de série do BIOS e nunca poderá fazê-lo. Como estou apenas gerando objetos Win32_OperatingSystem, minha saída nunca poderá incluir um número de série.

Não importa quanto tempo você investigue sobre o WMI, não encontrará uma só classe de objeto que inclua números de série de BIOS e informações de service pack. Por isso, minha função não pode simplesmente gerar uma classe WMI. Em vez disso, tenho de gerar um objeto personalizado que eu possa criar em um piscar de olhos — um objeto que contenha todos os dados de que preciso.

Para criar um objeto novo vazio sem nenhuma propriedade, basta executar o seguinte:

$obj = New-Object PSObject

O novo objeto é armazenado na variável $obj, e posso adicionar os dados que eu quiser. Depois que eu adicionar todos os meus dados, ele então se tornará a saída da minha função.

Na minha função, recuperei as informações de Win32_OperatingSystem e as armazenei na variável $wmi. Usar essas informações é tão simples quanto fazer referência às propriedades de $wmi. Por exemplo, para obter a propriedade BuildNumber, eu usaria isto:

$wmi.BuildNumber

Para adicionar uma propriedade ao meu objeto personalizado, tenho de canalizar o objeto (que está na variável $obj) para Add-Member. Digo a Add-Member que tipo de propriedade quero adicionar (sempre uma NoteProperty), o nome da propriedade e o valor que desejo que ela tenha, da seguinte maneira:

$obj | Add-Member NoteProperty BuildNumber 
($wmi.BuildNumber)

Posso fazer isso para obter o nome do sistema do computador e a versão do service pack:

$obj | Add-Member NotePropertyCSName 
  ($wmi.CSName)
$obj | Add-Member NotePropertySPVersion 
  ($wmi.ServicePackMajorVersion)

O resultado disso tudo é uma nova função (consulte a Figura 1). Observe que alterei a linha Write-Output para gerar o objeto personalizado em vez do objeto $wmi original.

Figura 1 Um objeto personalizado

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_       OperatingSystem –comp $_ | 
       Select CSName,BuildNumber,ServicePack         MajorVersion
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty 
      BuildNumber ($wmi.BuildNumber)
    $obj | Add-Member NoteProperty CSName      ($wmi.CSName) 
    $obj | Add-Member NoteProperty SPVersion 
      ($wmi.ServicePackMajorVersion)
    Write-Output $obj
  }
}

Agora preciso obter aquele número de série do BIOS. Algumas pesquisas revelam a classe Win32_BIOS, que tem uma propriedade SerialNumber (a Figura 2 mostra uma parte da página de documentação online). Basta consultar a classe Win32_BIOS e adicionar a propriedade SerialNumber ao meu objeto personalizado, e pronto! Você pode ver a função revisada na Figura 3.

fig02.gif

Figura 2 Página de documentação da classe Win32_BIOS (Clique na imagem para ampliá-la)

Figura 3 Com a propriedade SerialNumber

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_      OperatingSystem –comp $_ |    
      Select CSName,BuildNumber,ServicePack        MajorVersion
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty 
      BuildNumber ($wmi.BuildNumber)
    $obj | Add-Member NoteProperty CSName      ($wmi.CSName)
    $obj | Add-Member NoteProperty SPVersion 
      ($wmi.ServicePackMajorVersion)

    $wmi = Get-WmiObject Win32_BIOS –comp $_ | 
      Select SerialNumber
    $obj | Add-Member NoteProperty BIOSSerial 
      ($wmi.SerialNumber)
    Write-Output $obj
  }
}

Saída flexível

Utilizando um objeto personalizado para a saída, criei uma variedade de usos possíveis desta função. Por exemplo, para incluir somente computadores com o Windows Server 2008, posso usar isto:

Gc c:\computernames.txt | Get-SPInventory | 
Where { $_.BuildNumber –eq 6001 }

Ou para criar um arquivo HTML que liste todos os computadores com o Windows Vista que não têm o Service Pack 1 instalado, posso usar isto:

Gc c:\computernames.txt | Get-SPInventory | 
Where { $_.BuildNumber –eq 6000 } | 
ConvertTo-HTML | Out-File c:\VistaInventory.html

As possibilidades são infinitas: XML, CSV, tabelas, listas, HTML, classificado, filtrado, agrupado e assim por diante. Os cmdlets internos do Windows PowerShell foram projetados para funcionar com objetos. Ao criar objetos para a saída, você pode tirar proveito de tudo o que o Windows PowerShell é capaz de fazer — sem trabalho extra!

Entrada flexível

Infelizmente, minha função não é tão flexível em termos de entrada. Em parte, isso é uma limitação da maneira como as funções são criadas na versão 1 do Windows PowerShell — a versão 2 introduzirá cmdlets de script, que oferecem muito mais flexibilidade.

Minha função espera que sua entrada de pipeline seja simples objetos de cadeia de caracteres. Ela não funcionaria se eu tivesse de substituir o comando Get-Content por, digamos, um cmdlet para recuperar todos os nomes de computador do Active Directory, desta maneira:

Get-QADComputer | Get-SPInventory

Neste caso, o comando Get-QADComputer (parte de um conjunto gratuito de cmdlets de gerenciamento do Active Directory que pode ser obtido em quest.com/powershell) está retornando um objeto que tem a propriedade Name, em vez de retornar simples objetos de cadeia de caracteres. Para que o comando funcionasse, eu teria de refinar a função, mostrada na Figura 4; é importante observar que as alterações estão realçadas em vermelho.

Figura 4 O produto final

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_OperatingSystem –<span class="clsRed" xmlns="http://www.w3.org/1999/xhtml">comp 
    $_.Name</span> | Select 
      CSName,BuildNumber,
        ServicePackMajorVersion
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty 
      BuildNumber ($wmi.BuildNumber)
    $obj | Add-Member NoteProperty CSName 
      ($wmi.CSName)
    $obj | Add-Member NoteProperty SPVersion  
      ($wmi.ServicePackMajorVersion)

    $wmi = Get-WmiObject Win32_BIOS –comp <span class="clsRed" xmlns="http://www.w3.org/1999/xhtml">$_.Name |</span> 
      Select SerialNumber
    $obj | Add-Member NoteProperty BIOSSerial 
      ($wmi.SerialNumber)
    Write-Output $obj
  }
}

Em vez de passar todo o objeto pipeline para o parâmetro –computerName, agora minha função está trabalhando na propriedade Name do objeto pipeline. Essa pequena alteração é bastante conveniente em termos de flexibilidade — usando Get-QADComputer, consigo limitar minha entrada a computadores de uma unidade organizacional específica, por exemplo, ou apenas aos computadores que atendem a outros critérios baseados em atributos do Active Directory.

E quanto aos erros?

Inevitavelmente, esta função poderá se deparar com um computador ao qual não poderá se conectar, no qual não tem permissão para se conectar, e assim por diante. A função, da maneira como foi escrita, exibirá uma mensagem de erro com texto em vermelho na janela do console do Windows PowerShell e prosseguirá para o computador seguinte.

Isso, é claro, pode ser exatamente o que você deseja que ela faça. Ou talvez você prefira que a função suprima essas mensagens de erro. Fazer isso é fácil; basta adicionar esta linha ao início do bloco de scripts PROCESS da função:

$ErrorActionPreference = "SilentlyContinue"

Poderá ocorrer de posteriormente você querer adotar uma abordagem mais complexa e criar um log de erros que liste os nomes dos computadores aos quais não conseguiu se conectar. O Windows PowerShell pode fazer isso, mas você terá de esperar até o mês que vem para saber como!

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

Eis uma excelente maneira de compartilhar aliases personalizados que você criou ou de carregá-los facilmente cada vez que o shell for iniciado. Após criar todos os aliases desejados, exporte-os para um arquivo, da seguinte maneira:

Export-Alias c:\aliases.xml

Em seguida, para carregar esses aliases novamente no shell, execute este comando:

Import-Alias c:\aliases.xml

Você pode colocar esse segundo comando no script de perfil do Windows PowerShell para que ele seja executado sempre que o Windows PowerShell for iniciado. Também é possível colocar o arquivo em um compartilhamento da rede para que ele esteja prontamente disponível aos outros administradores com os quais você trabalha.

Tornando isso realmente útil

Até aqui, pode ser que você tenha inserido esta função em um arquivo .ps1 e executado o script. Isso tudo é ótimo, mas existem alguns problemas de usabilidade. Um deles é que, quando você quiser usar a função, terá de abrir o arquivo de script, modificar a linha que chama a função (para adicionar quaisquer comandos de saída, classificação ou outros que precisar no momento), salvar o script e executá-lo. Seria bem mais fácil se a própria função estivesse disponível na janela do console do shell, quase como um cmdlet.

Há duas opções para começar a fazer isso. A primeira é simplesmente copiar a função em um script de perfil do Windows PowerShell, um dos quatro arquivos de script que o shell executará automaticamente (se existir) sempre que for iniciado. O Guia de Início Rápido que é instalado com o Windows PowerShell lista os quatro locais.

Normalmente uso o arquivo profile.ps1, que deve estar localizado na pasta WindowsPowerShell (sem espaços) da pasta Documents (ou My Documents em computadores com o Windows XP e Windows Server 2003). Uma vez que a função estiver no seu perfil, ela ficará disponível no prompt de comando a qualquer momento que você estiver no shell.

A segunda opção é incluir a função — apenas a função, sem outro código — em um script e digitar um ponto final e um espaço em branco nesse script no shell. O que geralmente ocorre quando você executa um script é que o Windows PowerShell cria um novo escopo para o script. Tudo o que ocorre no script acontece dentro daquele escopo, como a definição de funções, por exemplo. Quando a execução do script termina, o escopo é descartado e tudo o que aconteceu dentro dele é perdido.

Por isso, lembre-se de que, se você tiver um arquivo de script que contém apenas a função Get-SPInventory, a execução desse script criará um novo escopo, definirá a função e descartará o escopo, então a função desaparecerá. Obviamente isso não é muito útil para você.

Por outro lado, digitar um ponto final e um espaço em branco executa um script sem criar um novo escopo. Se eu tiver colocado Get-SPInventory em um arquivo chamado MyFunction.ps1, poderei digitar um ponto final e um espaço em branco nele, da seguinte maneira:

PS C:\> . c:\functions\myfunction

Observe o ponto final sozinho, seguido de um espaço, que precede o caminho e o nome de arquivo do script. Isso instrui o shell a executar o script no escopo atual, o que significa que tudo o que acontece no script permanecerá após o término de sua execução. O resultado é que agora o script Get-SPInventory está definido no shell e você pode usá-lo diretamente da linha de comando, quase que da mesma forma que se ele fosse um cmdlet.

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 pelo site ConcentratedTech.com.