PowerShell : 创建一个更好的清单工具


在以前的专栏里,我创建了一个函数,可以收集到多台远程计算机中的补丁包清单信息,尽管这个工具已经很实用了,但是我更加关注开发这个工具中所用到的流程方法。在这里,我会向大家演示这些流程方法,这样你就可以更加清楚如何去创建自己的函数了
在前面我所创建的函数大概是如下
Function Get-SPInventory {
    PROCESS {
$wmi = Get-WmiObject Win32_OperatingSystem –comp $_ | select CSName,BuildNumber, ServicePackMajorVersion
Write-Output $wmi
    }
}
你可以这样使用它
Get-Content c:\computernames.txt | Get-SPInventory
如果文本文件中每行就是每个计算机名的话,这函数很有效


对像总是一样的

问题是,我的函数是从Win32_OperatingSystem 类读取信息,并且简单的输出显示该对象。Win32_OperatingSystem 类,就像其他对象一样所包含的数据是固定的--- 他没有并且不可能有包含BIOS序列号信息,如果我只是单纯输出Win32_OperatingSystem类对象,我是不可能得到序列号的。
不管我们怎么查找WMI,我们肯定是找不到一个单独的对象,它既包含了BIOS序列号信息,也包含了补丁包信息。因此,我的函数不能只是显示一个WMI类,我需要显示一个自定义对象,包含我所有数据的对象。
要创建一个新的、空白没有属性的对象,我只是运行
$obj = New-Object PSObject

这个新对象是存放在变量$obj中的,并且我可以往里面添加任何我需要的数据。一旦我把所有所需的数据加入进去,他就变成了我的函数的输出
在我的函数中,我从Win32_OperatingSystem中得到信息,并且保存在变量$Wmi中。就如察看$Wmi属性那样简单,我们可以如下的使用来得到想要的信息,比如,有关BuildNumber属性,我可以
$wmi.BuildNumber

为了给我的自定义对象加入属性,我需要对对象使用管道(在变量$obj中)输出到Add-Memeber,我需要告诉Add-Member 属性类型是什么(一般是NoteProperty),属性名称,属性值,比如
$obj | Add-Member NoteProperty BuildNumber ($wmi.BuildNumber)

同样也适用于计算机名和补丁包版本
$obj | Add-Member NotePropertyCSName ($wmi.CSName)
$obj | Add-Member NotePropertySPVersion ($wmi.ServicePackMajorVersion)

依此我们可以创建新的函数(请察看图片1),请注意我更改了Write-Output行来显示我定制化对象,而不是现实原来的$wmi对象
实战2: 使用PowerShell创建一个更好的清单工具_PowerShell_05 图1 自定义对象
Function Get-SPInventory {
PROCESS {
$wmi = Get-WmiObject Win32_OperatingSystem -comp $_ | 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)
Write-Output $obj
}
}
现在我需要拿到BIOS序列号信息,通过搜索我们知道它是Win32_BIOS类,其中有一个序列号Serial Number的属性(参考图片2),所以我只要见到的查询Win32_BIOS类,把序列号属性加到我定制对象中,做完后,你可以看到修改后的函数(图片3)
实战2: 使用PowerShell创建一个更好的清单工具_PowerShell_17
图2 Win32_Bios 文档页面
实战2: 使用PowerShell创建一个更好的清单工具_实战_18 图3:包含序列号(Serial Number)属性
Function Get-SPInventory {
PROCESS {
$wmi = Get-WmiObject Win32_OperatingSystem -comp $_ | 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 $_ | Select SerialNumber
$obj | Add-Member NoteProperty BIOSSerial ($wmi.SerialNumber)
Write-Output $obj
}
}


更灵活的输出

为了对自定义对象结果用于输出,我为这个函数的创建了各种可能的用途,比如,只显示Windows 2008服务器的电脑,可以使用
Gc c:\computernames.txt | Get-SPInventory | Where { $_.BuildNumber -eq 6001 }

或者,使用HTML文件来显示所有没有安装SP1的Windows Vista电脑,可以使用
Gc c:\computernames.txt | Get-SPInventory | Where { $_.BuildNumber -eq 6000 } | ConvertTo-HTML | Out-File c:\VistaInventory.html

其他的用法是相当多的 -XML(输出到XML格式), CSV(输出到XML格式), Table(输出到表格形式), Lists(以清单格式显示), HTML(以HTML格式输出), Sorted(排序), Filtered(过滤), Grouped(分组) 等等。 内置的Windows PowerShell的Cmdlets就可以实现,并不需要其他另外的工作。


