Windows PowerShell构建自己的软件清单工具

Don Jones

目录

查找信息
原型设计
读取计算机名称
模块化
管道函数

在本期的 Windows Power-Shell 专栏中,我将演示一个非常实用的用法:构建一个用于从计算机列表清点操作系统内部版本号(确定操作系统版本的最佳方法之一)和 Service Pack 版本号的工具。但是我不打算直接为您提供解决方案。我将逐步介绍用来开发这类脚本的流程。

显然,这是一个很有用的工具,但我认为开发此工具的流程更为重要。一旦了解并能够将此开发流程反复用于自己的任务,您便可以使用 Windows PowerShell 来解决几乎所有管理问题(古语有云:授人以鱼不如授人以渔)。让我们开始吧。

查找信息

第一个(通常也是最难的)任务就是找出您实际上可以查找操作系统和 Service Pack 版本号的位置。您可能会想到研究注册表。有时的确可以从注册表找到此类信息,不过我通常在迫不得已时才用这种方法,因为注册表使用起来有些棘手。

“检索”和“信息”等词立即使我联想到一个途径:Windows 管理规范 (WMI)。使我想到 WMI 的另一个关键词是“远程”,因为在第 1 版 Windows PowerShell 中,WMI 可能是执行任何类型的远程信息清点或管理操作的唯一选择。

遗憾的是,大多数 Windows 系统都包含成千上万的 WMI 类,因此很难找到包含所需信息的 WMI 类。我通常会使用 Web 搜索引擎并输入类似“wmi Service Pack 版本号”的词组。请注意,您需要输入较长的搜索词组(如上所示)才能获得更具体的结果。

您甚至还需要尝试使用各种搜索词,但不要使用缩写词(输入“wmi sp 版本”不会提供非常有用的结果)。请考虑使用替代术语。例如,您通常所说的“补丁”可能被其他人称为“修补程序”、“快速修复工程”或“快速修复工程补丁”。要找到正确的结果,可能需要尝试所有这些术语。

如果您要搜索与计算机硬件或 Windows 操作系统内核相关的内容,在搜索词中添加“Win32”应该会有帮助,因为大多数相关的 WMI 类都以“Win32_”前缀开头。在我目前的搜索中添加“Win32”的确会产生最佳结果。我看到许多结果的标题中都包含“Win32_OperatingSystem”,这是一个 WMI 类名称。

现在,重要的技巧就是不要单击任何搜索结果。(那样只会导致麻烦。)首先我要查找该类的实际文档页面,因此我首先使用刚找到的类名称进行新的搜索。通过执行该操作,在前几个搜索结果中通常会生成指向 msdn.microsoft.com 网站的链接,可以直接带您进入类文档页面。

图 1 显示了部分页面。我已滚动到该页面中特别重要的表格,其中列出了该类将使用的操作系统版本。有很多次,我费尽心力尝试使某个类能起作用,结果却发现我所尝试的操作在使用的 Windows I 版本中根本不存在!所以现在我养成了首先检查此表格的习惯。

fig01.gif

图 1 查找 WMI 类的相关信息(单击可获得大图)

将页面向上稍微滚动,我看到了两个感兴趣的属性:BuildNumber 和 ServicePackMajorVersion。事实上,ServicePackMinorVersion 也可能很有用,尽管我从未见过 Microsoft 的 2.1 Service Pack 版本号。尽管如此,为了防止遗漏,最好还是检查此属性。

本月 Cmdlet:Export-CliXML 和 Import-CliXML

Windows PowerShell 能够以特殊的 XML 格式存储对象的静态快照,将这些对象中的信息保存在文件中,并加载到内存以供日后检查。您可以直接将对象输送到 Export-CliXML 以将其保存在文件中:

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

稍后将这些对象导入到外壳后,您可以像检查任何其他对象一样对它们进行检查。它们不是“真正的”对象,但的确可以启用更多的报告选项。假定您已计划了一个脚本,使其于凌晨 3 点正在运行某些维护任务时导出指定服务器上的所有进程。等到您上班时,便可加载并查看这些进程,也可以根据虚拟内存使用量对它们进行排序,如下所示:

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

原型设计

