Windows PowerShell:将命令变为可重用工具

在使用 Windows PowerShell 命令和 cmdlet 时,您可以重新打包和重用您的工作成果。

Don Jones

无论您在最初开始使用 Windows PowerShell 时有多么地缺乏经验,您始终都有很大的进步空间。您可以在最初阶段运行简单的命令,而后渐渐学会使用更复杂的命令,并最终将这些命令重新打包到一个在外观和使用方面几乎类似于本机 cmdlet 的工具中。此类工具称作高级函数,俗称“脚本 cmdlet”。

假设您要从计算机中检索一些重要的清单信息。您需要 Windows 版本、BIOS 序列号、Service Pack 版本以及处理器体系结构。您可以使用以下三个命令获取此信息:

Get-WmiObject –class Win32_OperatingSystem –computername SERVER-R2 | Select-Object –property __SERVER,BuildNumber,Caption,ServicePackMajorVersion
Get-WmiObject –class Win32_BIOS –computername SERVER-R2 | Select-Object –property SerialNumber
Get-WmiObject –class Win32_Processor –computername SERVER-R2 | Select-Object –property AddressWidth

问题是这些命令将生成三个不同的结果集。您无法将所有结果直接通过管道传输到一个 CSV 文件(例如,用来存储清单数据)或一个 HTML 文件(用以将此清单显示为网页)。最好将这些数据绑定到一个即便是经验不足的用户也可以使用的参数化命令中。您需要此命令:

  • 通过管道接受一个或多个以字符串形式提供的计算机名称,如下所示:
Get-Content names.txt | Get-OSInfo | ConvertTo-HTML | Out-File info.html
  • 通过 –computername 参数接受一个或多个计算机名称,如下所示:
Get-OSInfo –computername Server-R2,ServerDC4 | Format-Table
  • 通过管道接受一个或多个分别包含一个 computername 属性的对象,如下所示:
Get-ADComputer –filter * -searchbase "ou=West,dc=company,dc=com" | Select-Object @{label='computername';expression={$_.Name}} | Get-OSInfo | Export-CSV inventory.csv

采用这种方式,您将无需担心计算机名称的出处,也不必担心您将创建的输出类型。相关任务将由 Shell 处理。

此外,您还需要有一个 –logfile 参数,该参数将接受日志文件的路径和文件名。由于此命令将仍使用 Windows Management Instrumentation (WMI) 连接和查询信息,因此您有可能无法访问一台或多台计算机。您仍需要将这些计算机的名称写入该日志文件,随后您可以使用此文件解决问题,甚至重新尝试访问这些计算机。

处理错误

您可以使用 PowerShell 中的 Try…Catch 构造完成最后这一部分。只需将第一个 WMI 查询打包到 Try…Catch 块中。然后指定 –ErrorAction Stop 参数,以便它能够捕获命令生成的任何错误。设置一个用于跟踪是否出错的变量,以便您的脚本确定是否尝试进行随后的两个 WMI 查询:

$continue = $True
Try {
  Get-WmiObject –class Win32_BIOS –computername $computername –EA Stop
} Catch {
  $continue = $False
  $computername | Out-File –path $logfile –append
}

处理输入

比较棘手的部分是处理输入。您将通过两种方法之一接收输入 — 通过管道或通过参数。您实际上可以使用 cmdlet 样式的参数绑定将通过管道传入的输入定向到参数。以下是此操作的基本结构:

Function Get-OSInfo {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
  [string[]]$computername,
  [string]$logfile 
 )
  BEGIN {}
  PROCESS {}
  END {}
}

cmdlet 中的 PROCESS 块可保证至少执行一次;如果未提供管道输入,PROCESS 块将执行一次。如果提供了管道输入,PROCESS 块将针对每个通过管道传入的项目执行一次。它会将该项目置于 $computername 变量中。

问题就出在这里:如果仅通过参数指定输入(如第二个项目符号标记的示例所示),则 PROCESS 将仅执行一次。$computername 将包含提供给该参数的每个计算机名称。您必须自行枚举或“展开”这些名称。如果这些项目通过管道传入,则您一次只须处理一个项目。您还可以在 $computername 变量中获得它们。

技巧在于创建一个执行实际工作的从属函数。使用高级函数接受任一类型的输入,并将相关操作分解为一次显示一个计算机名称。然后,调用每次处理一个计算机名称的从属“辅助”函数。以下是基本结构,此结构将位于主函数的 PROCESS 块中:

PROCESS {
  if ($PSBoundParameters.ContainsKey('computername')) {
    foreach($computer in $computername) {
      OSInfoWorker –computername $computer –logfile $logfile
    }
  } else {
    OSInfoWorker –computername $computername –logfile $logfile
  }
}

结束语

解决主要的技术难题后,将所有内容综合在一起,并添加一些其他细节,例如在每次运行函数之前清理旧的日志文件(此操作非常适合于 BEGIN 块)。由两个函数组成的最终脚本有一点长。但其中只有很少一部分需要实际的“编程”。大多数内容只是一些类似于在命令行上运行的 Windows PowerShell 命令。它们只是被大量声明性结构所包围,从而使其行为类似于 cmdlet。

全部内容发布在我的网站 ow.ly/39YcX 上,同时还提供了最终代码的视频演示。这是一个绝对可以重用的模板。实际上,它的主要功能只是处理输入,因此您几乎可以按原样使用它。您只需根据自己的特定需要更改参数,仅此而已。实际的工作在单独的函数中执行。

下个月,我将介绍如何将所有这些内容打包为一个易于分发的脚本模块。此外,我还将介绍如何为您的所有管理员同事可以使用的脚本模块设置一个共享位置。

Don Jones

Don Jones是 Concentrated Technology 的创始人,他会在 ConcentratedTech.com 上解答有关 Windows PowerShell 和其他技术的问题。他也是 Nexus.Realtimepublishers.com 的撰稿人,他的许多著作还在他的网站上以电子版的形式提供。

 

获取更多内容

本月的文章随附了视频演示以及可下载的示例代码。从此处获取这些额外的内容

相关内容