第15章 多任务后台作业
第15章 多任务后台作业
15.1 利用PowerShell实现多任务同时处理
借助于PowerShell的后台作业功能,它可以将一个命令移至另一个独立的后台线程(一个独立的,PowerShell后台进程)。该功能使得命令以后台模式运行,这样你就可以使用PowerShell处理其他任务。但是你必须在执行该命令之前就决定是否这样处理,因为在按回车键之后,无法将一个正在运行的命令移至后台进程。
15.2 同步VS异步
首先,介绍一些术语。在正常情况下,PowerShell会使用同步模式执行命令,也就意味着,在按回车键之后,你需要等待命令执行完毕。将一个命令置于后台模式将会使得该命令异步运行,也就是说,异步执行的命令在执行过程中,可以使用PowerShell处理其他任务。
下面是在两种模式中运行命令时的重要区别。
- 当在同步模式中运行命令时,可以响应输入请求;当使用后台模式运行命令时,根本就没有机会看到输入请求——实际上,当遇到输入请求时,会停止执行该命令。
- 在同步模式中,如果遇到错误,命令会立即返回错误信息;后台执行的命令也会产生错误信息,但是你无法立即查看这些信息。如果需要,你必须通过一些机制来获取这些信息(第22章会讲解如何实现)。
- 在同步模式中,如果忽略了某个命令的必要参数,PowerShell会提示对应的缺失信息;如果是后台执行的命令,无法提示缺失信息,所以命令会执行失败。
- 在同步模式中,当命令开始产生执行结果时,就会立即返回;但是当命令处于后台模式时,你必须等待命令执行结束,才能获取缓存的执行结果。
通常情况下,我们会用同步模式执行命令,以便对这些命令进行测试,并使得可以正常工作。仅当它们被全面调试并能按照预期执行后,我们才会使用后台模式。我们只有遵循这些规则来保证命令的成功执行,这才是将命令置于后台模式的最好的时机。
PowerShell将后台执行的命令称为作业(Jobs)。你可以通过多种方法创建作业,可以使用多种命令管理它们。
15.3 创建本地作业
首先讲到的第一个作业类型应该是最简单的:本地作业。这是指一个命令几乎完全在你的本地计算机上运行(在后面会讲到对应的例外),并且该命令以后台模式运行。
为了创建这种类型的作业,你需要使用Start-Job命令。参数-ScriptBlock使得你可以指定需要执行的命令(一个或多个)。PowerShell会自动使用默认的作业名称(Job1,Job2等)。当然,你也可以使用-Name参数指定特定的作业名称。如果你需要作业运行在其他凭据下,那么你可以使用-Credential参数接受一个域名\用户名(DOMAIN\UserName)的凭据,同时该参数也会使得提示输入密码。如果没有指定一个脚本块,你也可以使用-FilePath参数来使得作业执行包含多个命令的完整脚本文件。
下面是一个简单的示例。
PS C:\>Start-Job -ScriptBlock {Dir}
如果你执行一个可支持-ComputerName参数的命令,在这种情形下,作业中的命令会被允许访问远程计算机。比如下面的示例。
PS C:\>Start-Job -ScriptBlock {
Get-EventLog Security -Computer Server-R2
}
15.4 WMI作业
创建作业的另一种方法是使用Get-WMIObject命令。正如我们在上一章所讲,Get-WMIObject命令会与一台或多台远程计算机进行连接,但是通过串行方式实现。这意味着如果给出一长串计算机名称,将需要花费很长的时间执行某个命令,那么将该命令移至后台作业就成为了必然选择。为了将命令置为后台运行模式,像往常一样执行Get-WMIObject命令,但是需要加上-AsJob参数。此时,你不能指定一个自定义的作业名称,只能使用PowerShell指定的默认作业名称。
请注意,在第14章中学到的Get-CimInstance命令,并没有包含-AsJob参数。如果你想在作业中使用该命令,请运行Start-Job或者Invoke-Command(你即将学到的命令),并且将Get-CIMInstance(或者说,任何新的CIM命令)放在脚本块中。
15.5 远程处理作业
下面介绍最后一种可以用来创建新作业的技术:PowerShell的远程处理功能,也就是你在第13章中学习的功能。当使用Get-WMIObject时,你会使用-AsJob参数实现该功能,但是这里我们会通过将该参数添加到Invoke-Command Cmdlet中实现。
这里有一个重要的不同点:在-ScriptBlock参数(或者是该参数的别名,-Command)中指定的任意命令都会并行发送到指定的每台计算机。可以同时访问多达32台计算机,所以当你指定了超过32台计算机名称,仅有前32台计算机会开始执行该命令。当在前32台计算机即将结束时,剩余的计算机才可以开始执行这些命令。另外,当在所有计算机上都执行结束后,上层的父作业会返回一个完整的状态。
与另外两种新建作业的方式不同,该技术要求你在每台目标计算机上安装第二版或者之后版本的PowerShell,同时要求在每台目标计算机上PowerShell中均启用远程处理。
在下面的示例中,你可以看到通过-JobName参数指定一个特有的作业名称,这样就不需要无意义的默认名称。
PS C:\>Invoke-Command -Command {Get-Process}
-ComputerName (Get-Content .\allservers.txt)
-AsJob -JobName MyRemoteJob
15.6 获取作业执行结果
当开启一个作业之后,你最想做的第一件事应该就是确认作业是否执行结束。Get-Job这个Cmdlet可以获取在系统中定义的所有作业,并且返回其状态。
Get-Job
你也可以通过作业ID或者名称去查询特定的作业信息。我们建议你可以尝试该命令并且将返回结果通过管道传递给Format-List *,因为你已经收集了很多有用的信息。
Get-job -id 1 | format-list *
其中ChildJobs属性是返回信息中最重要的部分之一,在后面会讲到该部分。
为了获取一个作业的执行结果,请使用Receive-Job命令。但是在运行该Cmdlet之前,请先了解下面的一些知识点。
- 你必须指定希望获取返回结果的对应作业。
- 如果你获取了父作业的返回结果,那么该结果会包含所有子作业的输出结果。当然,你也可以获取一个或多个子作业的执行结果。
- 正常情况下,当获取了一个作业的返回结果之后,会自动在作业的输出缓存中清除对应的数据,这样你不能再次获取它们。可以通过-Keep命令在内存中保留输出结果的一份拷贝。或者你希望保存一份拷贝以作它用,也可以将结果输出到CliXML中。
- 作业的结果可能是反序列化的对象,也就是你在第13章中所学的知识。
Receive-Job -id 1
我们快速看一下如何将作业结果通过管道直接传递给其他Cmdlet。
PS C:\>Receive-Job -Name MyRemoteJob | Sort-Object PSComputerName | Format-Table -GroupBy PSComputer
15.7 使用子作业
在前面我们提及,所有的作业都由一个上层父作业以及一个子作业组成。我们再次查看该作业。
PS C:\>Get-job -id 1 | format-list *
你可以看到,Job1包含了一个子作业Job2。既然你知道了它的名字,那么你就可以直接获取该作业的信息。
PS C:\>Get-Job -Name Job2 | Format-List *
15.8 管理作业的命令
针对作业,也可以使用另外3个命令。对这3个命令中任意一个,你都可以指定作业ID、作业名称,或者先获取作业信息,然后通过管道传递给这3个命令之一。
- Remove-Job——该命令会移除一个作业,包括从内存中移除该作业缓存的所有输出结果。
- Stop-Job——如果某个作业看起来卡住了,你可以通过执行该命令停止它。但是仍然可以获取截止到该时刻产生的结果。
- Wait-Job——该命令在下面场景中比较有用。当使用一段脚本开启一个作业,同时希望该脚本在作业运行完毕之后继续执行。
15.9 调试作业
在v3版本的PowerShell中引入了针对调度作业的支持——可以在Windows的计划任务程序中使用PowerShell友好的方式创建任务。这里的作业与之前讲的那些作业相比,会采用不同的工作方式。
你通过创建一个触发器(New-JobTrigger)开启一个调度作业,该触发器主要用于定义任务的运行时间。同时,你也可以使用New-ScheduleTaskOption命令设置该作业的选项。之后你使用Register-ScheduledJob命令将该作业注册到计划任务程序中。该命令采用计划任务程序中的XML格式来创建作业的定义,之后在磁盘上新建一个层级结构的文件夹存放每次作业运行的结果。
现在看下面的示例。
PS C:\> Register-ScheduledJob -name DailyProcList -ScriptBlock { Get-process }
-Trigger (New-JobTrigger -Daily -At 2am) -ScheduledJobOption
(New-ScheduledJobOption -WakeToRun -RunElevated)
该命令会新建一个作业,该作业在每天凌晨2点执行Get-Process命令。如果有必要,会唤醒计算机,同时要求该作业运行在高级特权下。当作业执行完毕后,你只可以回到PowerShell中,执行Get-Job查看每次该调度作业执行结束时的一个标准作业列表。
输出的结果会存放于磁盘上特定的文件夹中,Receive-Job命令可以阅读这些结果。
你可以通过Register-ScheduledJob命令的-MaxResultCount参数控制存放的结果数量。