PowerShell und parallele Verarbeitung

Die meisten PowerShell-Skripte laufen einfach seriell ab. Das ist einfach, überschaubar aber nicht unbedingt sehr schnell. Wer auf Daten wartet, verzögert die Verarbeitung. Diese Seite beschäftigt sich mit den verschiedenen Optionen, Aufgaben in PowerShell parallel oder asynchron ausführen zu lassen.

Anwendungsfälle

Windows selbst ist schon lange ein "Multitasking"-System, auch wenn viele Unix-Administratoren gerne daran rummäkeln. Aber heute Systeme mit mehreren CPUs, flotten Festplatten und Netzwerken werden durch einfache serielle Aktionen nur selten ausgelastet. Natürlich ist eine "serielle" Programmierung am einfachsten zu schreiben und auf Fehler zu prüfen. Aber Sie ist auch langsam. Das merken insbesondere die Exchange Administratoren immer wieder, wenn Sie "Massenänderungen" durchführen oder Auswertungen über mehrere Server starten. Wer schon einmal ein Messagetracking über mehrere Server gemacht hat, wird das Leid kennen. Auch der Zugriff auf mehrere Clients kann meist problemlos parallel erfolgen. Wer ein "Subnetzscan" per Ping 1..254 macht und auf Timeouts wartet, wird sehr lange brauchen.

Eine parallele Verarbeitung kann Wunder wirken. Allerdings ist der Aufwand sowohl bei der Entwicklung als auch bei der Ausführung natürlich höher. Dies gilt insbesondere, wenn jedes mal eine eigene Laufzeitumgebung bereitgestellt wird. Programmierer unterscheiden dabei zwischen Threads und Prozessen.

Pipeline ist "pseudo" parallel

Also müssen wir uns mal ein paar unterschiedliche Varianten anschauen. Ohne besondere Vorkehrungen läuft ein PowerShell-Skript erst einmal konsequent seriell ab und selbst eine Verkettung von mehreren Befehlen mittels Pipeline bedeutet nicht automatisch eine parallele Ausführung. Um das zu zeigen, habe ich zwei Code-Stücke genommen, die etwas warten und die Eingabe als Ausgabe anzeigen.

{start-sleep 1 ; write-host "Modul1:"$_; $_}
{start-sleep 5; write-host "Modul2:"$_}

Beide machen also nichts anderes, als die Information, die Sie per Pipeline erhalten haben, nach einiger Verzögerung weiter zu geben. Diese beiden Prozessen werden einfach miteinander auf die verschiedenen Weisen verbunden. Als Eingabe dient einfach eine Zahlenreihe von 1 bis 9. Die kann in PowerShell auch einfach generiert werden. Schauen wir uns die einzelnen Optionen der Verarbeitung an.

Sequentielle Verarbeitung

Fangen wir einfach mit der einfachsten Verarbeitung in Bausteinen ab.

# Liste von Eingabeobjekten erstellen
$liste1 = 1..10

# Eingabeobjekte im ersten Modul verarbeiten und in Liste2 speichern
$liste2 = foreach ($item in $liste1){
    start-sleep 1 ; write-host "Modul1:"$item; $item}

# Liste2 als Eingabe in Modul2 verarbeiten
foreach ($item in $liste2){
    start-sleep 5 ; write-host "Modul2:"$item}

# Ausgabe erfolgt an der Console

Die erste Verarbeitung benötigt 10 Sekunden um dann in Modul2 mit 50 Sekunden zu schlafen.

Insgesamt kommen also knapp 60 Sekunden Laufzeit zusammen. Man sieht auch bei der Bildschirmausgabe, dass der das Modul1 natürlich alles verarbeitet haben muss, ehe das langsamere Modul2 überhaupt erst anfangen kann.

Verketten mit der Pipeline

Der erste und sicher den meisten Personen bekannte Weg ist die Nutzung einer Pipeline, bei der die Ausgaben eines Prozesses an einen anderen Prozess übergeben werden. Auch wenn die Abarbeitung sequentiell erfolgt, da der nachgeschaltete Prozess auf die Daten des Vorgängers warten muss, ist die Verarbeitung schon etwas effektiver.

1..5 | `
   %{start-sleep 1 ; write-host "Modul1:"$_; $_} | `
      %{start-sleep 3; write-host "Modul2:"$_; $_}  | `
         %{start-sleep 5; write-host "Modul3:"$_}

Einen Geschwindigkeitsrekord erreichen wir damit natürlich auch nicht, aber Modul2 kann zumindest schon nach einer Sekunden anfangen, zu verarbeiten. Leider wartet Modul2 und Modul 3 so lange.

Die Bildschirmausgabe zeigt auch, dass die Queue an sich sequentiell bleibt und das nächste Modul erst weiter machen kann, wenn das vorige Modul seine Teilaufgabe erledigt hat. Es ist also nicht so, dass Modul1 seine Ergebnisse nach 10 Sekunden in eine Pipeline geworfen hat und dann pausiert, sondern jedes Element wandert bei dem Beispiel durch die Pipeline durch. Die Verkettung von Prozessen per Pipeline bringt im Bezug auf die Laufzeit keine Vorteile, wohl aber im Bereich der Speicherbelastung, da eine Zwischenspeicherung nicht mehr erforderlich ist.

Echte Parallelität

Sie sehen, dass die verschiedenen Codeteile zwar miteinander "verschränkt" aber immer noch sequentiell ablaufen. Eine Pipeline ist elegant aber erst einmal nicht schneller. Daher stelle ich Alternativen vor, mit denen ich schon gearbeitet habe und weiter unten auch vorstelle:

Variante Beschreibung

Pipeline

Wie soeben schon gezeigt, ist eine Pipeline nicht wirklich "parallel" sondern eine verzahnte Ausführung der Eingaben. Die Skripte laufen aber selbst nicht parallel. Daten können von Vorne nach hinten durchgereicht werden aber ich habe keinen Weg gefunden, wie eine bidirektionale Kommunikation über die Pipeline erfolgen könnte.

Start-Job

Sehr naheliegend ist eine Ausführung als Background-Job. Über "Start-Job" kann der Code als "-SkriptBlock" oder Datei übergeben werden. PowerShell startet dann den Code in einem eigenen Prozess innerhalb der aktuellen PowerShell-Umgebung. Im Taskmanager ist er damit nicht als eigener Prozess zu sehen.. Ausgaben des Prozess können mit "Receive-Job" auch während der Laufzeit sowohl von der unsichtbaren Console als auch Ausgabepipeline abgerufen werden. Wenn der Code fertig ist, ist der Job abgeschlossen. Ich habe noch keinen Weg gefunden, den Job "dauerhaft" laufen zu lassen und Daten nachzufüttern. Er hat zwar Ausgabepipelines (Error, Progress, Verbose, Debug, Warning, Information) aber keine Eingabepipeline.

Start-Process

Mit Start-Process kann ich sogar Prozesse auf anderen Computern aber auch dem lokalen Computer starten. Es ist aber ein komplett eigener "Prozess" unter Windows mit einer eigenen ProcessID und läuft auch weiter, wenn das aufrufende Skript schon lange beendet ist.

PowerShell Runspace

Aus meiner Sicht ist ein Runspace die etwas leistungsfähigere Version eines Start-Jobs, der bestehen und vom Hauptprogramme auch immer wieder neu mit Befehlen und Daten bestückt und gestartet werden kann.

Thread

Neue Prozesse sind relativ "teuer" und brauchen lange zur Instanzierung. Ein Thread ist quasi ein "Leichtgewicht"-Prozess, der weniger stakt abgekoppelt ist.

Foreach -parallel/ PSWorkflow

Sie können eine Sammlung von Objekten über eine Foreach-Schleife mit "-parallel" durch mehrere Threads bearbeiten lassen. Im Hintergrund kommt dazu die "Windows Workflow Foundation" zur Koordination zum Einsatz. Es kann nur innerhalb eines Workflow-Umgebung genutzt werden, die es nur mit PowerShell 3-5.1 gibt.

Scheduled Job

Eher ungewöhnlich aber möglich ist der Weg über einen "geplanten Task" zu gehen. Ein PowerShell-Skript kann seinerseits einfach einen geplanten Task einrichten, der dann gemäß dem Zeitplan von Windows immer wieder gestartet wird. Das eigentliche Programm braucht es dazu nicht. Der Datenaustausch muss dann aber über Dateien, Datenbanken oder ggfls. eine andere PS Interprozesskommunikation erfolgen.

Bei allen verschiedenen Varianten gibt es unterschiede hinsichtlich Steuerung, Datenübergabe, Rückgaben, Ressourcenbedarf, Laufzeitumgebung, Bildschirmausgaben, Fehlerbehandlung, Überleben nach dem Ende des Masters etc. Alles im Hintergrund kann zumindest keine direkt "sichtbare" Bildschirmausgabe zur Fehlersuche nutzen und auch beim Zugriff auf Variablen und Dateien muss man sich abstimmen. Ausgaben mit "Write-Progress" machen für Background-Prozesse auch keinen Sinn und kann man sich eigentlich sparen. Wohl dem, der ausführliche Protokolldateien schreibt und diese per Tail überwachen kann.

Parallelität im Vergleich

Da ich auch immer mal wieder die verschiedenen Dinge verwechsele, habe ich mir folgende Tabelle erstellt:

Ich bin nicht sicher, ob ich alles richtig verstanden habe oder neuere PS-Versionen sich vielleicht anders verhalten. Auch ist die Tabelle noch nicht komplett
Ich freue mich über Feedback und Korrekturen.

Funktion Sequentiell Pipeline Job Process Runspace Thread Task System.
Management.
Automation.
PowerShell

Schwierigkeit

Das ist natürlich eine sehr subjektive Einschätzung

Leicht

Leicht

Mittel

Mittel

 

 

 

Mittel

Geschwindigkeit

Ich habe versucht ein paar Performancemessung zu machen.

Sehr Langsam
60 Sek

Langsam
51 Sek

Schnell
15 Sek

nicht gemessen

 

 

 

schnell

Abhängigkeit vom Vater

Was passiert, wenn das aufrufende Programm beendet wird oder endet. Möglich ist

  • Endet
  • Läuft weiter

Entfällt

Endet

Endet

Weiter

Endet

Endet

Weiter

Endet

Parameterübergabe

Wie können Werte an den anderen Prozess übergeben werden. Eine direkte Kommunikation per PS Interprozesskommunikation ist nicht berücksichtigt.

Globale Variable

Pipeline

Beim Aufruf per Parameter

Beim Aufruf per Parameter

Beim Aufruf per Parameter

 

 

?

ErgebnisRückgabe

Ein Prozess kann Aktionen ausführen aber interessant ist natürlich , ob und wie auch Werte an den Vater-Prozess geliefert werden können. Eine direkte Kommunikation per PS Interprozesskommunikation ist nicht berücksichtigt.

Variable oder Pipeline

Nein

Abruf der Pipeline über Receive-Job. Auch während der Laufzeit möglich

Abruf der Pipeline

Abruf der Streams

 

Nein

?

Jobkontrolle

Entfällt

Entfällt

Get-Job

Get-Process

 

 

 

Job-Objekt

Write-Host Support

Ja

Ja

Ja, pro Prozess

Ja, pro Prozess

 

 

 

Nein

Start-Transcript-Support

Ja, aber nur global

Ja, aber nur global

Nein

Ja, pro Prozess

 

 

 

Nein

LaufzeitUmgebung, d.h. Zugriff auf im Vater definierte Variablen und geladene Snapins und AddOns

Entfällt

Vererbt

"Privater Raum". Module etc. müssen selbst nachgeladen werden.

komplett eigenständig zu verwalten

 

 

 

"Privater Raum". Module etc. müssen selbst nachgeladen werden.

Berechtigung

Gleich

Gleich

Credentials optional

Credentials optional

 

 

 

Credentials optional

Synchronisation

Wie können Jobs untereinander kommunizieren ?

Entfällt

Pipeline Oneway

Selbst zu entwickeln

Selbst zu entwickeln

 

 

 

Selbst zu entwickeln

Throtting

Wie kann man die Anzahl der Jobs begrenzen

Entfällt nur 1 aktiver Teil

Entfällt, nur 1 aktiver Teil

Manuell

Manuell

 

 

 

Manuell

 

Foreach-Object -Parallel

Eine For-Schleife ist ja eine oft genutzte Lösung um den gleichen Code für verschiedene Elemente zu wiederholen. Erstmalig mit PowerShell 7 Preview 3 hat Microsoft einen Parameter "-parallel" ergänzt, mit dem der Inhalt parallel abgearbeitet werden kann. Sie sollten dabei natürlich drauf achten, dass ihr Code das auch abkann. Eine Variable in der Schleife hochzuzählen und im Code zu verwenden funktioniert dann z.B. nicht mehr gut. Auch parallele Zugriffe auf Dateien z.B. per "Out-File" sollten Sie sich genau überlegen.

Zudem müssen Sie schon "Foreach-Objekt" nutzen, denn Microsoft schreibt dazu.

Don’t confuse ForEach-Object cmdlet with PowerShell’s foreach keyword.
The foreach keyword does not handle piped input but instead iterates over an enumerable object.
There is currently no parallel support for the foreach keyword

Start-Job

Wenn Sie nun aber auf der einen Seite Daten relativ schnell ermitteln und die langsamere Nachverarbeitung parallel erfolgen kann, dann ist Start-Job ein mittel der Wahl. Über das "Start-Job"-Commandlet kann einfach ein Skriptblock in den "Hintergrund" gesendet werden. Über GET-Job lässt sich der Status des Jobs abfrage und über "Receive-Job" kann das Ergebnis des Jobs (Pipelineausgabe) einfach wieder abgerufen werden.

# Start einer Ausgabe
PS C:\> start-job {write-host "test";"pipetest"}

Id Name State HasMoreData Location
-- ---- ----- ----------- --------
11 Job11 Running True localhost

PS C:\> Get-Job

Id Name State HasMoreData Location
-- ---- ----- ----------- --------
11 Job11 Completed True localhost

PS C:\>$a= receive-Job 11
test

PS C:\>$a 
pipetest

PS C:\> Remove-Job 11

Ausgaben der Jobs mit "Write-Host" oder der Pipeline landen aber nicht im Fenster des aufrufenden Programms, sondern müssen mit "Receive-Job" abgeholt werden.

Achtung:
Sowohl Ausgaben mit "Write-Host" als auch direkt als STDOUT erscheinen bei einem einfachen "Receive-Job" vermengt. Bei einer Zuweisung an eine Variable werden aber nur die Daten von STDOUT kopiert.

Der Anruf leert dann aber die bis dahin angefallene Warteschlange, wenn Sie dies nicht mit "-keep" unterbinden. Interessant bei Start-Job ist die Option, alternative Credentials anzugeben. Im Taskmanager sehen Sie die Jobs als eigene Prozesse unter dem aufrufenden Prozess.

zurück zu unserem Performance Beispiel als "Job"

# Modul2 als Job parallel starten

1..10 | `
    %{start-sleep 1 ; write-host "Modul1:"$_; $_} | `
        %{$object = $_ ;
          write-host "StartJob: $object"
          start-job `
             -scriptblock {
                    start-sleep 5;
                    $data = $input;
                    write-host "Modul2:$data";
                    $data
                    } `
             -Inputobject $object 
          }
wait-job -state running
get-job | receive-job

Hier dauert es zwar nun wieder simulierte 10 Sekunden aber die Jobs laufen parallel mit ab und da der letzte Job bei Sekunde 10 startet und 5 Sekunden läuft, ist das ganze System nach 15 Sekunden "durch".

Ein Start-Job kann direkt einen Skript-Block enthalten oder ein eigenständige PowerShell-Skript starten. Es ist nicht möglich, als Bestandteil eines Jobs auf eine Funktion im gleichen Skript zu verweisen. Der Job hat also keine "Kopie" der LaufzeitUmgebung.

Wird der Vaterprozess geschlossen, dann werden damit auch alle Jobs entfernt. Da Jobs nach dem Start nicht mehr direkt gesteuert werden können, eignen Sie sich für klar umrissene Aufgaben, die auch ein Ende haben und weniger für Langläufer. Übrigens können Sie keine "PowerShell Events" nutzen, um zwischen Jobs der gleichen Instanz zu kommunizieren. Events können selbst aber Jobs starten. Auf der anderen Seite können Sie in einem Job aber kein "Start-Transcript" nutzen, um die Ausgaben zu protokollieren.

Ich nutze Jobs aber dennoch gerne, um länger Tasks asynchron schon mal auszuführen. Dabei darf man es aber nicht übertreiben. Ich würde z.B. die Aufgabe 100 DNS-Namen zu IP-Adressen aufzulösen sicher nicht auf 100 Jobs verteilen. Aber ein Job, der im Hintergrund die Arbeit schon mal macht, während das Programm gerade andere Daten sammelt und später dann darauf zurückgreift, ist durchaus sinnvoll nutzbar. Insbesondere wenn das Hauptprogramm "blockende" Befehle verwenden, wie z.B. einen HTTP-Listener.GetContext. (Siehe auf PRTG Pause) Hier ein Beispiel, wie man eine Liste von Namen später als "IP zu Name"-Liste als Jobs auslesen kann:

# IP2DNS
#
# PS Script to resolve Names to IPs in the Background

[string[]]$hosts=@()
$hosts+="www.microsoft.com"
$hosts+="www.netatwork.de"
$hosts+="www.msxfaq.de"
$hosts+="www.msxfaq.com"

start-job -ScriptBlock {

   param($hostlist)
   write-host "IP2DNSNAME:Start"
   [hashtable]$ip2name=@{}
   foreach ($hostname in $hostlist) {
      write-host "Hostname:" $hostname -nonewline
      foreach ($dnsanswer in ([System.Net.Dns]::GetHostAddresses($hostname))){
         write-host ("  -> Resolved to IP-Address:" + $dnsanswer.IPAddressToString) -nonewline
         if ($ip2name.containskey($dnsanswer.IPAddressToString)) {
            write-host " Duplicate - SKIP"
         }
         else {
             $result = "" | select ipaddress,hostname
             $result.ipaddress = $dnsanswer.IPAddressToString
             $result.hostname = $hostname
             $ip2name.add($dnsanswer.IPAddressToString,$result)
             write-host " Added"
          }
      }
   }
   $ip2name.values
   write-host "IP2DNSNAME:End"
   } `
   -ArgumentList (,$hosts)    # required to send array as one arument instead of individual args

Als Besonderheit muss man hier sehen, dass die Liste der Hosts in Klammern mit einem führenden Komma angegeben werden muss. So versteht Powershell diese Liste als einen Parameter und nicht als Parameterliste, die dann mit $ARGS ausgelesen werden müsste.

Parameter -AsJob

Eine Sonderform von Jobs verbirgt sich hinter der Option "-AsJob", den verschiedene Commandlets zulassen. Damit ist es möglich, diesen Befehl per PowerShell Remoting auf einem anderen Computer (oder natürlich auch wieder lokal) zu starten und damit zu parallelisieren.

Prozess

Bei einem Prozess wird nicht ein Job innerhalb bzw. genauer unterhalb der startenden Umgebung aufgemacht, sondern eine ganz eigene Instanz eines Prozesses gestartet. Das muss kein PowerShell-Script sein. Das kann ein beliebiger ausführbarer Code sein, also auch eine EXE oder BAT-Datei. Es kann sogar ein Dokument sein, zu dem es eine Programmverknüpfung gibt. So kann durch den Aufruf einer DOC-Datei z.B. Word gestartet werden und über den Parameter "-verb" kann sogar z.B. "Print" ausgewählt werden.

Diese Prozesse laufen eigenständig ab. Sie müssen sich also auch eine entsprechende "Umgebung" für ihre Aktionen schaffen. Sie können keine Module oder Variablen des aufrufenden Programme nutzen. Auch gibt es nicht wirklich eine einfache Schnittstelle zur Übergabe und Rückerhalt von Daten.

