PowerShell 多线程的使用
今天一个朋友问我在Powershell里面如何能够并发的ping上万台机器?默认的test-connection 尽管有-computer这个参数,他的方式是按顺序的挨个ping,全部跑下来可能有好几个小时。
比如我需要花18秒的时间才能ping完40台服务器,如果成千上万的话就很费时间了。
1
2
3
4
5
6
|
measure-commnd -expression {
$computers = Get-ADComputer - Filter {operatingsystem -like "*2012*" }
Test-Connection -ComputerName $computers .name -Count 1
} |
这之前,豆子对多线程的使用仅仅限于了解invoke-command可以同时对30个对象操作,经过一番学习,终于发现还有其他 的高级方式。
PowerShell里面,对于多线程的使用大概是两大方式。
第一个是创建多个后台的job。这种方式通过start-job或者 -asjob创建后台job,然后通过get-job获取当前的任务,通过receive-job来获取完成任务的结果,最后还得remove-job来释放内存。缺点是性能不高,尤其在创建job和退出job的过程中会消耗大量时间和资源。
第二个方式是创建多个runspace,这个工作原理和invoke-command一样,每一个远程的session绑定一个runspace。我们可以创建一个runspace pool,指定在这个资源池里面最多可以同时执行多少个runspace。
比起第一种方式,runspace的性能强悍了太多。下面有人做的对比实验,可以看见几乎是几十倍的性能差距。
现在看看怎么来实现。豆子主要参考了这篇博客的方法和原理,写了一个简单的脚本来。
http://thesurlyadmin.com/2013/02/11/multithreading-powershell-scripts/
思路很简单,创建runspace pool,指定runspace的数量,然后对要测试的对象集合,对每一个对象都创建一个后台的runspace job,绑定要执行的脚本,传入参数,把结果保存在ps对象或者hash表中,最后等待所有job结束,输出结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
$Throttle = 20 #threads
#脚本块,对指定的计算机发送一个ICMP包测试,结果保存在一个对象里面 $ScriptBlock = {
Param (
[string] $Computer
)
$a = test-connection -ComputerName $Computer -Count 1
$RunResult = New-Object PSObject -Property @{
IPv4Adress= $a .ipv4address.IPAddressToString
ComputerName= $Computer
}
Return $RunResult
} #创建一个资源池,指定多少个runspace可以同时执行 $RunspacePool = [RunspaceFactory] ::CreateRunspacePool(1, $Throttle )
$RunspacePool .Open()
$Jobs = @()
#获取Windows 2012服务器的信息,对每一个服务器单独创建一个job,该job执行ICMP的测试,并把结果保存在一个PS对象中 ( get-adcomputer - filter {operatingsystem -like "*2012*" }).name | % {
#Start-Sleep -Seconds 1
$Job = [powershell] ::Create().AddScript( $ScriptBlock ).AddArgument( $_ )
$Job .RunspacePool = $RunspacePool
$Jobs += New-Object PSObject -Property @{
Server = $_
Pipe = $Job
Result = $Job .BeginInvoke()
}
} #循环输出等待的信息.... 直到所有的job都完成
Write-Host "Waiting.." -NoNewline
Do {
Write-Host "." -NoNewline
Start-Sleep -Seconds 1
} While ( $Jobs .Result.IsCompleted -contains $false )
Write-Host "All jobs completed!"
#输出结果 $Results = @()
ForEach ( $Job in $Jobs )
{ $Results += $Job .Pipe.EndInvoke( $Job .Result)
} $Results |
大概5秒之后 结果就出来了。 如果有兴趣的话可以使用measure-command命令来测试不同线程数的效果,根据我的测试,30个进程同时执行只需4秒出结果,而2个同时执行大概需要9秒才能出结果。
知道原理之后就可以进一步优化和抽象化脚本。这一点已经有人做好了。https://github.com/RamblingCookieMonster/Invoke-Parallel/blob/master/Invoke-Parallel/Invoke-Parallel.ps1
下载,Unlock和dot source之后就能直接调用了。这里提供了一些例子作为参考https://github.com/RamblingCookieMonster/Invoke-Parallel
依葫芦画瓢,我想通过他来调用test-connection也是成功的
1
|
get-adcomputer - Filter {operatingsystem -like "*2012*" } | select -ExpandProperty name | Invoke-Parallel -ScriptBlock { Test-Connection -computername $_ -count 1}
|
再比如我ping 一个IP范围的计算机
1
|
1..254| Invoke-Parallel -ScriptBlock { Test-Connection -ComputerName "10.2.100.$_" -Count 1 -ErrorAction SilentlyContinue -ErrorVariable err | select Ipv4address, @{n= 'DNS' ;e={ [System.Net.Dns] ::gethostentry( $_ .ipv4address).hostname}}} -Throttle 20
|
最后,网上也有现成的脚本用来并发的测试ping,原理也是调用上面的invoke-parallel函数,不过他还增加了其他的函数用来测试rdp,winrm,rpc等远程访问的端口是否打开,进一步扩充了功能。可以直接在这里下载
http://ramblingcookiemonster.github.io/Invoke-Ping/
1
|
invoke-ping -ComputerName ( Get-ADComputer - Filter {operatingsystem -like "*2012*" }).name -Detail RDP,rpc | ft -Wrap
|
参考资料:
2. https://github.com/RamblingCookieMonster/Invoke-Parallel
3. http://thesurlyadmin.com/2013/02/11/multithreading-powershell-scripts/
4. http://ramblingcookiemonster.github.io/Invoke-Ping/
5. http://learn-powershell.net/2012/05/10/speedy-network-information-query-using-powershell/