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

半城伤御伤魂 提交于 2019-12-06 19:15:40

问题


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



回答3:


I got it to work as follows;

function CreateTable {
    $table = "" | select ServerName, ExitCode, ProcessID, StartMode, State, Status, Comment
    $table
}

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


来源:https://stackoverflow.com/questions/15520404/how-to-call-a-powershell-function-within-the-script-from-start-job

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