How to call powershell function in script from Start-Job?

I saw this question and this question , but could not find a solution to my problem.

So here is the situation: I have two DoWork and DisplayMessage functions in a script file (.ps1). Here is the code:

### START OF SCRIPT ### function DoWork { $event = Register-EngineEvent -SourceIdentifier NewMessage -Action { DisplayMessage($event.MessageData) } $scriptBlock = { Register-EngineEvent -SourceIdentifier NewMessage -Forward $message = "Starting work" $null = New-Event -SourceIdentifier NewMessage -MessageData $message ### DO SOME WORK HERE ### $message = "Ending work" $null = New-Event -SourceIdentifier NewMessage -MessageData $message Unregister-Event -SourceIdentifier NewMessage } DisplayMessage("Processing Starts") $array = @(1,2,3) foreach ($a in $array) { Start-Job -Name "DoActualWork" $ScriptBlock -ArgumentList $array | Out-Null } #$jobs = Get-Job -Name "DoActualWork" While (Get-Job -Name "DoActualWork" | where { $_.State -eq "Running" } ) { Start-Sleep 1 } DisplayMessage("Processing Ends") Get-Job -Name "DoActualWork" | Receive-Job } function DisplayMessage([string]$message) { Write-Host $message -ForegroundColor Red } DoWork ### END OF SCRIPT ### 

I am creating 3 background jobs (using $ array with 3 elements) and using an event to send messages from background jobs to the host. I would expect the powershell host to display “Start Processing” and “Processing Ends” 1 time and “Start Work” and “Shut Down” 3 times each. But instead, I don't get the “Getting Started” / “Finishing Work” displayed in the console.

The event is also considered as a job in powershell, so when I execute Get-Job, I see the following error related to setting the event:

{The term "DisplayMessage" is not recognized as a cmdlet name, function, script file, or operating program. Check the spelling of the name, or if the path was included, make sure the path is correct and try again.}

My question is: how can we reuse (or reference) the function (DisplayMessage in my case) defined in the same script that I call Start-Job from? Is it possible? I know that we can use -InitializationScript to transfer functions / modules to Start-Job, but I do not want to write the DisplayMessage function twice, one in a script and the other in InitializationScript.

+10
source share
3 answers

Background jobs are started in a separate process, so jobs created using Start-Job cannot interact with functions unless you include them in $scriptblock .

Even if you included the function in $scripblock , Write-Host does not print its contents to the console until you use Get-Job | Receive-Job Get-Job | Receive-Job to get the result of the job.

EDIT . The problem is that your DisplayMessage function is in the local script -scope, while your event handler is running in a different parent scope (e.g. global, which is the session scope), so it cannot find your function. If you create this function in the global area and call it from the global area, it will work.

I modified your script to do it now. I also modified the script block and unregistered events when the script is executed, so you will not receive 10x messages when the script is run multiple times :-)

Untitled1.ps1

 function DoWork { $event = Register-EngineEvent -SourceIdentifier NewMessage -Action { global:DisplayMessage $event.MessageData } $scriptBlock = { Register-EngineEvent -SourceIdentifier NewMessage -Forward $message = "Starting work $args" $null = New-Event -SourceIdentifier NewMessage -MessageData $message ### DO SOME WORK HERE ### $message = "Ending work $args" $null = New-Event -SourceIdentifier NewMessage -MessageData $message Unregister-Event -SourceIdentifier NewMessage } DisplayMessage("Processing Starts") $array = @(1,2,3) foreach ($a in $array) { Start-Job -Name "DoActualWork" $ScriptBlock -ArgumentList $a | Out-Null } #$jobs = Get-Job -Name "DoActualWork" While (Get-Job -Name "DoActualWork" | where { $_.State -eq "Running" } ) { Start-Sleep 1 } DisplayMessage("Processing Ends") #Get-Job -Name "DoActualWork" | Receive-Job } function global:DisplayMessage([string]$message) { Write-Host $message -ForegroundColor Red } DoWork Get-EventSubscriber | Unregister-Event 

Test

 PS > .\Untitled1.ps1 Processing Starts Starting work 1 Starting work 2 Ending work 1 Ending work 2 Starting work 3 Ending work 3 Processing Ends 
+5
source

An easy way to include local functions in a background job:

 $init=[scriptblock]::create(@" function DoWork {$function:DoWork} "@) Start-Job -Name "DoActualWork" $ScriptBlock -ArgumentList $array -InitializationScript $init | Out-Null 
+8
source

I got it to work; function CreateTable {$ table = "" | select ServerName, ExitCode, ProcessID, StartMode, State, Status, Comment $ table}

$ init = [scriptblock] :: create (@ "function CreateTable {$ function: createtable}" @)

0
source

All Articles