PowerShell 使用 Jobs 快速 Ping 子网

PowerShell quickly Ping Subnet with Jobs

以下函数将使用 PingRange 1 254 ping 我的子网以检查 IP:

function PingRange ($from, $to) {
    $from..$to | % {"192.168.0.$($_): $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$($_ ) -quiet -count 1)"}
}

但是,这很慢,所以我想知道是否可以同时对它们执行 ping 命令,然后收集结果。我想这意味着:

  1. 在每个测试连接上使用 Start-Job(我可以做到,那部分很简单)。

  2. 等待全部完成。

  3. 只收集ping成功的结果并排序。

    function PingRange $from $to { $from..$to | % {Start-Job { "192.168.0.$($_): $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$($_ ) -quiet -count 1)"} } Wait-Job *some test to check if all jobs are complete* Receive-Job some way to get each result, discard all failures, then sort and output to screen }

是否有 shorthand 方法来执行仅等待 所有 生成的作业完成的等待作业?

接收信息似乎也很棘手,当我尝试它时,我总是无法从 Receive-Job 返回任何信息(或者通常是一个讨厌的错误)。希望 PowerShell 作业方面的专家知道如何轻松获取这些结果?

您可以使用它添加 asJob 参数:

function PingRange ($from, $to) {
    $from..$to | % {"192.168.0.$($_): $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$($_ ) -quiet -count 1 -asJob)"}
}

Receive-Job 正在返回作业中命令的每个输出。这意味着如果在作业中抛出错误 - 它也会显示在 receive-job 之前。要解决此问题,请密切控制命令生成的输出,例如通过管道传输到 out-null.

一个基本的例子:

$jobIDs = @()
for ($i = 0;$i -lt 10;$i++){
    $jobIds += (start-job -ScriptBlock {
        sleep -seconds (get-random -Maximum 10)
        return (get-random -Maximum 5)
    }).Id
}

while ((get-job -State Running).count -gt 0){
    write-host "waiting for jobs to finish... ($((get-job -state Running).count) still running)"
    sleep -Seconds 1
}

foreach ($jobID in $jobIDs){
    write-host "Job $jobID returned: $(receive-job $jobID)"
}

注意:在 Windows PowerShell 中,最简单的解决方案是使用 Test-Connection -AsJob,如 所示。但是,PowerShell [Core] 6+ 不再支持 -AsJob
这个答案侧重于 与命令无关的方法来实现与作业的并发


在 PowerShell v7+ 中,您将能够 使用 ForEach-Object -Parallel,这可以大大简化您的功能,通过运行使用不同的线程并行执行命令:

function PingRange ($from, $to) {
  $from..$to | ForEach-Object -Parallel {
    "192.168.0.$_`: $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$_ -quiet -count 1)"
  } -ThrottleLimit ($to - $from + 1) 2>$null -ErrorVariable err | Sort-Object
}
  • -ThrottleLimit 默认为 5,这意味着最多 5 个命令 运行 并行,额外的命令 排队 直到池中的一个线程再次可用,因为先前的命令已完成。

    • 在这里,我选择允许 所有 线程并行到 运行,但您必须测试这在实践中是否有效 - 它可能有效对于像这里这样的网络绑定任务,但它不是 CPU 绑定任务的正确选择;请参阅 this blog post 以获取指导。
  • 2>$null 沉默错误输出,但 -ErrorVariable err 收集变量 $err 中的任何错误以供以后检查:

    • 注意:从 v7.0 开始,只有 2>$null 可用于消除错误;不支持常用的 -ErrorAction 参数(-WarningAction-InformationAction-PipelineVariable 也不支持);请注意,如果 $ErrorActionPreference = 'Stop' 恰好生效,2>$null 会触发脚本终止错误。
  • 线程的输出将以无保证顺序到达,但会在到达时打印。

    • 鉴于您无论如何都想要排序的输出,这在这里不是问题。
    • 如果确实需要输入顺序中的输出,使用-AsJob参数,使用结果作业对象Wait-Job等待所有线程完成,此时您可以调用 Receive-Job 以按输入顺序接收所有输出。

在 PowerShell v6- 中,使用 Start-ThreadJobStart-Job 更好,因为线程作业有比基于子进程的标准后台作业的开销少得多。

注意:实施 ThreadJob 模块随 PowerShell 6.x 一起提供;在 Windows PowerShell 中,您可以按需安装它;例如。: Install-Module ThreadJob -Scope CurrentUser.

function PingRange ($from, $to) {
  $from..$to | ForEach-Object {
    Start-ThreadJob -ThrottleLimit ($to - $from + 1) { 
      "192.168.0.$using:_`: $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$using:_ -quiet -count 1)" 
    }
  } | Receive-Job -Wait -AutoRemove -ErrorAction SilentlyContinue -ErrorVariable err |
      Sort-Object 
}

请注意需要 $using:_ 才能引用封闭的 ForEach-Object 脚本块的 $_ 变量。

虽然 Start-ThreadJob 使用 线程 (运行 空间)来 运行 它的作业,生成的作业对象可以使用标准管理作业 cmdlet,即 Wait-JobReceive-JobRemove-Job.


使用 Start-ThreadJob 优于 Start-Job 的优势:

  • Start-ThreadJob 使用 threads(单独的进程内 PowerShell 运行spaces 通过PowerShell SDK) 用于并发而不是 子进程 Start-Job 使用。基于线程的并发速度更快,资源占用更少。

    • 有关 Start-ThreadJob 带来的性能提升的示例,请参阅
  • 线程作业的输出保留其原始类型

    • 相比之下,在 Start-Job 作业中,输入和输出必须跨越 进程边界 ,需要同一种基于 XML 的序列化和反序列化,即在 PowerShell remoting 中使用,除了少数已知类型外,类型保真度已丢失:请参阅 .

Start-ThreadJob 的唯一(很大程度上是假设的)缺点是崩溃的线程可能会导致整个进程崩溃,但请注意,即使使用 Throw 创建的脚本终止错误也只会终止线程(运行空格)在手边,不是来电者。

简而言之:只有在需要全进程隔离时才使用Start-Job;也就是说,如果您需要确保以下内容:

  • 崩溃的作业不能使调用者崩溃。

  • 作业不应看到加载到调用者会话中的 .NET 类型。

  • 作业不应修改调用者的环境变量(在两种类型的作业中,调用者的环境变量值存在,但在后台作业的情况下,它们是 )。

请注意,在 Start-ThreadJobStart-Job 作业中,这些作业 而不是 根据 :

查看调用者的状态
  • 变量、函数、别名或 PSv5+ 自定义 类 添加到调用者的会话中,交互方式或通过 $PROFILE文件 - 作业 加载 $PROFILE 个文件。

    • 但是,线程作业会看到 .NET 类(类型)加载到调用者的会话中,并且与常规作业不同,它们不仅会看到调用者的环境变量,但也可以修改它们。
  • 在 PowerShell 6- 中,作业的初始当前目录(文件系统位置)与调用者的相同;幸运的是,这在 v7+ 中得到了修复;一旦启动,作业会保留自己的当前位置,更改它不会影响调用者。

您可以像这样测试整个列表。 test-connection 可以采用一系列主机。如果他们中的大多数人都起来了,那将会非常快。如果 ip 已启动,则 ResponseTime 属性 将不为空。

$list = 1..3 -replace '^','192.168.1.'
$result = test-connection $list -asjob -count 1 | receive-job -wait -autoremovejob
$result | where responsetime  # up hosts
$result | where { ! $_.responsetime } # down hosts