前言
PowerShell是一种单线程的应用程序,也就意味着PowerShell一次只能处理单个任务。
但是借助于PowerShell的后台作业功能,它可以将一个命令移至另一个独立的后台线程(一个独立的,PowerShell后台进程)。该功能使得命令以后台模式运行,这样就可以使用PowerShell处理其他任务。但是必须在执行该命令之前就决定是否这样处理,因为在按回车键之后,无法将一个正在运行的命令移至后台进程。
当命令处于后台模式时,PowerShell会使用一些机制来查看这些进程的状态,获取产生的结果等。
同步VS异步
正常情况下,PowerShell会使用同步模式执行命令,也就意味着,在按回车键之后,需要等待命令执行完毕。
将一个命令置于后台模式将会使得该命名异步运行,也就是说,异步执行的命令在执行过程中,可以使用PowerShell处理其他任务。
下面是在两种模式中运行命令时的重要区别:
- 当在同步模式下运行命令时,可以响应输入请求;
- 当使用后台模式运行命令时,根本就没有机会看到输入请求;
- 实际上,当遇到输入请求时,会停止执行该命令。
- 在同步模式中,如果遇到错误,命令会立即返回错误信息;
- 后台执行的命令也会产生错误信息,但是你无法立即查看这些信息。
- 如果需要,必须通过一些机制来获取这些信息(后面会讲解如何实现)。
- 在同步模式中,如果忽略了某个命令的必要参数,PowerShell会提示对应的缺失信息;
- 如果是后台执行的命令,无法提示缺失信息,所以命令会执行失败。
- 在同步模式中,当命令开始产生执行结果时,就会立即返回;
- 但是当命令处于后台模式时,必须等待命令执行结束,才能获取缓存的执行结果。
通常情况下,我们会用同步模式执行命令,以便对这些命令进行测试,并使得可以正常工作。仅当它们被全面调试并能按照预期执行后,我们才会使用后台模式。
后台作业
PowerShell将后台执行的命令称为作业(Jobs)。可以通过多种方法创建作业,可以使用多种命令管理它们。
创建本地作业:Start-Job
本地作业是指一个命令几乎完全在本地计算机上运行,并且该命令以后台模式运行。
创建这种类型的作业,需要使用Start-Job
命令。参数-ScriptBlock
可以指定需要执行的命令(一个或多个)。PowerShell会自动使用默认的作业名称(Job1、Job2等)。
- 可以使用
-Name
参数,指定特定的作业名称。 - 如果需要作业运行在其他凭据下,可以使用
-Credential
参数接受一个域名\用户名(DOMAIN\UserName)的凭据,同时该参数也会提示输入密码。 - 如果没有指定一个脚本块,可以使用
-FilePath
参数来使得作业执行完整的脚本文件。
范例:创建了一个作业对象(该作业会立即开始运行)
说明:
- 作业会按照顺序,被赋予一个作业ID号;
- 创建的第一个作业被命名为Job1,同时ID为1,但是创建的第二个Job名为Job3,同时ID为3。
- 每个作业至少都有包含一个子作业,第一个子作业(job1的子作业)会被命名为job2,其ID为2。
- 使用
-ComputerName
参数的命令,表示作业中的命令会被允许访问远程计算机。- 作业的进程会在本地计算机上运行,但它会与指定的远程计算机进行连接。所以从某种程度上说,这个作业就是一个“远程作业”。
尽管本地作业是在本地运行,但是它们也会需要使用PowerShell远程处理系统的架构。因此,如果没启用远程处理,那么将无法创建本地作业。
WMI作业
创建作业的另一种方法是使用Get-WMIObject命令。Get-WMIObject命令会与一台或多台远程计算机进行连接,但是通过串行方式实现。这意味着如果给出一长串计算机名称,将需要花费很长的时间执行某个命令,那么将该命令移至后台作业就成为了必然选择。
为了将该命令置为后台运行模式,像往常一样执行Get-WMIObject命令,但是需要加上**-AsJob**
参数。但其不能指定自定义的作业名称,只能使用PowerShell指定的默认作业名称。
在该示例中,PowerShell会创建一个上层的父作业(如Job3),同时会针对指定的每个计算机创建一个子作业。你可以看到,上面输出表格的Location列中包含多个计算机名称,也就表明该作业也会在这些计算机上运行。
Get-WMIObject命令仅会在本地计算机上运行,该命令会使用正常的WMI通信机制与指定的远程计算机进行连接。它仍然一次只在一台计算机上执行,并且遵循直接跳过不可访问的计算机的默认规则等。实际上,该实现过程等同于同步执行Get-WMIObject命令,唯一不同点是该命令在后台运行。
Get-CimInstance命令并没有包含-AsJob参数。如果想在作业中使用该命令,请运行Start-Job或者Invoke-Command,并且将Get-CIMInstance放在脚本块中。
远程处理作业:Invoke-Command
此处,我们会通过将-AsJob
参数,添加到[Invoke-Command](https://www.yuque.com/cc8816/snu0xo/ahs1d1#LUHfB)
Cmdlet中实现新的作业技术:PowerShell的远程处理功能。
请记住:在-ScriptBlock
参数(或别名-Command)中指定的任意命令,都会并行发送到指定的每台计算机。并且可以同时访问多达32台计算机(除非修改了-ThrottleLimit
参数),所以当指定了超过32个计算机名称,仅有前32台计算机会开始执行该命令。当在所有计算机上都执行结束后,上层的父作业会返回一个完整的状态。
与另外两种新建作业的方式不同,该技术要求在每台目标计算机上PowerShell中均启用远程处理。因为命令会真正在每台远程计算机上执行,所以可以通过分布式计算工作负载提升复杂的或者长时间运行命令的性能。执行结果会返回到你的本地计算机。在你准备查看它们之前,结果都会与作业一起被存储。
在下面的示例中,通过-JobName
参数指定一个特有的作业名称:
PS C:\> Invoke-Command -ScriptBlock {Get-Process}`
>> -ComputerName (Get-Content "D:\(Tmp\servers.txt")`
>> -AsJob -JobName MyRemoteJob
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
6 MyRemoteJob RemoteJob Running True localhost,im20211010 Get-Pr...
获取作业执行结果:Receive-Job
当开启一个作业之后,如何确认作业是否执行结束?Get-Job
这个Cmdlet可以获取在系统中定义的所有作业,并且返回其状态。
Tips:做实验时不是同一天,因此,命令执行的前后顺序不一致。
还可以通过作业ID或名称去查询特定的作业信息。将返回结果通过管道传递给Format-List *,可以收更多信息。
其中,ChildJobs
属性是返回信息中最重要之一。
为了获取一个作业的执行结果,请使用Receive-Job
命令:
- 必须指定希望获取返回结果的对应的作业ID或作业名称;
- 也可通过Get-Job命令获取作业列表,之后将它们通过管道传递给Receive-Job命令。
- 如果获取了父作业的返回结果,那么该结果会包含所有子作业的输出结果。
- 当然,也可以获取一个或多个子作业的执行结果。
- 当获取了一个作业的返回结果之后,会自动在作业的输出缓存中清除对应的数据,即不能再次获取它们。
- 可以通过
-Keep
参数,在内存中保留输出结果的一份拷贝。 - 也可以将结果输出到CliXML中。
- 可以通过
- 作业的返回结果可能是反序列化的对象,也就意味着它们可能不会包含可以执行的任何方法。
- 但是,可以将作业的返回结果通过管道传递给一些Cmdlet,比如Sort-Object、Format-List、Export-CSV、ConvertTo-HTML、Out-File等。
范例:获取作业的执行结果
当运行该命令时,PowerShell是在C:\路径下,但是在结果中的路径却是C:\Users\xxx\Documents
。
本地作业运行时会在不同的上下文中,这可能会导致路径改变。当使用后台作业时,请永远不要猜测这些文件路径。因此需要使用绝对路径,从而确保可以关联作业命令可能需要的任何文件。如果希望后台作业获取C:\下的目录信息,那么应该这样执行命令。
当获取Job4的结果时,没有指定-Keep参数。如果再次获取这部分结果,不会得到任何信息,因为这部分结果已经没有与作业一同被缓存了。
范例:强制将结果驻留在内存缓存中(再次获取结果若不加-keep参数,同样会被清空缓存)
范例:将作业结果通过管道直接传递给其他Cmdlet
Get-Job命令还会告知你,还有哪些作业还留有剩余的结果。
当某个作业的输出结果没有被缓存时,对应的HasMoreData列为False。
使用子作业
所有的作业,都由一个上层父作业以及至少一个子作业组成:
可以看到,Job4包含了一个子作业Job5。接下来,就可以通过子作业ID,直接获取该作业的信息。
有时,某个作业会包含多个子作业,它们都会以这种格式罗列出来。也可以通过Select-Object的-ExpandProperty
参数,单独来查看ChildJobs
:
当然,也可以使用Receive-Job
命令指定作业名称或ID,获取任意独立子作业的结果。
管理作业的命令
针对作业,还有另外3个命令。对这3个命令中任意一个,都可以指定作业ID、作业名称,或者先获取作业信息,然后通过管道传递给这3个命令之一。
Remove-Job
:该命令会移除一个作业,包括从内存中移除该作业缓存的所有输出结果。Stop-Job
:如果某个作业卡住了,可以通过执行该命令停止它。但仍可以获取截止到该时刻产生的结果。Wait-Job
:该命令在下面场景中比较有用:- 当使用一段脚本开启一个作业,同时希望该脚本在作业运行完毕之后继续执行。
- 该命令会使得PowerShell停止并等待作业执行,在作业执行结束后,允许PowerShell继续执行。
作业执行失败的情况
在PowerShell中,作业也可能执行失败,也就意味着在执行过程中发生了某些错误,如下:
此处,向根本不存在的计算机发送一条不存在的命令来开启一个作业。当然,该作业立即就会失败,返回的State列值为Failed。此时,根本就不需要使用Stop-Job,因为该作业并未运行。但仍然可以获取对应的子作业列表。
获取其子作业的信息:
该作业并没有产生任何输出,因此你并不能获取对应的结果。但是该作业的错误信息仍然保留在结果中,你可以使用Receive-Job
命令获取这部分信息。
可以看到,错误信息中包含产生错误的计算机名称:[NotOnline]。
当仅有某台计算机无法连接时会发生什么呢?看下面的示例:
它们都失败了,每一个子作业都会由于不同的原因执行失败,PowerShell能分别进行追踪。
计划任务调度作业
在v3版本的PowerShell中引入了针对调度作业的支持——可以在Windows的计划任务程序中,使用PowerShell友好的方式创建任务。
这里的作业与之前的那些作业相比,会采用不同的工作方式。正如前面写到的,作业是PowerShell中的一个扩展点,也就意味着允许存在多种通过不同方式实现的作业。调度作业正好是这些不同种类的作业中的一种。这种作业与标准计划任务作业有一点差别,这些作业的输出结果会存到磁盘中以供PowerShell后续进行使用。
实际上,术语调度作业(scheduledjobs)与调度任务(scheduled tasks)并不同——前一种是与PowerShell相关的,后一种是经常使用的传统作业。
创建调度作业
1)通过创建一个触发器(New-JobTrigger
)开启一个调度作业,该触发器主要用于定义任务的运行时间。
- 同时,也可以使用
New-ScheduledTaskOption
命令,设置该作业的选项。
2)之后使用Register-ScheduledJob
命令,将该作业注册到计划任务程序中。
- 该命令采用计划任务程序中的XML格式来创建作业的定义;
- 之后在磁盘上新建一个层级结构的文件夹,存放每次作业运行的结果。
范例:新建一个作业,在每天凌晨两点执行Get-Process命令。如果有必要,会唤醒计算机,同时要求该作业运行在高级特权下。
PS C:\Windows\system32> Register-ScheduledJob -Name DailyProcList -ScriptBlock {Get-Process} -Trigger (New-JobTrigger -Daily -At 3am) -ScheduledJobOption (New-ScheduledJobOption -WakeToRun -RunElevated)
Id Name JobTriggers Command Enabled
-- ---- ----------- ------- -------
1 DailyProcList 1 Get-Process True
查看创建的调度作业:Get-ScheduledJob
也可以在任务计划程序找到创建的作业:
当作业执行完毕后,回到PowerShell,执行Get-Job查看每次该调度作业执行结束时的一个标准作业列表。
不像常规的作业,从调度作业中获取结果并不会导致结果被删除,因为它们是被缓存在磁盘上,而非内存中。
当移除这些作业时,对应的结果也会从磁盘上被移除。如下图,输出的结果会存放于磁盘上特定的文件夹中,Receive-Job命令可以阅读这些结果。
- 请注意,它们存放于用户配置文件下,因此它通常要求管理员从配置文件中获取文件(和结果)。
可以通过Register-ScheduledJob
命令的-MaxResultCount
参数,控制存放的结果数量。
总结
请记住,不要混淆和随意匹配开启作业的3种方式。如下就是错误的使用范例: