PS Runspace

Ehe Sie sich mit Runspaces auseinandersetzen, sollten Sie Start-ThreadJob anschauen

Eine ganz einfache Variante zeigt, was damit möglich ist und was nicht. Hier ein Code, der einfach ein "Get-Recipient" ausführen soll. Er wurde in einer Exchange PowerShell gestartet.

$PowerShell = [powershell]::Create()
[void]$PowerShell.AddScript({
    Get-recipient -resultsize 3
})
$PowerShell.Invoke()

Der "$powershell.invoke()" startet den Code und wartet, bis er fertig ist. Das geht hier schnell und folgende Ausgaben kann ich generieren:

[PS] C:\>$PowerShell
Commands            : System.Management.Automation.PSCommand
Streams             : System.Management.Automation.PSDataStreams
InstanceId          : 5966f660-b993-4176-b5cd-a73d5a6b3ec7
InvocationStateInfo : System.Management.Automation.PSInvocationStateInfo
IsNested            : False
HadErrors           : True
Runspace            : System.Management.Automation.Runspaces.LocalRunspace
RunspacePool        :
IsRunspaceOwner     : True
HistoryString       :

Hier sehen Sie die verschiedenen Properties. Da sind "Commands" und "Streams" interessant:

[PS] C:\>$PowerShell.Commands | fl *
 
Commands : {
               Get-recipient -resultsize 3
           }

[PS] C:\>$PowerShell.Streams

Error       : {The term 'Get-recipient' 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.}
Progress    : {parent = -1 id = 0 act = Preparing modules for first use. stat =   cur =  pct = -1 sec = -1 type =
              Completed}
Verbose     : {}
Debug       : {}
Warning     : {}
Information : {}
 

Im Property "Commands" stehen die Befehle, die ich übergeben hatte und auch immer wieder per "invoke" aufrufen könnte. Das Skript ist aber nicht gelaufen, weswegen hier im ERROR-Stream der Fehlercode steht. Damit sehe ich aber auch, dass der Runspace eine eigene Shell ist und nicht die Module und Einstellungen der aufrufenden Shell vererbt bekommt. Die Exchange Commandlets funktionieren hier einfach nicht.

Ich muss also in dem Runspace ggfls. meine erforderlich Umgebung noch einmal neu schaffen. Um die Befehle nun parallel auszuführen, muss ich aber "BeginInvoke()" nutzen. Damit wird das aufrufende Programm nicht angehalten.

# Code definieren
$code = { 
   1..100 | % {
      $(get-date)
      start-sleep -seconds 1
   }
}
#Objekt System.Management.Automation.PowerShell vorbereiten
$newPowerShell = [PowerShell]::Create().AddScript($code)

# Job starten
$job = $newPowerShell.BeginInvoke()

#Warten auf das Ende
#While (-Not $job.IsCompleted) {
#   write-host "
#}

#Ergebnisse abholen. Wartet allerdings bis das Skript beendet ist. Notfalls endlos
$newPowerShell.EndInvoke($job)

# Aufraeumen
$newPowerShell.Dispose()

Die so gestartet Jobs finden Sie nicht mit "Get-Job". Sie sind also gut beraten das Ergebnis des "BeginInvoke" irgendwo zu hinterlegen. Soweit ich gesehen habe, werden aber alle Ausgaben erst mit dem "EndInvoke()" zurück gegeben und auch erst dann, wenn der Code terminiert. Das Beispiel wird also erst nach 100 Sekunden beendet und dann die Ausgabe eingesammelt. Das ist zwar "schnell parallel" aber eine Weiterverarbeitung der Zwischenergebnisse ist so nicht möglich. Auch scheint die Laufzeitumgebung sich nicht zu vererben wenngleich es keinen weiteren "Powershell.exe"-Prozess im Taskmanager gibt. Es sieht wirklich nach einem eigenen Thread aus.

Weitere Links