Windows PowerShell投球框

Don Jones

目录

扮演 Cmdlet
筛选函数
从物理对象角度思考
实际应用
游戏时间

在我最近讲授的一堂 Windows PowerShell 课中,一些学生难以想象出外壳管道的具体含义。我承认管道从概念上讲不十分直观,因此对于习惯于视觉教学的初学者来说,要弄清楚它的含义有些困难。当我讲到

直接在管道中工作的筛选函数的概念时,事情真地棘手起来,从他们的表情我可以看出有些学生没有听懂。

为了解决他们的困惑,第二天我带来了一个球框、一些名称标记贴纸和一些乒乓球(嘿,谁说 Windows PowerShell® 不能变得饶有趣味)。我决定使用以下命令行做个展示:

Get-Process | Where { $_.VM –gt 1000 } | Sort VM
–descending | Export-CSV C:\Procs.csv

扮演 Cmdlet

通过使用名称标记贴纸,就是您经常在同学会或类似活动中佩戴的“你好,我是……”贴纸,我给每个学生分配一个 cmdlet 名称。我解释说乒乓球代表 Windows® 进程对象(更具体地说,是 System.Diagnostics.Process 类型对象),然后我要求学生告诉我哪个 cmdlet 名称听起来最可能生成进程对象。

看过彼此的标记后,他们认为是最明显的选项 Get-Process。这也是我非常喜欢 Windows PowerShell 中使用的 cmdlet 名称的一个主要原因:它们表述得十分清楚。

因此,饰演 Get-Process 的学生抓起所有乒乓球并将它们投入纸板框中。这个框代表外壳的管道,学生的动作几乎就是 cmdlet 的功能。cmdlet 生成一个或多个对象,然后将它们倒入管道中。

然后管道中的下一 cmdlet 取得执行权。在此示例中,代表 Where-Object cmdlet 的学生捡起所有乒乓球,并一个一个地检查,查看是否某个球的 VM 属性大于 1,000。在外壳中,“VM”代表虚拟内存,在本练习中,我在每个乒乓球的标签上写下了不同数量的虚拟内存。

VM 大于或等于 1,000 的每个球(或称为进程)会返回球框(有时称为管道)中,其他的球弃之不用。(实际并非如此,我将它们解救了出来以便在将来的课程中使用。)

接下来,假扮 Sort-Object 的学生仔细检查乒乓球框,并根据 VM 属性将它们依次排序。我必须承认这里有点儿考虑不周 — 球总是滚来滚去的!看来下次课我得找一些方形的乒乓球,或者准备一些鸡蛋箱。

最后,扮演 Export-CSV 的学生捡起球并将信息写到一个 CSV 文件中。在真实世界中,是将进程的属性写到我们假设为 CSV 文件的活动挂图中。

筛选函数

了解过简单的管道后,我们决定看看筛选函数(它要复杂一些)。我推荐图 1 中所示的筛选函数和命令行。

图 1 示例筛选函数和命令行

Function Do-Something {
 BEGIN { }
 PROCESS {
  $obj = New-Object PSObject
  $obj | Add-Member NoteProperty "TimeStamp" (Get-Date)
  $obj | Add-Member NoteProperty "ProcessName" ($_.Name)
  Write-Output $obj
 }
 END {}
}
Get-Process | Do-Something | Format-Table *

首先,我想简要回顾一下筛选函数的功能。此函数包含三个脚本块:BEGIN、PROCESS 和 END。

在管道中使用此函数时,首先执行 BEGIN 脚本块。此脚本可能执行一些设置任务,如打开数据库连接。

然后,PROCESS 脚本块对通过管道传递给函数的每个对象执行一次。在 PROCESS 脚本块中,自动使用当前管道对象填充 $_ 变量。因此,如果传入 10 个对象,PROCESS 脚本块将执行 10 次。

最后,在执行完所有传入对象后,运行 END 脚本块并执行所有必要的清理任务(如关闭数据库连接)。

在这些脚本块中,使用 Write-Output 编写的所有内容将存储在下一 cmdlet 的管道中。其他内容则会被丢弃。

由于命令始于 Get-Process,之前的学生将所有乒乓球归拢在一起(我们休息了一会,有人就恶做剧一样将球扔得到处都是 — 想想看吧)并将它们投入管道框中。这项工作完成后,扮演“Do-Something”筛选函数的学生接过执行权。

他首先执行自己的 BEGIN 脚本块。它是空的,因此并不费事。接下来,他拾起所有管道对象(乒乓球)并开始一个一个地查看。这挺有趣的,因为一共大约有一打球,他正尽力在膝盖上摆放所有的球 — 哦,也许正需要您的帮助。

总之,他一次挑选一个进程对象并执行 PROCESS 脚本块。该脚本块让他创建一个全新的自定义对象(我使用特殊的黄色乒乓球来代表它,在当地体育用品店中找到这些球可不是件容易事)。在这些新乒乓球上,他记下了当前日期和时间以及当前查看的进程对象的名称。

这个新自定义对象随后被写入管道,即他将黄色乒乓球放入球框中,丢弃原来的进程对象乒乓球。他对框中的每个进程对象乒乓球执行此操作,总计约 12 个。完成后,每个白色乒乓球都被替换成黄色的自定义对象乒乓球。最后,他执行 END 脚本块(它没有内容,不耗费任何时间)。