Ein Prozess kann Natürlich über Kommandozeilen Informationen erhalten, aber strukturierte Daten muss man notfalls über Dateien oder andere Wege der Kommunikation zwischen Prozessen austauschen. Es ist aber möglich, zumindest STDIN, STDOUT und STDERR über Dateien umzuleiten. Das ist aber Natürlich ein Rückschritt zur objektorientierten Übergabe in einer reinen PowerShell-Umgebung. Import-CliXML und Export-CliXML können helfen, strukturierte Daten bis zu bestimmten Größen bereitzustellen.

Diese Unabhängigkeit bietet aber auch den Weg, alternative Anmeldedaten zu verwenden. Zudem ist der gestartete Prozess nicht vom Vater abhängig, d.h. läuft auch weiter, wenn der startende Prozess nicht mehr aktiv ist. Es gibt mehrere Wege einen Prozess zu starten:

  • Invoke-Command
    Hierbei wird über PSRemote der Prozess auf dem lokalen oder entfernten Computer mit umfangreichen Optionen gestartet. Erst mit der Option "-AsJob" wird das aufrufenden Skript nicht angehalten. Technisch wird lokal ein "Job" als Verwaltungsobjekt erstellt, auch wenn das Programm remote ausgeführt wird. Damit ist eine Verteilung auch auf mehrere Computer (Scale-Out) möglich, wenn diese die entsprechenden Code-Teile vorhalten
