├── Examples ├── Invoke-Example.1.ps1 └── Invoke-Example.2.ps1 ├── Invoke-Job.ps1 └── README.md /Examples/Invoke-Example.1.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Invoke-Job.ps1 2 | 3 | function Invoke-HelloWorld { 4 | Write-Warning 'Hello World' 5 | } 6 | 7 | (1..5) | Invoke-Job -ScriptBlock { Invoke-HelloWorld } -ImportFunctions -Throttle 2 -PassThru 8 | -------------------------------------------------------------------------------- /Examples/Invoke-Example.2.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Invoke-Job.ps1 2 | 3 | # Load a script in this user session 4 | # https://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542 5 | Import-Module .\Get-PendingReboot.ps1 6 | 7 | # Make a simple ScriptBlock that will receive the computername from the pipeline and execute the script against the remote computer. In this case, Get-PendingReboot that we've loaded in this current session. 8 | 9 | $ScriptBlock = { 10 | Process { 11 | 12 | # Collect the InputObject sent through the Pipeline. 13 | $ComputerName = $_ 14 | 15 | Try { 16 | 17 | # Because Get-PendingReboot has been imported into this session, I can call the function directly. 18 | # I'm also $Using variables from the parent session. Everything works as expected. 19 | 20 | Invoke-Command -ComputerName $ComputerName -Credential $Using:Credential -ScriptBlock ${function:Get-PendingReboot} 21 | 22 | } Catch { 23 | 24 | Write-Error $_.Exception.Message 25 | 26 | } 27 | 28 | } 29 | } 30 | 31 | # Get list of computers and set the credential used for the remote execution. 32 | 33 | $Computers = Get-Content -Path '.\computers.txt' 34 | $Credential = Get-Credential 35 | 36 | # Start-Job 4 jobs at a time, with a timeout of 30 seconds, and return the results (not the Job object). And beautify it by presenting the results in a table. 37 | 38 | $Computers | Invoke-Job -ScriptBlock $ScriptBlock -Throttle 4 -Timeout 30 -ImportFunctions -PassThru | Format-Table 39 | -------------------------------------------------------------------------------- /Invoke-Job.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Job { 2 | <# 3 | .SYNOPSIS 4 | Function to control background job processing exposing additional functionalities. 5 | 6 | .DESCRIPTION 7 | Function to control background job processing exposing additional functionalities. 8 | 9 | Added functionalities: 10 | -ImportFunctions 11 | -Throttle 12 | -Timeout 13 | -PassThru 14 | 15 | .NOTES 16 | Name: Invoke-Job 17 | Author: Marc R Kellerman (@mkellerman) 18 | 19 | Inspired by Invoke-Parallel by RamblingCookieMonster 20 | https://github.com/RamblingCookieMonster/Invoke-Parallel 21 | 22 | .PARAMETER ScriptFile 23 | Specifies a local script that this cmdlet runs as a background job. Enter the path and file name of the script or pipe a script path to Start-Job. The script must be on the local computer or in a folder that the local computer can access. 24 | 25 | When you use this parameter, Windows PowerShell converts the contents of the specified script file to a script block and runs the script block as a background job. 26 | 27 | 28 | .PARAMETER ScriptBlock 29 | Specifies the commands to run in the background job. Enclose the commands in braces ( { } ) to create a script block. This parameter is required. 30 | 31 | You may use $Using: language in PowerShell 3 and later. 32 | Refer to the InputObject as $Input. 33 | 34 | .PARAMETER ArgumentList 35 | Specifies an array of arguments, or parameter values, for the script that is specified by the FilePath parameter. 36 | 37 | Because all of the values that follow the ArgumentList parameter name are interpreted as being values of ArgumentList, specify this parameter as the last parameter in the command. 38 | 39 | .PARAMETER Authentication 40 | Specifies the mechanism that is used to authenticate user credentials. The acceptable values for this parameter are: 41 | 42 | Default 43 | Basic 44 | Credssp 45 | Digest 46 | Kerberos 47 | Negotiate 48 | NegotiateWithImplicitCredential 49 | The default value is Default. 50 | 51 | CredSSP authentication is available only in Windows Vista, Windows Server 2008, and later versions of the Windows operating system. 52 | 53 | For more information about the values of this parameter, see AuthenticationMechanism Enumeration in the MSDN library. 54 | 55 | Caution: Credential Security Support Provider (CredSSP) authentication, in which the user's credentials are passed to a remote computer to be authenticated, is designed for commands that require authentication on more than one resource, such as accessing a remote network share. This mechanism increases the security risk of the remote operation. If the remote computer is compromised, the credentials that are passed to it can be used to control the network session. 56 | 57 | .PARAMETER Credential 58 | Specifies a user account that has permission to perform this action. The default is the current user. 59 | 60 | Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object, such as one from the Get-Credential cmdlet. 61 | 62 | .PARAMETER InitializationScript 63 | Specifies commands that run before the job starts. Enclose the commands in braces ( { } ) to create a script block. 64 | 65 | Use this parameter to prepare the session in which the job runs. For example, you can use it to add functions, snap-ins, and modules to the session. 66 | 67 | .PARAMETER InputObject 68 | Specifies input to the command. Enter a variable that contains the objects, or type a command or expression that generates the objects. 69 | 70 | In the value of the ScriptBlock parameter, use the $Input automatic variable to represent the input objects. 71 | 72 | .PARAMETER PSVersion 73 | Specifies a version. This cmdlet runs the job with the version of Windows PowerShell. The acceptable values for this parameter are: 2.0 and 3.0. 74 | 75 | This parameter was introduced in Windows PowerShell 3.0. 76 | 77 | .PARAMETER RunAs32 78 | Indicates that this cmdlet runs the job in a 32-bit process. Use this parameter to force the job to run in a 32-bit process on a 64-bit operating system. 79 | 80 | On 64-bit versions of Windows 7 and Windows Server 2008 R2, when the Start-Job command includes the RunAs32 parameter, you cannot use the Credential parameter to specify the credentials of another user. 81 | 82 | .PARAMETER AutoRemoveJob 83 | Indicates that this cmdlet deletes the job after it returns the job results. 84 | 85 | .PARAMETER ImportFunctions 86 | Import all functions from current session in the background job. 87 | 88 | .PARAMETER Throttle 89 | Maximum number of background jobs to run at a single time. 90 | 91 | .PARAMETER Timeout 92 | Specifies the maximum wait time for each background job, in seconds. The default value, the cmdlet waits until the job finishes. 93 | 94 | .PARAMETER PassThru 95 | Returns the result from the background job. By default, this cmdlet returns the job progress. 96 | 97 | .PARAMETER All 98 | Returns all background job progress (Start/Stop/Failed/Completed). By default, this cmdlet will returns the job progress when completed. 99 | 100 | This parameter is ignored when -PassThru is used. 101 | 102 | .EXAMPLE 103 | 104 | Example 1: Start 5 background jobs, throttle 2 jobs in parallel, Output result from Job and use a custom function from current session. 105 | 106 | function Invoke-HelloWorld { 107 | Write-Warning 'Hello World' 108 | } 109 | 110 | (1..5) | Invoke-Job -ScriptBlock { Invoke-HelloWorld; Start-Sleep 2 } -ImportFunctions -Throttle 2 -PassThru 111 | 112 | .LINK 113 | https://github.com/mkellerman/Invoke-Job 114 | #> 115 | 116 | [cmdletbinding(DefaultParameterSetName="ScriptBlock")] 117 | Param( 118 | [Parameter(Mandatory=$True, ParameterSetName='FilePath')] 119 | [string]$FilePath, 120 | [Parameter(Mandatory=$True, ParameterSetName='ScriptBlock')] 121 | [scriptblock]$ScriptBlock, 122 | [object[]]$ArgumentList, 123 | [System.Management.Automation.Runspaces.AuthenticationMechanism]$Authentication, 124 | [pscredential]$Credential, 125 | [scriptblock]$InitializationScript, 126 | [Parameter(Mandatory=$False,ValueFromPipeline=$true)] 127 | [Alias('CN','__Server','IPAddress','Server','ComputerName')] 128 | [psobject]$InputObject, 129 | [version]$PSVersion, 130 | [switch]$RunAs32, 131 | [switch]$AutoRemoveJob, 132 | [switch]$ImportFunctions, 133 | [int]$Throttle, 134 | [int]$Timeout, 135 | [switch]$PassThru, 136 | [switch]$All 137 | ) 138 | Begin { 139 | 140 | $JobName = 'Invoke-Job' 141 | 142 | $JobQueue = New-Object System.Collections.Queue 143 | 144 | # Collect any previously created jobs and marked them as Received already. 145 | $Script:JobReceived = @() 146 | Get-Job -Name $JobName -ErrorAction SilentlyContinue | ForEach-Object { $Script:JobReceived += $PSItem.Id } 147 | 148 | # Create InitializationScript 149 | $InitializationScripts = @() 150 | If ($ImportFunctions) { 151 | 152 | $PSDefaults = Start-Job -ScriptBlock { 153 | 154 | #Get modules, snapins, functions in this clean runspace 155 | $Modules = Try { Get-Module | Select-Object -ExpandProperty Name } Catch { $Null } 156 | $Snapins = Try { Get-PSSnapin | Select-Object -ExpandProperty Name } Catch { $Null } 157 | $Functions = Try { Get-ChildItem function:\ | Select-Object -ExpandProperty Name } Catch { $Null } 158 | 159 | #Get variables in this clean runspace 160 | #Called last to get vars like $? into session 161 | $Variables = Try { Get-Variable | Select-Object -ExpandProperty Name } Catch { $Null } 162 | 163 | #Return a hashtable where we can access each. 164 | [PSCustomObject]@{ 165 | Modules = $Modules 166 | Snapins = $Snapins 167 | Functions = $Functions 168 | Variables = $Variables 169 | } 170 | 171 | } | Receive-Job -Wait -AutoRemoveJob 172 | 173 | $FunctionsFileName = [System.IO.Path]::GetTempFileName() | ForEach-Object { Move-Item -Path $PSItem -Destination "$($PSItem -Replace "\.tmp", ".ps1")" -PassThru } 174 | $UsingFunctions = Get-ChildItem function:\ | Where-Object { $_.ScriptBlock.Ast.GetType().Name -eq 'FunctionDefinitionAst' } | Where-Object { -not ($PSDefaults.Functions -contains $_.Name ) } | ForEach-Object { $_.ScriptBlock.Ast.Extent.Text } 175 | $UsingFunctions -Join "`r`n`r`n" | Set-Content $FunctionsFileName 176 | $InitializationScripts += "Try { Import-Module '$FunctionsFileName' -ErrorAction SilentlyContinue -NoClobber -Force } Catch { }" 177 | 178 | } 179 | If ($InitializationScript) { $InitializationScripts += $InitializationScript.ToString() } 180 | $InitializationScript = [scriptblock]::Create($InitializationScripts -Join "`r`n") 181 | 182 | function Invoke-GetJob ([switch]$First) { 183 | Get-Job -Name $JobName -ErrorAction SilentlyContinue | Where-Object { $Script:JobReceived -notcontains $_.Id } 184 | } 185 | function Invoke-StartJob { 186 | 187 | $JobParams = @{} 188 | If ($ScriptBlock) { $JobParams['ScriptBlock'] = $ScriptBlock } 189 | If ($FilePath) { $JobParams['FilePath'] = $FilePath } 190 | If ($Credential) { $JobParams['Credential'] = $Credential } 191 | If ($Authentication ) { $JobParams['Authentication '] = $Authentication } 192 | If ($RunAs32) { $JobParams['RunAs32'] = $RunAs32 } 193 | If ($PSVersion ) { $JobParams['PSVersion '] = $PSVersion } 194 | If ($ArgumentList) { $JobParams['ArgumentList'] = $ArgumentList } 195 | 196 | # Bring Parent scope variable into current scope 197 | # Fix to using $Using variable that uses the same name than a parameter of this function. 198 | 199 | Get-Variable -Name ScriptBlock -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 200 | Get-Variable -Name FilePath -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 201 | Get-Variable -Name Credential -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 202 | Get-Variable -Name Authentication -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 203 | Get-Variable -Name RunAs32 -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 204 | Get-Variable -Name PSVersion -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 205 | Get-Variable -Name ArgumentList -Scope 2 -ErrorAction SilentlyContinue | % { Set-Variable -Scope 0 -Name $_.Name -Value $_.Value } 206 | 207 | If ($JobQueue.Count) { 208 | $Job = $JobQueue.Dequeue() | Start-Job -Name $JobName -InitializationScript $InitializationScript @JobParams 209 | If (-Not($PassThru) -and ($All)) { Return $Job } 210 | } 211 | } 212 | 213 | function Invoke-ReceiveJob { 214 | 215 | If ($Job = Invoke-GetJob | Select-Object -First 1) { 216 | 217 | $Script:JobReceived += $Job.Id 218 | 219 | While ($Job | Where-Object State -eq 'Running') { 220 | If ($Timeout) { 221 | $Timespan = (Get-Date) - $Job.PSBeginTime 222 | If ($Timespan.TotalSeconds -ge $Timeout) { 223 | $Job | Stop-Job -Confirm:$False -EA 0 | Out-Null 224 | } 225 | } 226 | If ($PassThru) { $Job | Receive-Job -EA 0 } 227 | } 228 | 229 | If ($PassThru) { $Job | Receive-Job -Wait -EA 0 } 230 | Else { $Job | Wait-Job -EA 0 } 231 | 232 | If ($AutoRemoveJob) { $Job | Remove-Job -Force -EA 0 } 233 | 234 | If ($Job.State -eq 'Stopped') { Write-Error "This Job was stopped because execution time exceeded Timeout value ($Timeout)."} 235 | 236 | } 237 | 238 | } 239 | 240 | } 241 | 242 | Process { 243 | 244 | # Add each inputobjects into the JobQueue 245 | $InputObject | ForEach-Object { $JobQueue.Enqueue($PSItem) } 246 | 247 | } 248 | 249 | End { 250 | 251 | If (-Not($Timeout -gt 0)) { $Timeout = [int]::MaxValue } 252 | If (-Not($Throttle -gt 0)) { $Throttle = [int]::MaxValue } 253 | 254 | # While there is jobs in queue and jobs are running 255 | While (($JobQueue.Count -gt 0) -or ((Invoke-GetJob | Measure-Object).Count -gt 0)) { 256 | 257 | # While there is jobs in queue and jobs are running 258 | While (($JobQueue.Count -gt 0) -and ((Invoke-GetJob | Measure-Object).Count -lt $Throttle)) { 259 | Invoke-StartJob 260 | } 261 | 262 | Try { 263 | Invoke-ReceiveJob 264 | } Catch { 265 | 266 | # If -ErrorAction is set to Stop, then force stop all jobs that are running 267 | If ($ErrorActionPreference -eq 'Stop') { 268 | Invoke-GetJob | Stop-Job -EA 0 269 | } 270 | 271 | Write-Error $_.Exception.Message 272 | } 273 | Start-Sleep -Milliseconds 200 274 | 275 | } 276 | 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invoke-Job 2 | 3 | Function to control background job processing exposing additional functionalities. 4 | 5 | Added functionalities: 6 | * ImportFunctions 7 | * Throttle 8 | * Tmeout 9 | * PassThru 10 | 11 | ## Example 12 | 13 | Start 5 background jobs, throttle 2 jobs in parallel, Output result from Job and use a custom function from current session. 14 | 15 | ``` 16 | function Invoke-HelloWorld { 17 | Write-Warning 'Hello World' 18 | } 19 | 20 | (1..5) | Invoke-Job -ScriptBlock { Invoke-HelloWorld; Start-Sleep 2 } -ImportFunctions -Throttle 2 -PassThru 21 | ``` 22 | --------------------------------------------------------------------------------