Как вызвать функцию powershell в script из Start-Job?

Я видел этот вопрос и этот вопрос, но не смог найти решение моей проблемы.

Итак, вот ситуация: У меня есть две функции DoWork и DisplayMessage в файле script (.ps1). Вот код:

### 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 ###

Я создаю 3 фоновых задания (используя $array с 3 элементами) и используя событие для передачи сообщений из фоновых заданий на хост. Я бы ожидал, что хост powershell отобразит "Обработка старта" и "Обработка завершает" 1 раз и "Запуск работы" и "Завершение работы" 3 раза каждый. Но вместо этого я не получаю "Начальная работа" / "Конечная работа", отображаемая в консоли.

Событие также рассматривается как задание в powershell, поэтому, когда я выполняю Get-Job, я вижу следующую ошибку, связанную с заданием события:

{tермин "DisplayMessage" не распознается как имя командлета, функция, файл script или операционная программа. Проверьте правильность написания имя, или если путь был включен, убедитесь, что путь правильный и повторите попытку.}

Мой вопрос: как мы можем повторно использовать (или ссылаться) функцию (DisplayMessage в моем случае), определенную в том же script, с которого я вызываю Start-Job? Является ли это возможным? Я знаю, что мы можем использовать -InitializationScript для передачи функций/модулей в Start-Job, но я не хочу писать функцию DisplayMessage дважды, одну в script и другую в InitializationScript.

Ответ 1

Фоновые задания запускаются в отдельном процессе, поэтому задания, созданные с помощью Start-Job, не могут взаимодействовать с функциями, если вы не включили их в $scriptblock.

Даже если вы включили функцию в $scripblock, Write-Host не выводит ее содержимое на консоль, пока вы не будете использовать Get-Job | Receive-Job для получения результата задания.

РЕДАКТИРОВАТЬ. Проблема заключается в том, что ваша функция DisplayMessage находится в локальном script -scope, в то время как ваш обработчик событий работает в другой родительской области (например, global, которая является областью сеанса), поэтому не может найти вашу функцию. Если вы создадите эту функцию в глобальной области и вызовите ее из глобальной области, она будет работать.

Я изменил ваш script, чтобы сделать это сейчас. Я также модифицировал скриптовый блок и незарегистрировал события, когда выполняется script, поэтому вы не будете получать сообщения 10x при многократном запуске script: -)

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

Тест

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

Легкий способ включения локальных функций в фоновое задание:

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

Start-Job -Name "DoActualWork" $ScriptBlock -ArgumentList $array -InitializationScript $init | Out-Null

Ответ 3

Я получил это на работу; function CreateTable {$ table = "" | выберите ServerName, ExitCode, ProcessID, StartMode, State, Status, Comment $ table}

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