RDS User logoff Script Slow - powershell

With the help of the several online articles I was able to compile a powershell script that logs off all users for each of my RD Session hosts. I wanted something to be really gentle on logging off users and it writing profiles back to their roaming profile location on the storage system. However, this is too gentle and takes around four hours to complete with the amount of users and RDS servers I have.
This script is designed to set each RDS server drain but allow redirection if a server is available so the thought around this was within the first 15 minutes I would have the first few servers ready for users to log into.
All of this works but I would like like to see if there are any suggestions on speeding this up a little.
Here is the loop that goes through each server and logs users out and then sets the server logon mode to enabled:
ForEach ($rdsserver in $rdsservers){
try {
query user /server:$rdsserver 2>&1 | select -skip 1 | ? {($_ -split "\s+")[-5]} | % {logoff ($_ -split "\s+")[-6] /server:$rdsserver /V}
Write-Host "Giving the RDS Server time"
Write-Progress "Pausing Script" -status "Giving $rdsserver time to settle" -perc (5/(5/100))
Start-Sleep -Seconds 5
$RDSH=Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $rdsserver -Authentication PacketPrivacy -Impersonation Impersonate
$RDSH.SessionBrokerDrainMode=0
$RDSH.put() > $null
Write-Host "$rdsserver is set to:"
switch ($RDSH.SessionBrokerDrainMode) {
0 {"Allow all connections."}
1 {"Allow incoming reconnections but until reboot prohibit new connections."}
2 {"Allow incoming reconnections but prohibit new connections."}
default {"The user logon state cannot be determined."}
}
}
catch {}
}

