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

匿名 (未验证) 提交于 2019-12-03 08:41:19

问题:

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.

回答1:

Background jobs are run in a seperate process, so the jobs you create with Start-Job can not interact with functions unless you include them in the $scriptblock.

Even if you included the function in the $scripblock, Write-Host would not output it's content to the console until you used Get-Job | Receive-Job to recieve the jobs result.

EDIT The problem is that your DisplayMessage function is in a local script-scope while your eventhandler runs in a different parent scope(like global which is the session scope), so it can't find your function. If you create the function in the global scope and call it from the global scope, it will work.

I've modified your script to do this now. I've also modified to scriptblock and unregistered the events when the script is done so you won't get 10x messages when you run the script 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


回答2:

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!