Invoke-Command -scriptblock {write-host "hallo"}
  • Start-Process
    Diese Commandlet startet lokal einen neuen Prozess. Leider gibt es außer dem Programmnamen, Parametern und der Option einer Ein/Ausgabe-Umleitung keine weitere Möglichkeiten. Über einen "Get-Process" können Sie (alle) Prozesse sehen.
# Notepad starten aber nicht warten. Es gibt keine weitere Kommunikation
Start-Process notepad.exe

# Notepad starten und warten. Es gibt keine weitere Kommunikation zum Prozess
Start-Process notepad.exe -wait
  • [system.diagnostics.process]::Start
    Interessanter ist dabei vielleicht der Start über die reine .NET-Funktion. Hierbei erhält der aufrufende Prozess ein Objekt zurück, mit dem es den laufen Prozess weiter abfragen kann, selbst wenn der Prozess schon beendet ist. So entspricht diese Funktion besser dem lokalen "Start-Job"
$child = [system.diagnostics.process]::Start("PowerShell", "-file c:\temp\test.ps1 -param1 value1")
Get-process -id $child.id

Für diese Funktion habe ich keine Last/Performance-Tests gemacht, da die Anforderungen hierzu doch eher individuell sind und eine 10fach durchlaufende Schleife den PC nicht wirklich stresst. Das instanziieren von 10 weiteren Prozessen aber schon eher, so dass die gemessenen Zeiten nicht gültig wären.