Not sure how many Servers you have but if its less than 50 or so you can do this in parallel with PSJobs. You'll have to wrap your code in a scriptblock, launch each server as a separate job, then wait for them to complete and retrieve any data returned. You won't be able to use Write-Host when doing this but I've swapped those to Out-Files. I also didn't parse out your code for collecting your list of servers but I'm going to assume that works and you can have it return a formatted list to a variable $rdsservers. You'll probably also want to modify the messages a bit so you can tell which server is which in the log file, or do different logs for each server. If you want anything other than the names of jobs to hit the console you'll have to output it with Write-Output or a return statement.
$SB = {
param($rdsserver)
Start-Sleep -Seconds 5
$RDSH=Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $rdsserver -Authentication PacketPrivacy -Impersonation Impersonate
$RDSH.SessionBrokerDrainMode=0
$RDSH.put() > $null
"$rdsserver is set to:" | out-file $LogPath #Set this to whatever you want
switch ($RDSH.SessionBrokerDrainMode) {
0 {"Allow all connections." | out-file $LogPath}
1 {"Allow incoming reconnections but until reboot prohibit new connections." | out-file $LogPath}
2 {"Allow incoming reconnections but prohibit new connections." | out-file $LogPath}
default {"The user logon state cannot be determined." | out-file $LogPath}
}
foreach ($server in $rdsservers){
Start-Job -Scriptblock -ArgumentList $server
}
Get-Job | Wait-Job | Receive-Job
The foreach loop launches the jobs and then the last line waits for all of them to complete before getting any data that was output. You can also set a timeout on the wait if there is a chance your script never completes. If you've got a ton of boxes you may want to look into runspaces over jobs as they have better performance but take more work to use. This Link can help you out if you decide to go that way. I don't have an RDS deployment at the moment to test on so if you get any errors or have trouble getting it to work just post a comment and I'll see what I can do.

I have something ready for testing but it may break fantastically. You wizards out there may look at this and laugh. If i did this wrong please let me know.
$Serverperbatch = 2
$job = 0
$job = $Serverperbatch - 1
$batch = 1
While ($job -lt $rdsservers.count) {
$ServerBatch = $rdsservers[$job .. $job]
$jobname = "batch$batch"
Start-job -Name $jobname -ScriptBlock {
param ([string[]]$rdsservers)
Foreach ($rdsserver in $rdsservers) {
try {
query user /server:$rdsserver 2>&1 | select -skip 1 | ? {($_ -split "\s+")[-5]} | % {logoff ($_ -split "\s+")[-6] /server:$rdsserver /V}
$RDSH=Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $rdsserver -Authentication PacketPrivacy -Impersonation Impersonate
$RDSH.SessionBrokerDrainMode=0
$RDSH.put() > $null
}
catch {}
} -ArgumentList (.$serverbatch)
$batch += 1
$Job = $job + 1
$job += $serverperbatch
If ($Job -gt $rdsservers.Count) {$Job = $rdsservers.Count}
If ($Job -gt $rdsservers.Count) {$Job = $rdsservers.Count}
}
}
Get-Job | Wait-Job | Receive-Job

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

Start-Sleep pauses entire function

I have a PowerShell script with a function to pull LAPS information when someone enters a computer name. What I'd like is to clear the output after 60 seconds so someone doesn't accidentally leave a password on their screen.
Seems as though no matter where the sleep is in the function (or even after) the script is paused for the 60 seconds, then displays the information and it never clears.
The part of the script that waits and clears:
Start-Sleep -s 60
$WPFOutputbox.Clear()
(look for my #TRIED HERE comments below)
function GETLAPS {
Param ($computername = $WPFComputer.Text)
try {
$LAPSComputer = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty computername
$LAPSDistinguished = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty distinguishedname
$LAPSPassword = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty Password
$LAPSExpire = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty Expirationtimestamp
} catch {
$ErrorMessage = $_.Exception.Message
}
if ($ErrorMessage -eq $null) {
$WPFOutputBox.Foreground = "Blue"
$WPFOutputBox.Text = "Local Admin Information for: $LAPSComputer
Current: $LAPSPassword
Exipiration: $LAPSExpire
SCREEN WILL CLEAR AFTER 60 SECONDS"
#TRIED HERE
} else {
$WPFOutputBox.Foreground = "Red"
$WPFOutputBox.Text = $ErrorMessage
}
#TRIED HERE
}
Instead the script will wait 60 seconds to show information, and never clear the screen.
Unless you create a separate thread, I would strongly advise against using the Start-Sleep cmdlet in Windows Forms or Windows Presentation Foundation as it will obviously stall the User Interface.
Instead, I would recommend to use a (Windows.Forms.Timer) event.
For this, I wrote a small function to delay a command:
Delay-Command
Function Delay-Command([ScriptBlock]$Command, [Int]$Interval = 100, [String]$Id = "$Command") {
If ($Timers -isnot "HashTable") {$Global:Timers = #{}}
$Timer = $Timers[$Id]
If (!$Timer) {
$Timer = New-Object Windows.Forms.Timer
$Timer.Add_Tick({$This.Stop()})
$Timer.Add_Tick($Command)
$Global:Timers[$Id] = $Timer
}
$Timer.Stop()
$Timer.Interval = $Interval
If ($Interval -gt 0) {$Timer.Start()}
}; Set-Alias Delay Delay-Command
Example
Delay {$WpFOutputBox.Text = ''} 60000
Parameters
-Command
The command to be delayed.
-Interval
The time to wait before executing the command.
If -Interval is set to 0 or less, the command (with the related Id) will be reset.
The default is 100 (a minor time lapse to give other events the possibility to kick off).
-Id
The Id of the delayed command. Multiple commands which different Id's can be executed simultaneously. If the same Id is used for a command that is not yet executed, the interval timer will be reset (or canceled when set to 0).
The default Id in the command string.
Call the function, sleep 60 seconds, then clear it. Do not put your sleep inside the function, put it where you call the function. Something like:
$WPFGetLAPSButton.add_click({
GETLAPS
# If no error, wait 60 seconds and clear the text box
If($WPFOutputBox.Foreground -eq "Blue"){
Start-Sleep 60
$WPFOutputBox.Text = ''
}
})
Pretty sure that'll do what you're looking for.

PowerShell Job throttle blocking the server

I'm experiencing a strange issue with our script server being overloaded and running out of resources. We have a script that copies data from one location to another, this is defined in a large input file that contains over 200 lines of text in the format 'Source path, Destination path'.
We are now in the process of trying to throttle the maximum jobs we kick of at once and I think it's working fine. But for some reason or another we're still running out of resources on the server when the input file contains over 94 lines. This became apparent after some testing.
We tried to upgrade our Windows 2008 R2 server with PowerShell 4.0 to 4 processors and 8 GB of RAM, but no luck. So I assume my throttling isn't working as designed.
Error code:
Insufficient system resources exist to complete the requested service.
The code:
$MaxThreads = 4
$FunctionFeed = Import-Csv -Path $File -Delimiter ',' -Header 'Source', 'Destination'
$Jobs=#()
Function Wait-MaxRunningJobs {
Param (
$Name,
[Int]$MaxThreads
)
Process {
$Running = #($Name | where State -eq Running)
while ($Running.Count -ge $MaxThreads) {
$Finished = Wait-Job -Job $Name -Any
$Running = #($Name | where State -eq Running)
}
}
}
$ScriptBlock = {
Try {
Robocopy.exe $Using:Line.Source $Using:Line.Destination $Using:Line.File /MIR /Z /R:3 /W:15 /NP /MT:8 | Out-File $Using:LogFile
[PSCustomObject]#{
Source = if ($Using:Line.Source) {$Using:Line.Source} else {'NA'}
Target = if ($Using:Line.Destination) {$Using:Line.Destination} else {'NA'}
}
}
Catch {
"Robocopy | ERROR: $($Error[0].Exception.Message)" |
Out-File -LiteralPath $Using:LogFile
throw $($Error[0].Exception.Message)
}
}
ForEach ($Line in $FunctionFeed) {
$LogParams = #{
LogFolder = $LogFolder
Name = $Line.Destination + '.log'
Date = 'ScriptStartTime'
Unique = $True
}
$LogFile = New-LogFileNameHC #LogParams
' ' >> $LogFile # Avoid not being able to write to log
$Jobs += Start-Job -Name RoboCopy -ScriptBlock $ScriptBlock
Wait-MaxRunningJobs -Name $Jobs -MaxThreads $MaxThreads
}
if ($Jobs) {
Wait-Job -Job $Jobs
$JobResults = $Jobs | Receive-Job
}
Am I missing something here? Thank you for your help.
You're using background jobs, which actually run in remote sessions on the local machine. Remote sessions are intentionally resource restricted, according to settings set in the session configuration. You can check the current settings using
Get-PSSessionConfiguration
And adjust the settings to increase the resources available to the sessions with
Set-PSSessionConfiguration
You may need to do some testing to determine exactly what resource limit you're hitting, and what adjustments need to be made for this particular application to work.
Fixed the problem by enlarging the MaxMemoryPerShellMB for remote sessions from 1GB to 2 GB as described here. Keep in mind that Start-Job is using a remote PowerShell session as mjolinor already indicated, so this variable is applicable to PowerShell jobs.
Solution:
# 'System.OutOfMemoryException error message' when running Robocopy and over 94 PowerShell-Jobs:
Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB # Default 1024
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048
# Set PowerShell plugins memory from 1 GB to 2 GB
Get-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB # Default 1024
Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 2048
Restart-Service winrm

Powershell Wait for service to be stopped or started

I have searched both this forum and through google and can't find what I need.
I have a quite large script and I'm looking for some code that will check if the service is started or stopped before proceeding to the next step.
The function it self need to loop untill it's either stopped or started (Going to have a function for Stopped and one for Started).
In total 4 services which almost have the same name, so Service Bus * can be used as a wildcard.
I couldn't get the 'count' strategy, that Micky posted, to work, so here is how i solved it:
I created a function, that takes a searchString (this could be "Service Bus *") and the status that i expect the services should reach.
function WaitUntilServices($searchString, $status)
{
# Get all services where DisplayName matches $searchString and loop through each of them.
foreach($service in (Get-Service -DisplayName $searchString))
{
# Wait for the service to reach the $status or a maximum of 30 seconds
$service.WaitForStatus($status, '00:00:30')
}
}
The function can now be called with
WaitUntilServices "Service Bus *" "Stopped"
or
WaitUntilServices "Service Bus *" "Running"
If the timeout period is reached, a not so graceful exception is thrown:
Exception calling "WaitForStatus" with "2" argument(s): "Time out has expired and the operation has not been completed."
In addition to the answer of mgarde this one liner might be useful if you just want to wait for a single service (also inspired by a post from Shay Levy):
(Get-Service SomeInterestingService).WaitForStatus('Running')
The following will loop and verify the status of the given services until the number of services with the "Running" state is equal to zero (hence they are stopped), so you can use this if you are waiting for services to Stop.
I've added a $MaxRepeat variable, which will prevent this from running for ever. It will run 20 times max as defined.
$services = "Service Bus *"
$maxRepeat = 20
$status = "Running" # change to Stopped if you want to wait for services to start
do
{
$count = (Get-Service $services | ? {$_.status -eq $status}).count
$maxRepeat--
sleep -Milliseconds 600
} until ($count -eq 0 -or $maxRepeat -eq 0)
I had to tweak this a bit with multiple counters because this service purposely starts and stops slowly. The original script got me on the right track. I had to wait for the service to be in a completely stopped status before I could move on because I'm actually restarting that same service.
You could probably remove the "sleep," but I don't mind leaving it in.
You could probably remove everything and just use the $stopped variable. :)
# change to Stopped if you want to wait for services to start
$running = "Running"
$stopPending = "StopPending"
$stopped = "Stopped"
do
{
$count1 = (Get-Service $service | ? {$_.status -eq $running}).count
sleep -Milliseconds 600
$count2 = (Get-Service $service | ? {$_.status -eq $stopPending}).count
sleep -Milliseconds 600
$count3 = (Get-Service $service | ? {$_.status -eq $stopped}).count
sleep -Milliseconds 600
} until ($count1 -eq 0 -and $count2 -eq 0 -and $count3 -eq 1)
In my Azure build/deployment pipelines I use it like this to start and stop services (after already having sent a 'Stop' command asynchronously before) and which works for all transitional states like Starting, Stopping, Pausing and Resuming (which are called StartPending, StopPending, PausePending and ContinuePending in the status enumeration ServiceControllerStatus).
# Wait for services to be stopped or stop them
$ServicesToStop | ForEach-Object {
$MyService = Get-Service -Name $_ -ComputerName $Server;
while ($MyService.Status.ToString().EndsWith('Pending')) {
Start-Sleep -Seconds 5;
$MyService.Refresh();
};
$MyService | Stop-Service -WarningAction:SilentlyContinue;
$MyService.Dispose();
};
This needs a traditional powershell to function on a remote server, the cmdlet of pwsh.exe does not include parameter -ComputerName.
In my opinion no counters are needed as only transitional states cause the cmdlet to fail and they change anyway to one of the supported states in the near future (maximum 125 seconds for a Stop command).
To add more details to my response to #Christoph
here is a script i recently created to stop services and ensure the processes are also stopped. in our case the processes were predictable. it may be necessary to do more work to get the service/processid mapping if you have multiple services running off the same executeable.
$MaxWait = 180 #seconds
$ServiceNames = "MyServiceName*"
$ProcName = 'MyServiceProcName' #for the services
$sw = [System.Diagnostics.Stopwatch]::StartNew() # to keep track of
$WaitTS = (New-TimeSpan -Seconds $MaxServiceWait) #could also use a smaller interval if you want more progress updates
$InitialServiceState = get-service $ServiceNames | select Name,Status,StartType
write-Host "$ENV:COMPUTERNAME Stopping $ServiceNames"
$sw.Restart()
$Services = #()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending
#make sure the processes are actually stopped!
while (Get-Process | where Name -Match $ProcName)
{
#if there were services still running
if ($Services) {
Write-Host "$ENV:COMPUTERNAME ...waiting up to $MaxServiceWait sec for $($Services.Name)"
#wait for the service to stop
$Services.WaitForStatus("Stopped",$WaitTS)
}
#if we've hit our maximum wait time
if ($sw.Elapsed.TotalSeconds -gt $MaxServiceWait) {
Write-Host "$ENV:COMPUTERNAME Waited long enough, killing processes!"
Get-Process | where name -Match $ProcName | Stop-Process -Force
}
Start-Sleep -Seconds 1
#get current service state and try and stop any that may still be running
#its possible that another process tried to start a service while we were waiting
$Services = #()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending
}

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
}
}