首先我要确保这些属性可以执行我需要的操作,然后才会继续。使用 Windows PowerShell 可轻松执行此任务。首先,我在本地计算机上检查此信息:

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

次版本是零,跟我预期的一样,那我就不管它了。其他信息也跟我预期的一样 — Windows Server 2008 计算机的内部版本号为 6001,Service Pack 版本为 1。

现在我要在远程计算机(其中我是管理员)上执行类似测试:

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

如果测试行不通,在继续之前,我必须先停下来找出原因。问题可能与连接性、防火墙或权限有关,这些都超出了 Windows PowerShell 自身的范围。一切正常之后,我便可以继续执行解决问题所需的下一步骤:如何从文件中获取一组计算机名称。

读取计算机名称

假设我的计算机名称列表保存在文本文件中且每一行都列出一个计算机名称,则最简单的办法就是使用 Get-Content cmdlet。(如果您的计算机名称没有以这种方式提供,也不必烦恼,我将在后续专栏中介绍处理不同情况的技巧。)

每个名称都以独立字符串对象的形式返回。Get-WmiObjectcmdlet 的一个有用功能是其 computerName 参数可接受计算机名称集合,这样问题就解决了:

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

现在的问题在于我的输出是一份编号列表,而且无法指示哪个编号代表哪台计算机。幸运的是,Win32_OperatingSystem 类恰好包含另一个属性 CSName,其中包含计算机名称。因此我可以将该属性添加到输出中,从而获得理想的清点结果:

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

模块化

对于经验不足的技术人员来说,使用上述技巧可能有点困难,因此最后一步就是将所有这些步骤模块化成一个函数。其中一种方法是编写可接受文件名的函数,然后使此函数执行所有工作:

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

您可以看到,我只是将有效代码包含在名为 Get-SPInventory 的函数中。我已使用名为 $filename 的输入参数对其进行定义,因此只能按如下方式调用该函数:

Get-SPInventory c:\computernames.txt

您仍可以使用标准的 Windows PowerShell 命令将结果输送到 CSV 文件、转换为 HTML 格式或采用不同的格式,如下所示:

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

但这个函数仍不完美。如果我还想清点未包含在 Win32_OperatingSystem 类中的某些信息(如每台计算机的 BIOS 序列号),怎么办?许多配置管理数据库 (CMDB) 都将 BIOS 序列号用作计算机的唯一标识符,因此此类信息会很有用。

我还希望函数输出更具灵活性,以便我更轻松地对结果进行排序或筛选。例如,然后我可以选择在最终输出中只包含安装了早期版本 Service Pack 的 Windows Server 2003 计算机以创建需要更新的计算机列表。

管道函数

现在,我要重新编写函数以使其在 Windows PowerShell 管道中发挥更大作用。具体地说,我希望函数直接从管道接受计算机名称,这样,每次使用函数时,我都能确定要从何处获取计算机名称。计算机名称可能保存在文件中,也可能在 Active Directory 中,但我希望函数在这两种情况下都能正常工作。以下为针对管道重新编写的函数:

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

这种特殊的函数类型使用 PROCESS 脚本块,该脚本块将针对输送到函数的每个管道对象执行一次。(忠实的读者应该记得,我在本专栏 2008 年 7 月刊中曾讨论过 PROCESS 脚本块,网址是 technet.microsoft.com/magazine/cc644947.aspx。)特殊的 $_ 变量会自动使用管道输入填充;只要我输送计算机名称,它就能正常工作。新函数的使用方式如下:

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

您可以看到,在从不同的源中获取计算机名称时,此操作可提供更多灵活性。我只是使用检索计算机名称所需的命令替换了命令的 Get-Content 部分。请注意,我还对其进行了设置,以便在构建输出前通过查询不同的 WMI 类(或任何其他数据源)来创建更可靠的输出。

但讨论尚未结束,下个月我将对此函数进行扩展使其输出包含 BIOS 序列号。

Don Jones 是 Concentrated Technology 的创始人之一,同时也是大量 IT 书籍的作者。您可以通过以下网址阅读他的 Windows PowerShell 每周提示或与他取得联系:www.ConcentratedTech.com