Eigene Prozesse sind eher etwas, wenn es um die Verteilung auf verschiedene Systeme oder direkt die entfernte Ausführung geht oder ein Nachgeschalteter Prozess gestartet werden soll, ohne das der Vaterprozess lange aktiv bleiben muss.

Ein Einsatzfeld könnte die Aktivierung von PowerShell-Skripten per Taskplaner sein, welcher eiben geplanten Prozess immer nur einmal startet. Hier könnte ein kleiner "Starter" das eigentliche Modul als Prozess starten und sich selbst wieder beenden, damit der Taskplaner ihn erneut aufwecken kann. Dies kann aber auch ganz schnell zu Überlastsituationen führen.

Powershell Workflows

Seit PowerShell 3-5.1 gibt es so genannte "Workflows". Sie können mehrere Befehle einfach parallel ausführen. Auch wenn es wie PowerShell aussieht, werden dabei aber im Hintergrund die Windows Workflow Foundation genutzt. Ähnlich dem Schlüsselwort "Function" wird nun ein Workflow definiert. Siehe dazu PowerShell Workflows.

Aus meiner Sicht ist das ein ganz interessanter Ansatz mit wenig Aufwand eine überschaubare Anzahl von Aktionen zu parallelisieren. Allerdings mit nicht PS6+ zu verwenden.

ScheduledTasks

Wie am Anfang schon geschrieben, sind "geplante Tasks" eigentlich keine echten parallelen Prozesse. Aber der Vollständigkeit halber sei dieser Weg erwähnt. Ich habe den Weg auch schon in Projekten genutzt, um z.B. über einen Webserver und Eventlogs entsprechende PowerShell-Skripte asynchron laufen zu lassen. Natürlich muss ich mir überlegen, wie Eingabedaten an so einen Prozess übermittelt werden und wie die Ergebnisse weiter verarbeitet werden.

Denkbar sind z.B. Dateischnittstellen (Export-CliXML, Import-CLiXML) oder der Prozess erlaubt eine PS Interprozesskommunikation. Eingerichtet ist so ein Prozess relativ schnell

$schedtrigger = New-JobTrigger -Daily -At '00:00 PM'
$schedoptions = New-ScheduledJobOption -StartIfOnBattery -StartIfIdle
Register-ScheduledJob `
   -Name 'PSJob1' `
   -ScriptBlock { ... } `
   -Trigger $schedtrigger `
   -ScheduledJobOption $schedoptions

