How to call a powershell function within the script from Start-Job?

digitguy picture digitguy · Mar 20, 2013 · Viewed 68.8k times · Source

I saw this question and this question but couldn't find solution to my problem.

So here is the situation: I have two functions DoWork and DisplayMessage in a script (.ps1) file. 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 event to pass messages from background jobs to the host. I would expect the powershell host to display "Processing Starts" and "Processing Ends" 1 time and "Starting work" and "Ending work" 3 times each. But instead I am not getting "Starting work"/"Ending work" displayed in console.

Event is also treated as a job in powershell, so when I do Get-Job, I can see following error associated with the event job:

{The term 'DisplayMessage' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.}

My question: How can we reuse (or reference) a function (DisplayMessage in my case) defined in the same script from where I am calling Start-Job? Is it possible? I know we can use -InitializationScript to pass functions/modules to Start-Job, but I do not want to write DisplayMessage function twice, one in script and another in InitializationScript.

Answer

mjolinor picture mjolinor · Mar 20, 2013

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