首页 > 解决方案 > 多线程 PowerShell 脚本

问题描述

我有一个 PowerShell 脚本,用于检查文件夹中是否存在文件。问题是:这个脚本可以正常工作,但是速度很慢。我必须每天检查 10K Pcs 的统计数据。我想使用Invoke-Command,但我不能使用它,因为并非所有客户端都启用了 WinRM。是否可以在没有 WinRM 的情况下使此脚本成为多线程?

这是我的代码:

function Show-Menu {
    param (
        [string]$Title = 'Debug'
    )
    cls
    Write-Host "================ $Title ================"

    Write-Host "Ziel Clients werden in der Datei C:\temp\srv.txt angegeben!" -ForegroundColor Red
    Write-Host "1: Detailansicht alle Clients" -ForegroundColor Green
    Write-Host "2: Today Crash" -ForegroundColor Green
    Write-Host "3: Detailansich einzelner Client" -ForegroundColor Green
    Write-Host "Q: 'Q' zum beenden."
    Write-Host "Script wird ausgefuehrt als:"
    whoami
}
do {
    Show-Menu
    $input = Read-Host "Nummer angeben"
    switch ($input) {
        '1' {
            cls
            Write-Host "Detailansicht alle Clients" 
            $computers = Get-Content C:\temp\srv.txt

            foreach ($computer in $computers) {

                Write-Host -foregroundcolor "green" "Verarbeite $computer..." 
                if ( ! (Test-Connection $computer -Count 1 -Quiet)) {
                    Write-Host -foregroundcolor "red" "$computer ist offline" 
                    continue
                }

                $path = Test-Path "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*" -Include *dump* 
                Get-Item "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
                If ($path -eq $true ) { Write-Host $computer 'Dumps are present' }

                Else { Write-Host $computer 'Dumps are not present' } 
                pause
            }

        } 
        '2' {
            cls
            Write-Host "Today Crash" 
            $computers = Get-Content C:\temp\srv.txt

            foreach ($computer in $computers) {
                Write-Host -foregroundcolor "green" "Verarbeite $computer..." 
                if ( ! (Test-Connection $computer -Count 1 -Quiet)) {
                    Write-Host -foregroundcolor "red" "$computer ist offline" 
                    continue
                }

                $result = Get-ChildItem -Path "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*" | Where-Object { $_.LastWriteTime -ge (Get-Date).Date }
            }
            $result | Out-GridView
        }
        '3' {
            cls
            Write-Host "Detailansich einzelner Client" 
            $computer = Read-Host -Prompt 'Client angeben'
            $result = Get-ChildItem -Path "\\$computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
            $result | Out-GridView
        }
    }
}
until ($input -eq 'q')

标签: powershell

解决方案


虽然后台作业(通过方式启动Start-Job)确实允许并行运行,但它们在进程中运行,因此资源密集且速度慢;此外,您不能限制它们的使用,即您不能(直接)控制最多允许同时运行多少个子进程

相比之下,线程作业作为进程内线程运行,因此需要更少的资源,并且比基于子进程的后台作业快得多;此外,还支持节流(限制允许同时运行的线程数)

线程作业通过PowerShell [Core] v6+Start-ThreadJob附带的 cmdlet启动,在Windows PowerShell中可以按需安装,例如.Install-Module ThreadJob -Scope CurrentUser

您只需调用Start-ThreadJob而不是Start-Job,并使用标准*-Jobcmdlet 来管理此类线程作业 - 与管理Start-Job-launched 后台作业的方式相同。

在最简单的情况下,您可以简单地等待所有线程完成:

# PowerShell [Core] 6+
# Windows PowerShell with the ThreadJob module installed.
Get-Content C:\temp\srv.txt | ForEach-Object {
  $computer = $_
  Start-ThreadJob { # create a thread job and output an object representing it
    Write-Host -ForegroundColor Green "Processing $using:computer..."
    # ...
    Get-Item "\\$using:computer\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
    # ...
  }
} | Receive-Job -Wait -AutoRemoveJob
  • 注意$using:范围说明符的使用,它是访问调用者$computer值所必需的;此要求同样适用于后台作业、线程作业和远程处理。

  • 默认最多允许5个线程同时运行;您可以使用该-ThrottleLimit参数来修改此值,但请注意,将此值增加到超出硬件可以支持的范围实际上会减慢速度。

  • 不能保证输出排序;也就是说,不能保证输出与指定计算机名称的顺序相对应。


PowerShell 7+中,有一种更简单的方法可以通过参数并行运行线程:ForEach-Object
-Parallel

# PowerShell 7+
Get-Content C:\temp\srv.txt | ForEach-Object -Parallel {
  Write-Host -foregroundcolor Green "Processing $_..."
  # ...
  Get-Item "\\$_\c$\Program Files\Oracle\Runtime\BIN\ifrun60_*"
  # ...
}

上面关于输出排序和限制的评论在这里同样适用,但请注意,计算机名称现在通过管道作为输入提供,因此可以在线程脚本块内使用通常的自动$_变量。


推荐阅读