原文:http://blogs.technet.com/b/heyscriptingguy/archive/2013/09/28/weekend-scripter-max-out-powershell-in-a-little-bit-of-time.aspx


我的工作环境是一些分布范围比较广的数据中心,经常需要从多台服务器中寻找特定的事件。如何快速的从5台、10台、甚至20台、70台服务器中获取事件日志,成为一大挑战。

先来看看我们的可选解决方案。首先想到的是Windows PowerShell的远程处理(invoke)。其次PowerShell Jobs(任务处理)也是可行的,同时对于那些热衷开发的管理员们,他们会考虑使用RunSpaces(运行空间)。

远程处理(Invoke):

Invoke-Command带有一个可接受Array的ComputerName参数,和ThrottleLimit(活动线程数限制)参数。这样看起来Invoke-Command简直就是为了此类任务而生,让我们看看它是如何使用的。

我将在5台服务器上查询 “系统日志”里的Event ID 1074的事件,基于演示目的,这里指定一下

MaxEvents(最大返回事件数)为1:
$Servers = "mail01","mail02","mbx01","mbx02","mbx03"
$Events = Invoke-Command -ComputerName $Servers -ScriptBlock {$sb=@{Logname='System';Id=1074};Get-WinEvent -FilterHashtable $sb -MaxEvents 1}
$Events

让PowerShell更快一点-Part.1_第1张图片

 

这些命令可以简单的写成一行执行,使用Invoke-Command命令简单粗暴。当你需要在多台服务器上执行一条命令,而那条命令的ComputerName参数又无法接受数组形式的参数时,Invoke-Command会满足你的需求。我们可以看到Invoke-Command命令几乎立即执行并且返回了结果。

带上Measure-Command,可以显示出这一系列的命令一共花费了742毫秒,但是我们再仔细看一下其返回的对象。似乎和我在本地执行Get-WinEvent一模一样。

而其实,注意一下Properties属性的值,它是一个有深度的,嵌套对象。对其进行展开看看内容:

其实这个嵌套对象只返回了「字符串类型」的「嵌套对象的名字」。在本地执行一下Get-WinEvent来比较看看:

$Event = Get-WinEvent -cn mail01 -FilterHashtable @{Logname='System';id=1074} -MaxEvents 1
$Event | select *

目前为止看起来没什么差别,但是展开一下Properties里的值:

可以看到本地执行Get-WinEvent返回的对象里是有具体值的。

这个具体值包含在Properties参数里,如果我使用其他的命令或者功能,这个具体值就会包含在其他的参数里。比如,我现在想知道谁重启了我的服务器Mail01,我目前只知道,该服务器被重启过了。

那么我可以使用Get-WinEvent,分析其Message属性里的文本对象,或者我可以看一下Properties参数里的第七个值。在前面的例子里,vmicsvc.exe初始化了一个关机的动作,使用的是NT Authority\System账户。所以可以判断是用户使用了Hyper-V管理器或Stop-VM命令关闭了该虚拟机。为了强调这一点,我可以通过针对Properties的一段文本解析来获得该结果,如下命令:

$Event = Get-WinEvent -cn mail01 -FilterHashtable @{Logname='System';id=1074} -MaxEvents 1
$Event | Format-Table MachineName,@{name="ShutdownByUser";Expression={$_.Properties[6].value}}

在这个例子里面,只使用了两行代码(其实可以写在一行里面),我就能得到启用本次重启的用户名。非常简单。

任务(Jobs):

接下来我们使用Jobs,Jobs的执行方式非常屌,因为它是异步的。所以如果你将命令放入Job里执行,提示符会立刻返回,你可以继续使用PowerShell,因为Job会在后台执行。(更多关于Jobs,请参考Using Windows PowerShell Jobs),现在来看看Jobs是如何操作的:

$Servers = "mail01","mail02","mbx01","mbx02","mbx03"
Foreach($Server in $Servers){
Start-Job -ScriptBlock {Get-WinEvent -ComputerName $using:Server -FilterHashtable @{LogName='System';Id=1074} -MaxEvents 1}
}
$EventsUsingJobs = Get-Job | Wait-Job | Receive-Job
$EventsUsingJobs

Jobs方法会比其他方法多几个步骤,Start-Job中不包含ComputerName参数,所以必须得遍历一个包含所有服务器名的数组,将每一个服务器名提供给Get-WinEvent命令。你可以看到Jobs已经被创建,而当Jobs执行完毕时,必须使用一个Receive-Job命令来获取结果。这里也加上一个Measure-Command命令来查看其耗时。

使用Jobs耗费了4秒来查询5台服务器,比起Invoke-Command,那是相当的慢。但是也不是特别慢啦,毕竟比手动要快很多。

然后再来看Jobs返回的对象,依然和Invoke-Command一样,丢失了嵌套对象,所以我只能考虑将Get-WinEvent的执行结果管道到Export-CliXml里,这个结果就会被写到磁盘里然后使用Import-CliXml来返回,当然这也额外的增加了工作量。

运行空间(RunSpaces):

接下来是运行空间,这里我单独写了一个功能名字叫Get-AsyncEvent,它在异步状态下运行Get-WinEvent命令,下面就是其在这个例子中运行的结果:

可以看到Runspaces比Inovke-Command要快上不少(293ms),基本上快了一倍。接下来看看返回对象:

不错!不仅仅是快了,而且返回的是完整的对象,而非反序列化过的。

让我们重新总结一下:

  • Invoke-Command使用起来非常简单,但是它不返回原始对象。(反序列化)

  • Start-Job比Inovke-Command需要多一点编码,但是是最慢的。必须使用Export-CliXml否则得不到任何原始对象的返回。

  • Runspaces呢,更快更高更强!

可以说,Runspaces是目前最复杂的多线程解决方案,在下一章,我们就聊聊如何使用Runspaces。