更灵活的输入

不幸的是,我的输入不是很灵活,这是Windows PowerShell版本1中的弱点,而在版本2中引入了脚本Cmdlets, 这就提供了相当多的灵活性。
我的函数希望输入是简单的字符串对象,比如,如果我使用cmdlet来取代get-content命令的话,该功能不能工作 (Get-Content是一个命令,可以从活动目录中拿到计算机名),就如
Get-QADComputer | Get-SPInventory

在这里,get-QADComputer命令返回的对象是包含名称属性,而不是返回简单的字符串对象。为了让这个命令能够运行,我需要去修改这个函数,请注意图片4。请注意其中的变化(用红色字体显示的)
(是免费工具活动目录管理Cmdlets中的一部分,你可以从quest.com/powershell中得到)
实战2: 使用PowerShell创建一个更好的清单工具_实战_23 图4 最终结果
Function Get-SPInventory {
    PROCESS {
        $wmi = Get-WmiObject Win32_OperatingSystem –comp $_.Name | 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 $_.Name | Select SerialNumber
        $obj | Add-Member NoteProperty BIOSSerial ($wmi.SerialNumber)
        Write-Output $obj
    }
}

我的函数是作用在管道对象的名称属性上的,而不是把管道所有对象都传递给参数-computername。,就灵活性而言,这个小改变很有意义---通过get-QADComputer函数,我可以把我的输入限定到一个特定的组织单元,比如只有符合活动目录某属性的电脑


有错误,怎么办?

可以肯定的是,这个函数肯定会遇到某些无法连接的电脑,或者没有权限去连接的电脑,我们的函数,到现在为止,可以在Windows PowerShell界面中以红色字体显示错误信息,然后继续去找下一台电脑

当然,这可能也就是你想要的。但是你也有可能不想显示这些错误信息,我们也可以通过在函数的Process 脚本块中添加下面一行
$ErrorActionPreference = "SilentlyContinue"
然后,你可能需要一个更复杂的方法来创建错误信息日志,可以显示那些不能完成的计算机名称。 Windows PowerShell可以做到这点,但是你要等到下个月才能够看到如何做到(在这里,你就要等到下个礼拜才能看到)


让他变得真正有用起来

现在在,可能你已经把这个函数输入到一个.PS1文件中然后再运行这个脚本。这样已经很好了,但是还是有些可用性方面的问题,有一个问题是,无论你什么时候想用这个函数,你需要打开这个脚本问题,修改启用这个函数的命令行(添加输出,排序或者其他的命令),保存脚本,然后再运行。
如果能够像Cmdlet那样,能够直接在Windows Powershell中直接调用该函数
这里有两种办法,
第一种就是简单的把函数复制到Windows Powershell属性脚本中,该脚本是Poweshell每次启动时会自动运行的四个脚本文件之一(如果存在的话),在Powershell的快速入门指南中列出了这四个位置。
我一般使用的是名为Profile.PS1的文件,它必须位于“我的文档”文件夹中的Windows-PowerShell文件夹中,一旦函数在你的配置文件后,你可以直接在Shell命令提示符直接使用
第二种办法就是,将函数(只有函数,不含任何其他代码)包含在脚本中,然后将该脚本以“点源”(dot source) 方式置入 shell 中。当运行脚本时,Windows PowerShell 通常都会为该脚本创建新作用域。该脚本内发生的任何操作都会在该作用域内进行,例如函数的定义。当脚本结束运行时,该作用域会被弃用,发生在该作用域内的一切操作随之丢失。
因此,请记住,如果你拥有只包含 Get-SP Inventory 函数的脚本文件,那么运行该脚本将会创建新作用域、定义该函数,然后弃用该作用域,该函数会随之消失。显然,这不太实用。
相反,通过 dot source 运行脚本不会创建新作用域。如果我把 Get-SPInventory 置于名为 MyFunction.ps1 的文件中,我可以以如下所示方式 dot source 它:
PS C:\> . c:\functions\myfunction

请注意脚本的路径和文件名前面的单个句点后面有个空格。这会告知 shell 在当前作用域中运行脚本,这意味着发生在脚本中的一切操作在脚本结束运行后会保存下来。结果 Get-SPInventory 脚本现在已定义在 shell 中,而您现在可以直接从命令行使用它,几乎像使用 cmdlet 一样。