为了有始有终,扮演 Format-Table 学生现在登场。她捡起框中的所有球(此时全是黄色的“自定义”对象)并开始使用每个球上写的属性构建一个表。最终会生成写在活动挂图上的一个表,共有两列(TimeStamp 和 ProcessName),大约十二行。

从物理对象角度思考

此练习使管道和筛选函数真正触动了班级中的每个人。乒乓球出色地表达出了对象的含义,如果只说外壳很容易让人感觉抽象。

每个人都可以看到 cmdlet 如何处理这些对象,结果如何被放入管道中以及下一 cmdlet 如何选取这些结果并进一步处理对象。筛选函数中的事件顺序更加明显,因为 PROCESS 脚本块技术一次处理一个输入对象。

它还显示了创建新自定义对象并将其放入管道的方式。并且,它有助于展示生成自定义对象的好处,与简单文本相比,其他 cmdlet(如 Format-Table)可使用新的自定义对象,随后可以很灵活地选择使用和代表数据的方式。

实际应用

学生中一个普遍的问题是如何在实际应用中使用我们刚才展示的管道和筛选函数。对于我来讲,答案很简单,因为我刚为最近的一个会议制作了一些示例(可从 scriptinganswers.com/essentials/index.php/2008/03/25/techmentor-2008-san-francisco-auditing-examples 自行下载示例)。

其中一个示例是要列出 Win32_UserAccount 类的多个属性,它来自多个计算机的 Windows 管理规范 (WMI)。假定计算机名称列在 C:\Computers.txt 中,以下简单命令即可完成此工作:

Gwmi win32_useraccount –comp (gc c:\computers.txt)

问题在于 Win32_UserAccount 类并不包括告诉您每个实例来自哪个计算机的属性,因此结果列表将是一个无用的大杂烩,其中包含许多计算机的帐户。我的解决办法是创建一个包括源计算机名称的新自定义对象并选择所需的类属性。此自定义对象的代码如图 2 所示。

图 2 使用自定义对象来收集计算机名称并选择属性

function Get-UserInventory {
  PROCESS {
    # $_ is a computer name
    $users = gwmi win32_useraccount -ComputerName $_
    foreach ($user in $users) {
      $obj = New-Object
      $obj | Add-Member NoteProperty Computer $_
      $obj | Add-Member NotePropertyPasswordExpires ($user.PasswordExpires)
      $obj | Add-Member NoteProperty Disabled ($user.Disabled)
      $obj | Add-Member NotePropertyFullName ($user.FullName)
      $obj | Add-Member NoteProperty Lockout ($user.Lockout)
      $obj | Add-Member NoteProperty Name ($user.Name)
      $obj | Add-Member NotePropertyPasswordRequired ($user.PasswordRequired)
      $obj | Add-Member NotePropertyPasswordChangeable ($user.PasswordChangeable)
    }
  }
}

Get-Content c:\computers.txt | 
  Get-UserInventory | 
  where { $_.PasswordRequired -eq $False } | 
  selectComputer,Name | 
  Export-Csv c:\BasUsersBad.csv

最后的命令行将所有计算机名称传递给筛选函数(它会生成自定义对象)。接下来,我筛选掉所有其他用户,只保留其 PasswordRequired 属性为 False 的用户(此想法旨在生成问题帐户的审核报告)。然后我仅保留 Computer 和 Name 属性,这样最终报告是一份计算机名称和帐户名称的列表,由于它们的密码永不过期,因此需要加以注意。 筛选函数能生成这份多计算机报告,因为它向输出添加了源计算机名称,同时仅留下了我所感兴趣的属性。

尽管有其他类似方法可完成此任务,但此方法可能最直接。它还可用于阐述重要概念和技术。

游戏时间

即使已经对管道运用自如,仍有许多内容需要学习。从物理对象角度思考有助于弄清楚您正在尝试做什么。

因此下次遇到 Windows PowerShell 概念问题时,试着跳出计算机的束缚,以日常物品为例来模仿任务。我的愚见是在手边放一小袋乒乓球(或立方体,如果找得到)备用。

本月 Cmdlet:Out-File

您有多少次使用 > 符号将 cmdlet 的输出导出到某个文件?它类似于 Get-Process > Processes.txt。您是否知道您实际只是使用伪装的 Out-File cmdlet?以下是执行完全相同功能的命令:Get-Process | Out-File Processes.txt。

当然,它需要的键入操作要多一点,那么为什么要麻烦地键出完整的 Out-File cmdlet?一个原因是 Out-File 在阅读时要更加清楚些。比如,某人六个月后可能看到您的一个脚本,并且想知道 > 代表什么(毕竟,它是一种旧事物)。

相反,Out-File 非常清楚地表示将创建一个文件并将其写出。并且,Out-File 提供了 -append 参数(非常类似于 >>)以及 -force 和 -noClobber 参数,从而使您可以控制是否覆盖现有文件。最后,键出 Out-File 还可以让您访问 -whatif 参数。这个便捷的参数将向您展示 Out-File 将执行的操作,但并不会实际执行!它是测试复杂命令行的一个不错方法,几乎没有风险。

Don Jones 是《Windows PowerShell:TFM》一书的合著者并且教授 Windows PowerShell 课程 (www.ScriptingTraining.com)。

© 2008 Microsoft Corporation 和 CMP Media, LLC。保留所有权利;未经允许不得复制本文的部分或全部内容。