Alternativ kann ich statt einem Skriptblock auch eine Kommandozeile angeben und anstatt den geplanten Task zyklisch per Zeitplaner zu starten, kann ich ihn natürlich über den Namen manuell anschubsen.

(Get-ScheduledJob -Name 'PSJob1').StartJob()

Jobs mit Runspace / Invoke

Die Aufrufe von Routinen mit "Start-Job" oder dem Parameter "-asJob" startet immer eine neue PowerShell-Session, in der Sie alle Verbindungen neu aufbauen müssen. Sie vererben nichts und zudem kostet die zusätzliche PowerShell-Laufzeitumgebung kostbaren Speicher und dauert wenige Sekunden bis Sie "aktiv" ist.

Mit PowerShell Runspaces wird nur ein weiterer Thread gestartet, was weniger Speicher und weniger Zeit als eine Job-Einrichtung dauert.

MultiThreading mit Sockets

Es muss nicht immer ein kompletter Job sein, wenn man einfach etwas im Hintergrund laufen lassen will. Neben kompletten Jobs mit eigener Laufzeitumgebung gibt es ja noch die Option zusätzlicher Threads. Das geht einmal bei diversen-NET-Klassen, die einen asynchronen Start erlauben. Ich nutzte das z.B. bei dem Aufbau von TCP-Verbindungen. Der klassische Weg ist dabei:

$Socket = New-Object System.Net.Sockets.TCPClient
$Connection = $Socket.Connect("www.msxfaq.de",443)
if ($Socket.connected ) {
   write-host "Connected"
}
else {
   write-host "Timeout on connection - port not open"
}

Dieses Skript "wartet" beim $socket.connect() bis die Verbindung steht. Viel störender ist es, wenn die Verbindung nicht zustande kommt. Da der TCP-Stack das mehrfach versucht, kann das auch mal 21 Sekunden dauern, bis das Skript weiter läuft. Eleganter ist es daher den Socket asynchron zu öffnen und später zurück zu kommen und den Status zu prüfen.

$Socket = New-Object System.Net.Sockets.TCPClient
$Connection = $Socket.BeginConnect("www.msxfaq.de",443,$null,$null)

## hier kann ich dann was anderes tun

if ($Socket.connected ) {
   write-host "Connected"
}
else {
   write-host "Timeout on connection - port not open"
}

Throtting und Synchronisation

Wer parallel mehrere Aufgaben abarbeiten lässt, muss ich um mindestens zwei Dinge sorgen.

  • Throttling
    Parallelisieren kann Geschwindigkeit bringen. Wenn Sie aber 10.000 Empfänger einlesen und 10.000 Jobs starten, um eine Eigenschaft der Empfänger zu ändern, dann wird das vermutlich gar nicht mehr schnell ablaufen. Zum einen sind das sehr viele Prozesse die ihre CPU, Speicher und LAN belasten und zudem kennt auch Exchange Schutzmechanismen (Siehe EWS, Outlook 2003 mit Exchange 2010 und Daten werden abgerufen), um eine Überlastung zu verhindern.
    Insofern sollten Sie ihr Skript entsprechend zügeln, dass es sich nicht selbst blockiert, z.B. indem sie die Anzahl der aktiven Jobs zählen ehe sie neue Jobs anlegen und die Daten beendeter Jobs zeitnah abholen und die Jobs entfernen.
  • Synchronisation
    Sie können in die Situation kommen, dass Sie mehrere parallele Jobs aufeinander abstimmen müssen, z.B.: weil ein Job erst loslegen kann, wenn die Vorarbeiten durch einen anderen Job erledigt sind (und das Active Directory sich repliziert hat). Dies ist insbesondere der Fall, wenn Sie verkettete Aktionen in sich parallelisieren. Aber auch die Zusammenfassung von Ergebnissen verschiedener Jobs kann eine Aufgabe am Ende der Verarbeitung sein. So muss das Hauptprogramm auf das Ende der Jobs warten und die Ergebnisse einsammeln. Wenn es dabei noch auf die Reihenfolge ankommt, dann müssen sie beim Anlegen der Jobs die Job-Daten nutzen.

Ganz allgemein sollten sie schon überlegen, wo eine parallele Verarbeitung heute wirklich sinnvoll ist. Erst mit den Jobs oder Prozessen ist eine merkliche Verbesserung möglich.

Weitere Links