PS Runspace

Wenn ich eine PowerShell starte, dann habe ich auch immer eine "Laufzeitumgebung", in der ich mich bewege. Wir wissen alle, dass wir auch weitere PowerShell-Fenster öffnen können oder mit Start-Job oder Start-Thread auch Befehle in den Hintergrund senden. Ich kann aber auch parallel weitere Runspaces instanzieren und Code dort parallel und im Hintergrund ausführen lassen.

Anstatt nun viel Text und Beispielcode zu lesen, können Sie sich das kurze Video von Adam Driscol anschauen, welches sehr gut die Nutzung von PowerShell Runspaces erklärt

Adan Driscol: Advanced PowerShell - Runspaces
https://www.youtube.com/watch?app=desktop&v=WvxUuru_vhk

 

Mit PowerShell Core gibt es mit Start-ThreadJob auch eine neue Option zum Start von Prozessen im Hintergrund.

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