Copy-item using invoke-async in Powershell - powershell
This article shows how to use Invoke-Async in PowerShell: https://sqljana.wordpress.com/2018/03/16/powershell-sql-server-run-in-parallel-collect-sql-results-with-print-output-from-across-your-sql-farm-fast/
I wish to run in parallel the copy-item cmdlet in PowerShell because the alternative is to use FileSystemObject via Excel and copy one file at a time out of a total of millions of files.
I have cobbled together the following:
.SYNOPSIS
<Brief description>
For examples type:
Get-Help .\<filename>.ps1 -examples
.DESCRIPTION
Copys files from one path to another
.PARAMETER FileList
e.g. C:\path\to\list\of\files\to\copy.txt
.PARAMETER NumCopyThreads
default is 8 (but can be 100 if you want to stress the machine to maximum!)
.EXAMPLE
.\CopyFilesToBackup -filelist C:\path\to\list\of\files\to\copy.txt
.NOTES
#>
[CmdletBinding()]
Param(
[String] $FileList = "C:\temp\copytest.csv",
[int] $NumCopyThreads = 8
)
$filesToCopy = New-Object "System.Collections.Generic.List[fileToCopy]"
$csv = Import-Csv $FileList
foreach($item in $csv)
{
$file = New-Object fileToCopy
$file.SrcFileName = $item.SrcFileName
$file.DestFileName = $item.DestFileName
$filesToCopy.add($file)
}
$sb = [scriptblock] {
param($file)
Copy-item -Path $file.SrcFileName -Destination $file.DestFileName
}
$results = Invoke-Async -Set $filesToCopy -SetParam file -ScriptBlock $sb -Verbose -Measure:$true -ThreadCount 8
$results | Format-Table
Class fileToCopy {
[String]$SrcFileName = ""
[String]$DestFileName = ""
}
the csv input for which looks like this:
SrcFileName,DestFileName
C:\Temp\dummy-data\101438\101438-0154723869.zip,\\backupserver\Project Archives\101438\0154723869.zip
C:\Temp\dummy-data\101438\101438-0165498273.xlsx,\\backupserver\Project Archives\101438\0165498273.xlsx
What am I missing to get this working, because when I run .\CopyFiles.ps1 -FileList C:\Temp\test.csv nothing happens. The files exist in the source path, but the file objects aren't being pulled from the -Set collection. (Unless I have misunderstood how the collection is used?)
No, I can't use robocopy to do this because there are millions of files which resolve to different paths depending upon their original location.
I have no explanation for your symptom based on the code in your question (see bottom section), but I suggest basing your solution on the (now) standard Start-ThreadJob cmdlet (comes with PowerShell Core; in Windows PowerShell, install it with Install-Module ThreadJob -Scope CurrentUser, for instance[1]):
Such a solution is more efficient than use of the third-party Invoke-Async function, which as of this writing is flawed in that it waits for jobs to finish in a tight loop, which creates unnecessary processing overhead.
Start-ThreadJob jobs are a lightweight, thread-based alternative to the process-based Start-Job background jobs, yet they integrate with the standard job-management cmdlets, such as Wait-Job and Receive-Job.
Here's a self-contained example based on your code that demonstrates its use:
Note: Whether you use Start-ThreadJob or Invoke-Async, you won't be able to explicit reference custom classes such as [fileToCopy] in the script block that runs in separate threads (runspaces; see bottom section), so the solution below simply uses [pscustomobject] instances with the properties of interest for simplicity and brevity.
# Create sample CSV file with 10 rows.
$FileList = Join-Path ([IO.Path]::GetTempPath()) "tmp.$PID.csv"
#'
Foo,SrcFileName,DestFileName,Bar
1,c:\tmp\a,\\server\share\a,baz
2,c:\tmp\b,\\server\share\b,baz
3,c:\tmp\c,\\server\share\c,baz
4,c:\tmp\d,\\server\share\d,baz
5,c:\tmp\e,\\server\share\e,baz
6,c:\tmp\f,\\server\share\f,baz
7,c:\tmp\g,\\server\share\g,baz
8,c:\tmp\h,\\server\share\h,baz
9,c:\tmp\i,\\server\share\i,baz
10,c:\tmp\j,\\server\share\j,baz
'# | Set-Content $FileList
# How many threads at most to run concurrently.
$NumCopyThreads = 8
Write-Host 'Creating jobs...'
$dtStart = [datetime]::UtcNow
# Import the CSV data and transform it to [pscustomobject] instances
# with only .SrcFileName and .DestFileName properties - they take
# the place of your original [fileToCopy] instances.
$jobs = Import-Csv $FileList | Select-Object SrcFileName, DestFileName |
ForEach-Object {
# Start the thread job for the file pair at hand.
Start-ThreadJob -ThrottleLimit $NumCopyThreads -ArgumentList $_ {
param($f)
$simulatedRuntimeMs = 2000 # How long each job (thread) should run for.
# Delay output for a random period.
$randomSleepPeriodMs = Get-Random -Minimum 100 -Maximum $simulatedRuntimeMs
Start-Sleep -Milliseconds $randomSleepPeriodMs
# Produce output.
"Copied $($f.SrcFileName) to $($f.DestFileName)"
# Wait for the remainder of the simulated runtime.
Start-Sleep -Milliseconds ($simulatedRuntimeMs - $randomSleepPeriodMs)
}
}
Write-Host "Waiting for $($jobs.Count) jobs to complete..."
# Synchronously wait for all jobs (threads) to finish and output their results
# *as they become available*, then remove the jobs.
# NOTE: Output will typically NOT be in input order.
Receive-Job -Job $jobs -Wait -AutoRemoveJob
Write-Host "Total time lapsed: $([datetime]::UtcNow - $dtStart)"
# Clean up the temp. file
Remove-Item $FileList
The above yields something like:
Creating jobs...
Waiting for 10 jobs to complete...
Copied c:\tmp\b to \\server\share\b
Copied c:\tmp\g to \\server\share\g
Copied c:\tmp\d to \\server\share\d
Copied c:\tmp\f to \\server\share\f
Copied c:\tmp\e to \\server\share\e
Copied c:\tmp\h to \\server\share\h
Copied c:\tmp\c to \\server\share\c
Copied c:\tmp\a to \\server\share\a
Copied c:\tmp\j to \\server\share\j
Copied c:\tmp\i to \\server\share\i
Total time lapsed: 00:00:05.1961541
Note that the output received does not reflect the input order, and that the overall runtime is roughly 2 times the per-thread runtime of 2 seconds (plus overhead), because 2 "batches" have to be run due to the input count being 10, whereas only 8 threads were made available.
If you upped the thread count to 10 or more (50 is the default), the overall runtime would drop to 2 seconds plus overhead, because all jobs then run concurrently.
Caveat: The above numbers stem from running in PowerShell Core, version on Microsoft Windows 10 Pro (64-bit; Version 1903), using version 2.0.1 of the ThreadJob module.
Inexplicably, the same code is much slower in Windows PowerShell, v5.1.18362.145.
However, for performance and memory consumption it is better to use batching (chunking) in your case, i.e, to process multiple file pairs per thread.
The following solution demonstrates this approach; tweak $chunkSize to find a batch size that works for you.
# Create sample CSV file with 10 rows.
$FileList = Join-Path ([IO.Path]::GetTempPath()) "tmp.$PID.csv"
#'
Foo,SrcFileName,DestFileName,Bar
1,c:\tmp\a,\\server\share\a,baz
2,c:\tmp\b,\\server\share\b,baz
3,c:\tmp\c,\\server\share\c,baz
4,c:\tmp\d,\\server\share\d,baz
5,c:\tmp\e,\\server\share\e,baz
6,c:\tmp\f,\\server\share\f,baz
7,c:\tmp\g,\\server\share\g,baz
8,c:\tmp\h,\\server\share\h,baz
9,c:\tmp\i,\\server\share\i,baz
10,c:\tmp\j,\\server\share\j,baz
'# | Set-Content $FileList
# How many threads at most to run concurrently.
$NumCopyThreads = 8
# How many files to process per thread
$chunkSize = 3
# The script block to run in each thread, which now receives a
# $chunkSize-sized *array* of file pairs.
$jobScriptBlock = {
param([pscustomobject[]] $filePairs)
$simulatedRuntimeMs = 2000 # How long each job (thread) should run for.
# Delay output for a random period.
$randomSleepPeriodMs = Get-Random -Minimum 100 -Maximum $simulatedRuntimeMs
Start-Sleep -Milliseconds $randomSleepPeriodMs
# Produce output for each pair.
foreach ($filePair in $filePairs) {
"Copied $($filePair.SrcFileName) to $($filePair.DestFileName)"
}
# Wait for the remainder of the simulated runtime.
Start-Sleep -Milliseconds ($simulatedRuntimeMs - $randomSleepPeriodMs)
}
Write-Host 'Creating jobs...'
$dtStart = [datetime]::UtcNow
$jobs = & {
# Process the input objects in chunks.
$i = 0
$chunk = [pscustomobject[]]::new($chunkSize)
Import-Csv $FileList | Select-Object SrcFileName, DestFileName | ForEach-Object {
$chunk[$i % $chunkSize] = $_
if (++$i % $chunkSize -ne 0) { return }
# Note the need to wrap $chunk in a single-element helper array (, $chunk)
# to ensure that it is passed *as a whole* to the script block.
Start-ThreadJob -ThrottleLimit $NumCopyThreads -ArgumentList (, $chunk) -ScriptBlock $jobScriptBlock
$chunk = [pscustomobject[]]::new($chunkSize) # we must create a new array
}
# Process any remaining objects.
# Note: $chunk -ne $null returns those elements in $chunk, if any, that are non-null
if ($remainingChunk = $chunk -ne $null) {
Start-ThreadJob -ThrottleLimit $NumCopyThreads -ArgumentList (, $remainingChunk) -ScriptBlock $jobScriptBlock
}
}
Write-Host "Waiting for $($jobs.Count) jobs to complete..."
# Synchronously wait for all jobs (threads) to finish and output their results
# *as they become available*, then remove the jobs.
# NOTE: Output will typically NOT be in input order.
Receive-Job -Job $jobs -Wait -AutoRemoveJob
Write-Host "Total time lapsed: $([datetime]::UtcNow - $dtStart)"
# Clean up the temp. file
Remove-Item $FileList
While the output is effectively the same, note how only 4 jobs were created this time, each of which processed (up to) $chunkSize (3) file pairs.
As for what you tried:
The screen shot you show suggests that the problem is that your custom class, [fileToCopy], isn't visible to the script block run by Invoke-Async.
Since Invoke-Async invokes the script block via the PowerShell SDK in separate runspaces that know nothing about the caller's state, it is to be expected that these runspaces don't know your class (this equally applies to Start-ThreadJob).
However, it is unclear why that is a problem in your code, because your script block doesn't make an explicit reference to you class: your script-block parameter $file is not type-constrained (it is implicitly [object]-typed).
Therefore, simply accessing the properties of your custom-class instance inside the script block should work, and indeed does in my tests on Windows PowerShell v5.1.18362.145 on Microsoft Windows 10 Pro (64-bit; Version 1903).
However, if your real script-block code were to explicitly reference custom class [fileToCopy] - such as by defining the parameter as param([fileToToCopy] $file) - you would see the symptom.
[1] In Windows PowerShell v3 and v4, which do not come with the PowerShellGet module, Install-Module isn't available by default. However, the module can be installed on demand, as described in Installing PowerShellGet.
Related
Test-Path timeout for PowerShell
I'm trying to routinely check the presence of particular strings in text files on hundreds of computers on our domain. foreach ($computer in $computers) { $hostname = $computer.DNSHostName if (Test-Connection $hostname -Count 2 -Quiet) { $FilePath = "\\" + $hostname + "c$\SomeDirectory\SomeFile.txt" if (Test-Path -Path $FilePath) { # Check for string } } } For the most part, the pattern of Test-Connection and then Test-Path is effective and fast. There are certain computers, however, that ping successfully but Test-Path takes around 60 seconds to resolve to FALSE. I'm not sure why, but it may be a domain trust issue. For situations like this, I would like to have a timeout for Test-Path that defaults to FALSE if it takes more than 2 seconds. Unfortunately the solution in a related thread (How can I wrap this Powershell cmdlet into a timeout function?) does not apply to my situation. The proposed do-while loop gets hung up in the code block. I've been experimenting with Jobs but it appears even this won't force quit the Test-Path command: Start-Job -ScriptBlock {param($Path) Test-Path $Path} -ArgumentList $Path | Wait-Job -Timeout 2 | Remove-Job -Force The job continues to hang in the background. Is this the cleanest way I can achieve my requirements above? Is there a better way to timeout Test-Path so the script doesn't hang besides spawning asynchronous activities? Many thanks.
Wrap your code in a [powershell] object and call BeginInvoke() to execute it asynchronously, then use the associated WaitHandle to wait for it to complete only for a set amount of time. $sleepDuration = Get-Random 2,3 $ps = [powershell]::Create().AddScript("Start-Sleep -Seconds $sleepDuration; 'Done!'") # execute it asynchronously $handle = $ps.BeginInvoke() # Wait 2500 milliseconds for it to finish if(-not $handle.AsyncWaitHandle.WaitOne(2500)){ throw "timed out" return } # WaitOne() returned $true, let's fetch the result $result = $ps.EndInvoke($handle) return $result In the example above, we randomly sleep for either 2 or 3 seconds, but set a 2 and a half second timeout - try running it a couple of times to see the effect :)
Wait for all child processes to finish
Currently I am trying to run a script multiple times and let the parent wait for all the child processes to finish. The creation of the children is as follows: # Spawn balance load processes $command = "-i $ScriptPath\balanceLoaders\%BALANCE_DIR% -o $outputDirectory -c %BALANCE_DIR%" #$command = "$ScriptPath\convertFiles.ps1 -i $ScriptPath\balanceLoaders\%BALANCE_DIR% -o $outputDirectory -c %BALANCE_DIR%" for ([int]$i = 0; $i -lt $b; $i++) { #$proc = Start-Process powershell.exe -WindowStyle Hidden -Argument $command.Replace('%BALANCE_DIR%', $i) -PassThru $proc = Start-Process $ScriptPath\convertfiles.exe -WindowStyle Hidden -Verb Runas -ArgumentList $command.Replace('%BALANCE_DIR%', $i) -PassThru } The children are spawned dynamically. Currently I can only track the last spawned child with $proc. However, I want to track them all. Currently using $proc.WaitForExit() to wait for the child.
Collect all processes in an array and run an idle loop until all of them have exited. $proc = for ($i = 0; $i -lt $b; $i++) { Start-Process ... -PassThru } while (($proc | Select-Object -Expand HasExited) -contains $false) { Start-Sleep -Milliseconds 100 } If you have PowerShell v3 or newer you can simplify the loop to something like this: while ($proc.HasExited -contains $false) { Start-Sleep -Milliseconds 100 } because PowerShell v3 introduced a new feature called member enumeration that allows accessing properties or methods of array elements via the array object.
Ansgar Wiecher's helpful answer contains good pointers and a working solution, but if you simply want to wait for all spawned processes to terminate, use of Wait-Process is simpler. That said, if you want to check exit codes as they become available, before all processes have exited, a loop that sleeps periodically (Start-Sleep), as in Ansgar's answer, is necessary. The following solution demonstrates the synchronously-wait-for-all technique with Wait-Process with a simplified example that creates 3 instances of Notepad and waits for all of them to terminate: # Create all processes and store objects representing them in array $procs. $b = 3 # number of processes to create $procs = foreach ($i in 0..($b-1)) { Start-Process -PassThru Notepad } # Wait for all processes to exit. # (Close the Notepad windows manually or, if no Notepad windows were previously # open, use `Stop-Process -Name Notepad` from another session to forcefully close them.) # Use Wait-Process -Timeout <secs> to limit the max. period of time to wait. $procs | Wait-Process # Get the exit codes. $exitCodes = $procs.ExitCode # PSv3+; v2: $procs | Select-Object -ExpandProperty ExitCode Or, as a single pipeline: # Use $_ inside { ... } to refer to the current iteration value. # Use $procs.ExitCode to get the exit codes. 0..($b-1) | ForEach-Object -ov procs { Start-Process -PassThru Notepad } | Wait-Process Note the use of -ov - short for common parameter -OutVariable, which collects the process objects output by the ForEach-Object cmdlet's script block invocations.
Why order is not maintained while reading a text file from Powershell?
Below is my Powershell script: $server_file = 'serverlist.txt' $servers = #{} Get-Content $server_file | foreach-object -process {$current = $_.split(":"); $servers.add($current[0].trim(), $current[1].trim())} foreach($server in $servers.keys){ write-host "Deploying $service on $server..." -foregroundcolor green } My serverlist.txt looks like this: DRAKE : x64 SDT: x64 IMPERIUS : x64 Vwebservice2012 : x64 Every time I run this script, I get IMPERIUS as my server name. I would like loop through the servers in the order they are written in serverlist.txt. Am I missing anything in Get-Content call?
Don't store servers in a temporary variable. The iteration order of hashtables (#{}) is not guaranteed by the .NET framework. Avoid using them if you want to maintain input order. Simply do: $server_file = 'serverlist.txt' Get-Content $server_file | ForEach-Object { $current = $_.split(":") $server = $current[0].trim() $architecture = $current[1].trim() Write-Host "Deploying $service on $server..." -ForegroundColor Green } Note: Even if it probably won't make much of a difference in this particular case, in general you always should explicitly define the file encoding when you use Get-Content to avoid garbled data. Get-Content does not have sophisticated auto-detection for file encodings, and the default it uses can always be wrong for your input file.
Displaying only changes when using get-content -wait
I created the following function which I wanted to use for a very simple CTI solution I have to use at work. This CTI process is writing all received calls to a text logile. This function starts a new powershell Job and checks if the .log has been saved during the last 2 seconds and gets the last 4 lines of the log (receiving calls always creates 4 new lines). During the job update I'm using regex to find the line with the phonenumber and time and append this to a richtextbox in a form. In theory this works exactly as I want it to work. If I manually add new lines and save the file, it's always showing the timecode and phone number. In the field however, this doesn't work as the CTI process is opening the file and doesn't save the it unless the process is shutting down. I know that I can use get-content -wait to display new lines. I already tested this in the console and it's displaying new lines as soon as the .log is updated from the CTI process. What I don't know is how to rewrite the function to work with that, displaying only new lines and not all the old stuff when first running the script. I need to keep it in the job for a responsive form. Another thing is, that the computer running the form, doesn't have that much power. I don't know if get-content -wait could cause high memory usage after several hours. Maybe there are also some alternative solutions for a case like that available? function start-CTIlogMonitoring { Param ($CTIlogPath) Write-Debug "startCTI monitor" Add-JobTracker -Name "CTILogger" -ArgumentList $CTIlogPath ` -JobScript { #-------------------------------------------------- #TODO: Set a script block #Important: Do not access form controls from this script block. Param ($CTIlogPath) #Pass any arguments using the ArgumentList parameter while ($true) { $diff = ((Get-ChildItem $CTIlogPath).LastWriteTime - (get-date)).totalseconds Write-Debug "diff $diff" if ($diff -gt -2) { Write-Debug "cti log DIFF detected" Get-Content -Path "$CTIlogPath" -Tail 4 Start-Sleep -Seconds 1 } } #-------------------------------------------------- }` -CompletedScript { Param ($Job) }` -UpdateScript { Param ($Job) $results = Receive-Job -Job $Job | Out-String # Out-String required to get new lines in RTB #get the stuff from results and make it more appearing to read for humans if ($results -match '(Ein, E, (\d+))') { Write-debug "Incoming Call:" $time = ([regex]'[0-9]{2}:[0-9]{2}:[0-9]{2}').Match($results) $phoneNumber = ([regex]'Ein, E, (\d+)').Split($results)[1] Write-Debug "$time ----> $phoneNumber" if ($richtextboxCTIlogs.lines.count -eq 0) { $richtextboxCTIlogs.AppendText("$time ----> $phoneNumber") } else { $richtextboxCTIlogs.AppendText("`n$time ----> $phoneNumber") } $richtextboxCTIlogs.SelectionStart = $richtextboxCTIlogs.TextLength; $richtextboxCTIlogs.ScrollToCaret() } <#else { Write-Debug "found nothin" }#> } }
How to run a command against multiple servers simultaneously in Powershell
I am looking for a way to restart three services on multiple servers simultaneously. I know how to restart services against a list of servers by using a loop but as I have many servers it would take a long time to wait for each service on each server to restart in a sequential order. Is there a way to send restart service command to all servers at once instead of waiting for each server?
You could try to work with jobs. Jobs are run in the background and you have to retrieve them with Get-Job to see their status. Please read the information to Powershell jobs on these two sites: http://msdn.microsoft.com/en-us/library/dd878288%28v=vs.85%29.aspx http://technet.microsoft.com/de-DE/library/hh847783.aspx Your code would look something like this: $servernames | ForEach-Object {Start-Job -Name "Job-$_" -Scriptblock {"Enter your code here -Computername $_"}} This will create a background job for each servername. As already mentioned you can see the status using the cmdlet Get-Job. To get the result use the cmdlet Receive-Job.
you can use the invoke-command cmdlet invoke-command -computername computer1,computer2,computer3 {restart-service servicename}
I use and improove a multi-thread Function, you can use it like : $Script = { param($Computername) restart-service servicename -Computername $Computername } #('Srv1','Srv2') | Run-Parallel -ScriptBlock $Script include this code in your script function Run-Parallel { <# .Synopsis This is a quick and open-ended script multi-threader searcher http://www.get-blog.com/?p=189#comment-28834 Improove by Alban LOPEZ 2016 .Description This script will allow any general, external script to be multithreaded by providing a single argument to that script and opening it in a seperate thread. It works as a filter in the pipeline, or as a standalone script. It will read the argument either from the pipeline or from a filename provided. It will send the results of the child script down the pipeline, so it is best to use a script that returns some sort of object. .PARAMETER ScriptBlock This is where you provide the PowerShell ScriptBlock that you want to multithread. .PARAMETER ItemObj The ItemObj represents the arguments that are provided to the child script. This is an open ended argument and can take a single object from the pipeline, an array, a collection, or a file name. The multithreading script does it's best to find out which you have provided and handle it as such. If you would like to provide a file, then the file is read with one object on each line and will be provided as is to the script you are running as a string. If this is not desired, then use an array. .PARAMETER InputParam This allows you to specify the parameter for which your input objects are to be evaluated. As an example, if you were to provide a computer name to the Get-Process cmdlet as just an argument, it would attempt to find all processes where the name was the provided computername and fail. You need to specify that the parameter that you are providing is the "ComputerName". .PARAMETER AddParam This allows you to specify additional parameters to the running command. For instance, if you are trying to find the status of the "BITS" service on all servers in your list, you will need to specify the "Name" parameter. This command takes a hash pair formatted as follows: #{"key" = "Value"} #{"key1" = "Value"; "key2" = 321; "key3" = 1..9} .PARAMETER AddSwitch This allows you to add additional switches to the command you are running. For instance, you may want to include "RequiredServices" to the "Get-Service" cmdlet. This parameter will take a single string, or an aray of strings as follows: "RequiredServices" #("RequiredServices", "DependentServices") .PARAMETER MaxThreads This is the maximum number of threads to run at any given time. If ressources are too congested try lowering this number. The default value is 20. .PARAMETER SleepTimer_ms This is the time between cycles of the child process detection cycle. The default value is 200ms. If CPU utilization is high then you can consider increasing this delay. If the child script takes a long time to run, then you might increase this value to around 1000 (or 1 second in the detection cycle). .PARAMETER TimeOutGlobal this is the TimeOut in second for listen the last thread, after this timeOut All thread are closed, only each other are returned .PARAMETER TimeOutThread this is the TimeOut in second for each thread, the thread are aborted at this time .PARAMETER PSModules List of PSModule name to include for use in ScriptBlock .PARAMETER PSSapins List of PSSapin name to include for use in ScriptBlock .EXAMPLE 1..20 | Run-Parallel -ScriptBlock {param($i) Start-Sleep $i; "> $i sec <"} -TimeOutGlobal 15 -TimeOutThread 5 .EXAMPLE Both of these will execute the scriptBlock and provide each of the server names in AllServers.txt while providing the results to GridView. The results will be the output of the child script. gc AllServers.txt | Run-Parallel $ScriptBlock_GetTSUsers -MaxThreads $findOut_AD.ActiveDirectory.Servers.count -PSModules 'PSTerminalServices' | out-gridview #> Param( [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] $ItemObj, [ScriptBlock]$ScriptBlock = $null, $InputParam = $Null, [HashTable] $AddParam = #{}, [Array] $AddSwitch = #(), $MaxThreads = 20, $SleepTimer_ms = 100, $TimeOutGlobal = 300, $TimeOutThread = 100, [string[]]$PSSapins = $null, [string[]]$PSModules = $null, $Modedebug = $true ) Begin{ $ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault() ForEach ($Snapin in $PSSapins){ [void]$ISS.ImportPSSnapIn($Snapin, [ref]$null) } ForEach ($Module in $PSModules){ [void]$ISS.ImportPSModule($Module) } $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host) $RunspacePool.CleanupInterval=1000 $RunspacePool.Open() $Jobs = #() } Process{ #ForEach ($Object in $ItemObj){ if ($ItemObj){ Write-Host $ItemObj -ForegroundColor Yellow $PowershellThread = [powershell]::Create().AddScript($ScriptBlock) If ($InputParam -ne $Null){ $PowershellThread.AddParameter($InputParam, $ItemObj.ToString()) | out-null }Else{ $PowershellThread.AddArgument($ItemObj.ToString()) | out-null } ForEach($Key in $AddParam.Keys){ $PowershellThread.AddParameter($Key, $AddParam.$key) | out-null } ForEach($Switch in $AddSwitch){ $PowershellThread.AddParameter($Switch) | out-null } $PowershellThread.RunspacePool = $RunspacePool $Handle = $PowershellThread.BeginInvoke() $Job = [pscustomobject][ordered]#{ Handle = $Handle Thread = $PowershellThread object = $ItemObj.ToString() Started = Get-Date } $Jobs += $Job } #} } End{ $GlobalStartTime = Get-Date $continue = $true While (#($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0 -and $continue) { ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){ $out = $Job.Thread.EndInvoke($Job.Handle) $out # return vers la sortie srandard #Write-Host $out -ForegroundColor green $Job.Thread.Dispose() | Out-Null $Job.Thread = $Null $Job.Handle = $Null } foreach ($InProgress in $($Jobs | Where-Object {$_.Handle})) { if ($TimeOutGlobal -and (($(Get-Date) - $GlobalStartTime).totalseconds -gt $TimeOutGlobal)){ $Continue = $false #Write-Host $InProgress -ForegroundColor magenta } if (!$Continue -or ($TimeOutThread -and (($(Get-Date) - $InProgress.Started).totalseconds -gt $TimeOutThread))) { $InProgress.thread.Stop() | Out-Null $InProgress.thread.Dispose() | Out-Null $InProgress.Thread = $Null $InProgress.Handle = $Null #Write-Host $InProgress -ForegroundColor red } } Start-Sleep -Milliseconds $SleepTimer_ms } $RunspacePool.Close() | Out-Null $RunspacePool.Dispose() | Out-Null } }