PowerShell runspace 的创建,使用和查错

今天拜读了The Scripting Guy关于runspace的几篇大作,温故而知新,一些忽略的地方更为清楚。

https://blogs.technet.microsoft.com/heyscriptingguy/2015/11/26/beginning-use-of-powershell-runspaces-part-1/


runspace这两年在PowerShell使用的频率越来越高,由于他的高效率,基本上很多时候已经取代了传统的Job后台操作。不管是多线程实现,或者是后台操作,亦或是入侵脚本,runspace 的性能可以是job的几十倍以上。


现在由浅入深的看几个例子,到底是怎么实现的。


例1 同步操作一个PowerShell实例


创建一个PowerShell的实例,然后添加一段代码,然后invoke执行。


1
2
3
4
5
6
$PowerShell  [powershell] ::Create()
[void] $PowerShell .AddScript({
     Get-Date
     Start-Sleep  -Seconds 10
})
$PowerShell .Invoke()


注意几点:

  •  PowerShell不仅仅是一个脚本语言,我们可以通过这个类System.Management.Automation.PowerShell所提供的方法来创建实例,添加脚本和参数。

    Powershell 作为平台的使用方法参考https://blogs.msdn.microsoft.com/powershell/2013/10/01/paap-windows-powershell-as-a-platform-part-1/


  •  我使用了[void]的目的是避免输出太多乱起八糟的信息(比如当前runspace的信息等等)来污染我的输出结果 


  •  我在这个脚本里面添加了一条等待10秒的命令,当我们执行invoke命令的时候,会看见屏幕上卡了10秒以后,才返回当前的时间。 这种同步的操作在执行多线程和后台操作的时候应该避免,后面我们会说如何实现异步操作。


wKioL1eDHSnz19gLAAAlf_ymGvo443.png


可以看见执行invoke之后直接返回了一个结果,事实上,这个和我直接在控制台输入Get-Date的效果一样,这样的脚本并不适合在后台操作。


例2,异步操作Runspace和PowerShell实例。


1
2
3
4
5
6
7
8
9
$Runspace  [runspacefactory] ::CreateRunspace()
$PowerShell  = [powershell] ::Create()
$PowerShell .runspace =  $Runspace
$Runspace .Open()
[void] $PowerShell .AddScript({
     Get-Date
     Start-Sleep  -Seconds 10
})
$AsyncObject  $PowerShell .BeginInvoke()


这一次,我们创建一个runspace的对象,然后把它绑定到一个PowerShell的实例中,运行这个runspace,然后和上面一样,给这个PowerShell的实例添加脚本。请注意,一个很重要的改进是这里使用了BeginInvoke()方法,这个会返回一个异步的对象。异步和同步的区别在于,同步会死等在某个环节,直到输出结果,而异步会自动切换出来,定期的检查结果,等操作结束之后,再切换回去获取结果。


一个简单有趣的C#例子老王洗澡很生动了对比了同步和异步操作

http://www.cnblogs.com/lxblog/archive/2012/12/11/2813893.html


回到之前的脚本,我们来看看BeginInvoke返回的异步对象是什么样。因为我设置了要等10秒钟,所以可以看见他的IsComleted属性是False

wKiom1eDHGuR0FQyAAAfUmP6AwI619.png


10秒钟以后再查看,就已经变成True,这表示操作已经结束。

wKioL1eDHG6jj0T7AAAfbnD7N48773.png


现在我们调用EndInvoke这个异步对象切换回去获取结果


1
2
$Data  $PowerShell .EndInvoke( $AsyncObject )
$Data


成功获取结果

wKioL1eDHHGjsth7AAAXBigfK94941.png


最后别忘记关掉这个实例。

1
$PowerShell .Dispose()


例3 给PowerShell的平台传递参数


1
2
3
4
5
6
7
8
9
10
11
12
13
$name  'James'
$title  'Manager'
$PowerShell  = [powershell] ::Create()
[void] $PowerShell .AddScript({
     Param  ( $Param1 $Param2 )
     [pscustomobject] @{
         Param1 =  $Param1
         Param2 =  $Param2
     }
}).AddArgument( $name ).AddArgument( $title )
#Invoke the command
$PowerShell .Invoke()
$PowerShell .Dispose()

比如我定义了2个变量,我可以通过AddArgument来传递到脚本块里,当然脚本块里面也得定义对应的参数,然后按顺序传入变量。


例4  如果参数过多,我可以通过定义hash表和 addParameters来传递参数,这样显得更为简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ParamList  = @{
     param1 =  'Kevin'
     Param2 =  'receptionist'
}
$PowerShell  [powershell] ::Create()
[void] $PowerShell .AddScript({
     Param  ( $Param1 $Param2 )
     [pscustomobject] @{
         name =  $Param1
         title =  $Param2
     }
}).AddParameters( $ParamList )
#Invoke the command
$PowerShell .Invoke()
$PowerShell .Dispose()



例5  整合上面的知识点,来个完整的例子,例如使用 runspace pool来实现多线程的Ping


这个是我之前写过的一个例子。http://beanxyz.blog.51cto.com/5570417/1760880


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
59
60
$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可以同时执行,这里表示最低1个,最多20个
  
$RunspacePool  [RunspaceFactory] ::CreateRunspacePool(1,  $Throttle )
$RunspacePool .Open()
$Jobs  = @()
   
  
#获取Windows 2012服务器的信息,对每一个服务器单独创建一个Powershell实例,每一个实例都异步的执行PING的操作,并把异步对象保存在Result里面
#最后把所有的结果都保存在一个Jobs的对象中。
#注意这里绑定的是RunspacePool,而不是单个的Runspace
   
( 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



例6, 最后看看来怎么debug runspace。 PowerShell 5 以后提供了一个命令 Debug-Runspace可以像普通的debug一样来跟踪当前执行的命令或者脚本。


先直接创建一个runspace,可以看见他的状态是available

1
2
3
4
$rs = [runspacefactory] ::CreateRunspace()
$rs .name= "MyRunSpace"
$rs .open()
get-runspace

wKioL1eDLWSASkHOAAAnZLVt6qQ278.png

把这个runspace绑定到一个powershell的实例,绑定某个脚本,异步执行


1
2
3
4
5
$ps = [powershell] ::create()
$ps .runspace= $rs
$ps .addscript( 'C:\users\yli\documents\github\Powershell\Restart-WSUSComputers.ps1' ) >  $null
$async = $ps .BeginInvoke()
get-runspace


可以看见他的状态变成了busy

wKiom1eDLWfR40SEAAAzHxLES2M166.png


这个时候我们来debug一下

1
Debug-Runspace  Myrunspace


他自动就切换到我的脚本页面了,按F10就会一行行的自动跟踪执行下去了


wKioL1eDLWqCliC0AAC9V7VbQZ0394.png




如果需要终止debug,在控制台输入 detach 就可以了






本文转自 beanxyz 51CTO博客,原文链接:http://blog.51cto.com/beanxyz/1825300,如需转载请自行联系原作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值