xen__
xen__

Reputation: 13

Multithread PowerShell script

I have a PowerShell script that checks if a file is present in a folder. The problem is: This script works as it should, but it's very slowly. I must check 10K Pcs for a statistic / day. I want to use Invoke-Command, but I can't use it because not all clients have enabled WinRM. Is it possible to make this script multithread without WinRM?

Here is my code:

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')

Upvotes: 1

Views: 4922

Answers (1)

mklement0
mklement0

Reputation: 440247

While background jobs - started via Start-Job - do permit running in parallel, they run in child processes, and are therefore both resource-intensive and slow; furthermore, you cannot throttle their use, i.e. you cannot (directly) control how many child process at most are permitted to run simultaneously

Thread jobs, by contrast, run as threads in-process and therefore require fewer resources and are much faster than child-process-based background jobs; furthermore, throttling (limiting the number of threads permitted to run simultaneously) is supported.

Thread jobs are started via the Start-ThreadJob cmdlet, which comes with PowerShell [Core] v6+ and in Windows PowerShell can be installed on demand with, e.g., Install-Module ThreadJob -Scope CurrentUser.

You simply call Start-ThreadJob instead of Start-Job, and use the standard *-Job cmdlets to manage such thread jobs - the same way you'd manage a Start-Job-launched background job.

In the simplest case, you can simply wait for all threads to complete:

# 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
  • Note the use of the $using: scope specifier, which is needed to access the caller's $computer value; this requirement applies equally to background jobs, thread jobs, and remoting.

  • By default, up to 5 threads are allowed to run simultaneously; you can use the -ThrottleLimit parameter to modify this value, but note that increasing this value beyond what your hardware can support can actually slow things down.

  • Output sequencing isn't guaranteed; that is, the outputs aren't guaranteed to correspond to the order in which the computer names were specified.


In PowerShell 7+, there's an even easier way to run threads in parallel, via ForEach-Object's
-Parallel
parameter:

# 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_*"
  # ...
}

The comments above regarding output sequencing and throttling apply equally here, but note that the computer names are now provided as input via the pipeline, so the usual automatic $_ variable can be used inside the thread script block.

Upvotes: 3

Related Questions