web-dev-qa-db-fra.com

Comment appeler une fonction PowerShell dans le script à partir de Start-Job?

J'ai vu cette question et cette question mais je n'ai pas trouvé de solution à mon problème.

Voici donc la situation: j'ai deux fonctions DoWork et DisplayMessage dans un fichier script (.ps1). Voici le 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 ###

Je crée 3 tâches d'arrière-plan (en utilisant $ array avec 3 éléments) et j'utilise l'événement pour transmettre des messages des tâches d'arrière-plan à l'hôte. Je m'attendrais à ce que l'hôte PowerShell affiche 1 fois "Démarrage du traitement" et "Fin du traitement" et "Début du travail" et "Fin du travail" 3 fois. Mais à la place, je ne reçois pas "Démarrage du travail"/"Fin du travail" affiché dans la console.

L'événement est également traité comme un travail dans PowerShell, donc quand je fais Get-Job, je peux voir l'erreur suivante associée au travail d'événement:

{Le terme 'DisplayMessage' n'est pas reconnu comme le nom d'une applet de commande, d'une fonction, d'un fichier de script ou d'un programme exploitable. Vérifiez l'orthographe du nom, ou si un chemin a été inclus, vérifiez que le chemin est correct et réessayez.}

Ma question: comment réutiliser (ou référencer) une fonction (DisplayMessage dans mon cas) définie dans le même script d'où j'appelle Start-Job? C'est possible? Je sais que nous pouvons utiliser -InitializationScript pour passer des fonctions/modules à Start-Job, mais je ne veux pas écrire la fonction DisplayMessage deux fois, une dans le script et une autre dans InitializationScript.

11
digitguy

Les travaux en arrière-plan sont exécutés dans un processus séparé, donc les travaux que vous créez avec Start-Job ne peut interagir avec les fonctions que si vous les incluez dans le $scriptblock.

Même si vous avez inclus la fonction dans le $scripblock, Write-Host ne publierait pas son contenu sur la console tant que vous n'auriez pas utilisé Get-Job | Receive-Job pour recevoir le résultat des travaux.

EDIT Le problème est que votre fonction DisplayMessage est dans une portée de script locale tandis que votre gestionnaire d'événements s'exécute dans une portée parent différente (comme global qui est la portée de la session), il ne peut donc pas trouver votre fonction. Si vous créez la fonction dans la portée globale et l'appelez depuis la portée globale, cela fonctionnera.

J'ai modifié votre script pour le faire maintenant. J'ai également modifié le scriptblock et désenregistré les événements lorsque le script est terminé afin que vous n'obteniez pas de messages 10x lorsque vous exécutez le script plusieurs fois :-)

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

Tester

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
6
Frode F.

Un moyen simple d'inclure des fonctions locales dans un travail d'arrière-plan:

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

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

Je l'ai fait fonctionner comme suit;

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

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