第16章 同时处理多个对象
第16章 同时处理多个对象
PowerShell存在的主要意义在于自动化管理,这通常意味着你将会在多个目标上同时执行任务。你或许希望重启多台计算机,重新配置多个服务,修改多个邮箱等。在本章,你将学到3种技术:批处理Cmdlet、WMI方法以及对象枚举,用于完成这些以及其他多目标任务。
16.2 首选方法:“批处理”Cmdlet
比如说,我们希望改变3个服务的启动模式。我们不选择VBScript方式的方法,而是采用下面这种。
Get-Service -name BITS,Spooler,W32Time | Set-Service -startuptype Automatic
从某种程度上来说,Get-Service也是一种批处理Cmdlet。这是由于该命令能够从多台计算机中获取服务。假设你需要变更同样这3台计算机上的服务。
Get-Service -name BITS,Spooler,Win32Time -computer Server1,Server2,Server3 | Set-Service -Startuptype Automatic
上述方法中一个潜在的问题在于,执行动作的Cmdlet通常不会返回表示作业状态的结果。这意味着上面两个命令都不会产生可视化结果,这非常令人不安。值得庆幸的是,这些命令通常会有一个-PassThru参数,该参数用于打印出该命令所接受的对象。你也可以使用Set-Service输出其修改的服务,并使用Get-Service重新获取这些服务以便查看之前的命令是否生效。
下面是不同Cmdlet使用-PassThru参数的示例。
Get-Service -name BITS -computer Server1,Server2,Server3 |
Start-service -passthru |
Out-file NewServiceStatus.txt
该命令将会从3台计算机列表中获取指定的服务,然后通过管道将这些服务传递给Start-Service。该命令不仅会启动服务,而且会将涉及的服务对象打印在屏幕上。然后这些服务对象将会通过管道传递给Out-File,将这些被更新对象的信息存储在文本文件中。
再重审一次:这是我们使用PowerShell推荐的首选方式。
16.3 CIM/WMI方式:调用方法
不幸的是,总有一些任务无法通过调用Cmdlet完成。而且有一些我们可以通过Windows管理规范(WMI)可以操控的条目。
++注意:我们将通过故事线的方式帮助你体验人们如何使用PowerShell。这会看起来有点多余,但请记住,经验本身是无价的++。
比如,WMI中的Win32_NetworkAdapterConfiguration类。该类代表与网卡绑定的配置信息(网卡可以有多个配置,但目前我们假设它只有一个配置信息,这也是对于大多数计算机的常见配置)。假如说我们的目标是在计算机上所有的Intel网卡上启用DHCP,但我们不希望启用RAS或其他虚拟网卡的DHCP。
为了得到上述输出结果,我们需要查询合适的WMI类并过滤出只有描述中包含INTEL的配置。下面的代码可以完成该功能(注意在WMI过滤中以”%“作为通配符)。
PS C:\>gwmi win32_networkadapterconfiguration -filter "description like '%intel%'"
我们在管道中包含这些配置对象信息后,我们希望启用DHCP(你可以看到其中一块网卡并没有启用DHCP)。我们或许可以找一个名称类似”Enable-DHCP”的Cmdlet。不幸的是,我们找不到该Cmdlet,因此不存在该Cmdlet。没有任何Cmdlet可以直接在批处理中与WMI对象打交道。
下一步是查看对象本身是否包含可以启用DHCP的方法,为了找出结果,我们将配置对象通过管道传输给Get-Member(或者其别名gm)。
PS C:\>gwmi win32_networkadapterconfiguration -filter "description like '%intel%'" | gm
在结果列表的开始部分,我们可以看到我们寻找的方法EnableDHCP()。
下一步,也是很多PowerShell新手会尝试的方法,将配置对象通过管道传递给该方法。
PS C:\>gwmi win32_networkadapterconfiguration -filter "description like '%intel%'" | EnableDHCP()
不幸的是,这是无效的。你不能将对象通过管道传输给方法,你只能将其传递给Cmdlet。EnableDHCP并不是一个PowerShell的Cmdlet,而是直接附加在配置对象自身的行为。这种传统的、类似VBScript的方法和我们在本章开篇所展示给你的VBScript示例非常类似。但使用PowerShell,你能够以更简单的方式完成该任务。
虽然没有名为Enable-DHCP的“批处理”Cmdlet,但可以使用Invoke-WmiMethod这个通用Cmdlet。该Cmdlet特别设计用于接受一批WMI对象,比如说我们的Win32_NetworkAdapterConfiguration对象。并调用附加在这些对象上的某个方法。下面是我们开始运行的命令。
PS C:\>gwmi win32_networkadapterconfiguration
-filter "description like '%inetl%'" |
Invoke-WmiMethod -name EnableDHCP
你要记住如下几条。
- 方法名称后面无须加括号。
- 方法名称不区分大小写。
- Invoke-WmiMethod一次只能接收一种类型的WMI对象。当然也可以一次发送多个对象,但所有的对象都必须是同一类型。
- 你可以针对Invoke-WmiMethod方法加上-WhatIf和-Confirm参数。但直接由对象调用方法时,无须使用这些参数。
Invoke-WmiMethod的输出结果有点让人困惑。WMI总是产生结果对象,并包含大量系统对象(名称以两个下划线开始)。
当你有一个WMI对象包含可执行的方法时,大多可以使用Invoke-WmiMethod。该命令对远程计算机同样有效。我们的基本原则是“如果你可以使用Get-WmiObject获取对象,则也能够使用Invoke-WmiMethod执行它的方法”。
当你回忆第14章所学内容时,你会发现Get-WmiObject与Invoke-WmiMethod都是“遗留”用于操作WMI的Cmdlet;这两个命令的接替者为Get-CimInstance和Invoke-CimMethod。它们的工作方式或多或少有些相同。
PS C:\>Get-CimInstance -classname win32_networkadapterconfiguration
-filter "description like '%intel%'" |
Invoke-CimMethod -methodname EnableDHCP
16.4 后备计划:枚举对象
不幸的是,我们遇到的一些情况是Invoke-WmiMethod无法执行某个方法——执行时不断返回奇怪的错误信息(Invoke-CimMethod更可靠)。我们还遇到的一些情况是虽然某个Cmdlet可以产生对象,但我们知道并没有可以通过管道接收这些对象并进行操作的批处理Cmdlet。无论是上述哪种情况,你依然可以完成任务,但你必须回到传统的VBScript风格的方法来指挥计算机枚举对象并一次执行一个对象。PowerShell提供了两种方法:第一种是使用Cmdlet,另一种是使用脚本结构。我们在本章主要关注第一种技术,并在第21章阐述第二种。在第21章中,我们将会深入PowerShell内置的脚本语言。
我们使用Win32_Service这个WMI类作为示例。更详细地说,我们将使用Change()。这是一个可以一次性变更某个服务中多个元素的复杂方法。图16.2展示了其在线文档(通过搜索“Win32_Service”并单击Change方法找到)。
通过阅读该页,你会发现无须为该方法的第一个参数赋值。你可以将你希望忽略的参数指定为Null(PowerShell中有一个特殊的内置$null变量)。
对于本例来说,我们希望变更服务的启动密码,也就是第8个参数。为了完成该工作,我们需要将前7个参数指定为$null。这意味着我们的方法执行代码看上去像下面这样。
Change($null,$null,$null,$null,$null,$null,$null,"P@ssw0rd")
顺便提一下,无论是Get-Service还是Set-Service,都无法显示或设置某个服务的登录密码。但WMI可以完成该工作,所以我们使用WMI。
PS C:\>gwmi win32_service -filter "name = 'BITS'" | foreach-object {$_.change($null,$null,$null,$null,$null,$null,$null,"P@ssw0rd"}
让我们把之前的示例中的代码分解为模块。
- 首先,你将会看到Cmdlet名称:Foreach-Object。
- 接下来,使用-Process参数指定脚本段。我们原先并没有输入-Process的参数名称,这是由于该参数为位置参数。但脚本段中,所有在花括号中的代码都是-Process参数的值。所以我们接下来将参数名称包含在内,并更好地格式化,以方便阅读。
- Foreach-Object将会对于每一个通过管道传输给Foreach-Object的对象执行脚本段。每次脚本段执行后,下一个通过管道传输进来的对象都会被置于特殊的$_容器。
- 通过在$_后输入一个“.”,告诉Shell我们需要访问当前对象的属性或方法。
- 在示例中,我们访问Change()方法。注意,方法的参数以逗号分隔列表方式存在,并被包在括号内。我们使用$null作为我们不希望变更的参数传入,并将新密码作为第8个参数。该方法可以接受更多参数,但由于我们不希望修改第9个、第10个或第11个参数,我们可以完全忽视它。(我们也可以将最后三个参数指定为$null。)
你可以对WMI方法使用完全同样的模式。为什么你从不使用Invoke-WmiMethod来代替上面的方法呢?好吧,该命令通常会起作用,并更容易输入和阅读。但如果你倾向于只记住一种方式,那就是Foreach-Object方式。