PowerShell Streams
Auf der Seite PowerShell Pipeline habe ich beschrieben, wie ein Skripte eine Information über die Pipeline an ein nachfolgendes Skript geben kann. Aber PowerShell hat nicht nur eine Pipeline, sondern mindestens 6 getrennte Pipelines, die als "Stream" bezeichnet werden und für unterschiedliche Aufgaben genutzt werden.
Sofern nicht anders beschrieben, wurden alle Tests mit der PowerShell 7.4.4 und den Standardeinstellungen erstellt.
Stream 1 bis 6
Die meisten Administratoren kennen sicherlich die "Pipeline", und über die "Verkettung" kann ein Skript entsprechend Daten über das "|"-Zeichen an ein anderes Skript übergeben. Zudem sollten alle Kommandozeilen-Nutzer auch die Umleitung mit ">" und ">>" in Dateien kennen. Vielleicht hat jemand unter DOS schon mal ein "2>" verwendet, um auch Errors in eine Datei zu schreiben. Und damit sind wir genau beim Thema. Das gibt es auch bei PowerShell aber an verschiedenen Stellen der Microsoft Dokumentation werden sechs Streams aufgeführt.
1 Success Stream 2 Error Stream 3 Warning Stream 4 Verbose Stream 5 Debug Stream 6 Information Stream
Die Streams können über verschiedene Befehle mit Daten gefüllt werden und auch die Ausgabe landet je nach Stream an unterschiedlichen Stellen.
Test mit PowerShell
Um die Funktionsweise zu testen, habe ich folgendes Skript verwendet.
Start-Transcript .\transcript.txt Write-Host "0WriteHost" Write-Output "1WriteOutput" Write-Error "2WriteError" Write-Warning "3WriteWarning" Write-Verbose "4WriteVerbose" Write-Debug "5WriteDebug" Write-Information "Write6Information" "7Direktausgabe" [console]::Out.WriteLine("8ConsoleWrite") [console]::Error.WriteLine("9ConsoleError") Stop-Transcript
Die Ausgabe zeigt noch die erwartete Information, wenngleich etwas durchwachsen.
Die Ausgaben von "Write-Verbose", "Write-Debug" und "Write-Information" sind nicht sichtbar, das deren Funktion durch die Voreinstellungen der Preferences mit "SilentlyContinue" unterdrückt werden.
Im Transcript habe ich aber andere Ausgaben gefunden. Der Write-Host ist natürlich vorhanden aber Write-Error hat es zweimal ins Transcript geschafft. Die Direktausgabe ist hier auch im Transcript gelandet, da es keine Nachverarbeitung gegeben hat. Würde ich das Skript mit "| out-null" aufrufen, wäre der String "Direktausgabe" nicht hier sichtbar.
Die Ausgaben mit "[console]" sind generell nicht sichtbar.
********************** PowerShell transcript start Start time: 20240808184536 Username: NETATWORK\fcarius RunAs User: NETATWORK\fcarius Configuration Name: Machine: FC-T480S (Microsoft Windows NT 10.0.22631.0) Host Application: C:\Program Files\PowerShell\7\pwsh.dll Process ID: 94204 PSVersion: 7.4.4 PSEdition: Core GitCommitId: 7.4.4 OS: Microsoft Windows 10.0.22631 Platform: Win32NT PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0 PSRemotingProtocolVersion: 2.3 SerializationVersion: 1.1.0.1 WSManStackVersion: 3.0 ********************** Transcript started, output file is .\transcript.txt 0WriteHost 1WriteOutput Write-Error: 2WriteError Write-Error: 2WriteError WARNING: 3WriteWarning 7Direktausgabe ********************** PowerShell transcript end End time: 20240808184537 **********************
Pipeline
Wenn ich das gleiche Skript mit "| out-file pipe.txt" aufrufe, dann ändert sich die Ausgabe. Schon die Rückmeldung, dass das Transcript gestartet wurde, ist nicht mehr auf der Konsole sichtbar aber sehr wohl im Transcript selbst. Aber auch andere Meldungen fehlen auf der Konsole. So wird die Ausgabe von "Write-Host" und die Direktausgabe natürlich abgefangen.
Diese landen beide in der PIPE1.TXT-Datei.
Das war so zu erwarten.
Ausgabeumleitung
Eine Ausgabe in einen Stream macht natürlich nur Sinn, wenn die Ausgaben ankommen und auch weiter verarbeitet werden können. dazu kennt PowerShell die Pipeline für die Standardausgabe aber auch Wege die anderen Streams umzuleiten.
- PowerShell redirection operators
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-5.1#powershell-redirection-operators
Also habe ich das gleiche Skript noch mal mit folgenden Parametern gestartet.
PS C:\> .\testwriteoutput.ps1 1>stream1.txt 2>stream2.txt 3>stream3.txt 4>stream4.txt 5>stream5.txt 6>stream6.txt | out-file .\pipe.txt
Die Ausgabe war dann weiter reduziert:
Nur die Ausgaben direkt auf die Konsole wurden ausgegeben. Alle anderen Ausgaben wurden in die entsprechenden Dateien umgeleitet:
Der Inhalt der Dateien war:
Dateiname | Inhalt |
---|---|
pipe.txt |
Diese Datei war leer, obwohl ich doch mit "| out-file" die Pipeline umleiten wollte.. |
stream1.txt |
Transcript started, output file is .\transcript.txt 1WriteOutput 7Direktausgabe Transcript stopped, output file is C:\test\transcript.txt |
stream2.txt |
Write-Error: 2WriteError |
stream3.txt |
3WriteWarning |
stream4.txt |
<leer> |
stream5.txt |
<leer> |
stream6.txt |
0WriteHost Write6Information |
Transcript.txt |
********************** PowerShell transcript start Start time: 20240808185242 Username: NETATWORK\fcarius RunAs User: NETATWORK\fcarius Configuration Name: Machine: FC-T480S (Microsoft Windows NT 10.0.22631.0) Host Application: C:\Program Files\PowerShell\7\pwsh.dll Process ID: 94204 PSVersion: 7.4.4 PSEdition: Core GitCommitId: 7.4.4 OS: Microsoft Windows 10.0.22631 Platform: Win32NT PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0 PSRemotingProtocolVersion: 2.3 SerializationVersion: 1.1.0.1 WSManStackVersion: 3.0 ********************** 0WriteHost Write-Error: 2WriteError ********************** PowerShell transcript end End time: 20240808185243 ********************** |
Ich finde hier folgende Dinge interessant:
- Stream-Umleitung vor Pipeline
Wenn ich beim Aufruf mit "1>stream1.txt den Stream in eine Datei umleite, dann wird er nicht mehr in die klassische Pipeline ausgegeben. Das muss man wissen, aber kann es auch strategisch einsetzen, um z.B. bei einer Verkettung zum Test die Daten einem nachfolgenden Prozess zu entziehen. - Stream4 (Verbose) und Stream5 (Debug)
sind noch leer
Das ist verständlich, weil die Preference bei beiden auf "SilentlyContinue" steht und damit schon der Write-Verbose und Write-Debug keine Ausgabe schreiben - Stream 6 ist mit Write-Information
gefüllt
Dass die normale Ausgabe von "Write-Host" auch im Stream6 (Information) landet, ist bemerkenswert. Noch mehr finde ich interessant, dass auch der Write-Information eine Ausgabe in den Stream schreibt, obwohl auch hier die "InformationPreference=SilentlyContinue" ist. - Write-Host und Write-Information
unterscheiden sich im Transcript
Im Transcript landen mit den aktuellen Preference-Einstellungen nur die Ausgaben von Write-Host und Write-Error aber nicht Write-Information. - Keine Direktausgaben im Transcript
Direkte Ausgaben auf die Konsole landen nicht im Transcript
Insofern gibt es zumindest bei der PS 7.4.4 einen Unterschied zwischen Write-Host und Write-Informationen hinsichtlich des Stream 6 und dem Transcript.
Eine Umleitung von "Wirte-Host" mit z.B. "6> $null" beschleunigt den Ablauf des PowerShell Scripts nur minimal.
Preference Continue
Ich habe das Skript dann noch mal mit "VerbosePreference="Continue" und "DebugPreference="Continue" gestartet. Beim Setzen der Einstellungen ist mir etwas aufgefallen. Der Standard steht auf "SilentlyContinue" und ich habe dann einfach den Wert auf Continue gesetzt. Es kam keine Fehlermeldung aber der Wert wurde auch nicht übernommen. Erst wenn ich "Continue" in Anführungszeichen als String geschrieben habe, wurde er übernommen. Habe ich aber eine Abkürzung verwendet, dann kam ein Fehler. Die Abkürzung konnte ich aber problemlos beim Write-Information mit dem Parameter "-InformationAction" nutzen.
Das Verhalten gibt übrigens generell für alle Preference-Variablen. Achten Sie als darauf, wenn Sie die Preference-Variablen setzen, das Sie diese auch wirklich korrekt setzen.
Dies gilt nicht, wenn Sie im Skript das Schlüsselwort "[CMDLetBinding[]]" verwenden und dann über die Parameter nur für das Skript die Preferences ändern.
Die folgenden Test wurden mit folgender Einstellung durchgeführt:
Zuerst wieder der Aufruf ganz ohne Parameter.
Diesmal sehen wie alle Ausgaben von 1 - 9. Im Transcript landen aber nur die Zeilen 1-7
0WriteHost 1WriteOutput Write-Error: 2WriteError Write-Error: 2WriteError WARNING: 3WriteWarning VERBOSE: 4WriteVerbose DEBUG: 5WriteDebug Write6Information 7Direktausgabe
Nun starte ich das Skript noch einmal mit der Umleitung.
Mit aktivierter Umleitung für alle Streams landen nur die Ausgaben auf der Console auch auf dem Bildschirm. Dafür hat nun jeder Stream-Datei ihren Teil abbekommen.
Allerdings haben sich nun die Inhalte etwas verändert.
Dateiname | Inhalt |
---|---|
pipe.txt |
Diese Datei war leer, obwohl ich doch mit "| out-file" die Pipeline umleiten wollte.. |
stream1.txt |
Transcript started, output file is .\transcript.txt 1WriteOutput 7Direktausgabe Transcript stopped, output file is C:\test\transcript.txt |
stream2.txt |
Write-Error: 2WriteError |
stream3.txt |
3WriteWarning |
stream4.txt |
4WriteVerbose |
stream5.txt |
5WriteDebug |
stream6.txt |
0WriteHost Write6Information |
Transcript.txt |
********************** PowerShell transcript start Start time: 20240808185242 Username: NETATWORK\fcarius RunAs User: NETATWORK\fcarius Configuration Name: Machine: FC-T480S (Microsoft Windows NT 10.0.22631.0) Host Application: C:\Program Files\PowerShell\7\pwsh.dll Process ID: 94204 PSVersion: 7.4.4 PSEdition: Core GitCommitId: 7.4.4 OS: Microsoft Windows 10.0.22631 Platform: Win32NT PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0 PSRemotingProtocolVersion: 2.3 SerializationVersion: 1.1.0.1 WSManStackVersion: 3.0 ********************** 0WriteHost Write-Error: 2WriteError Write6Information ********************** PowerShell transcript end End time: 20240808185243 ********************** |
Auch hier sehen Sie aber, dass bei Stream2 noch ein "Write-Error" davor steht, die "pipe.txt" wieder leer ist weil der Inhalte in stream1.txt gelandet ist und auch im Transcript finde ich die Ausgabe von Write-Information, während die Ausgaben von Write-Verbose, Write-Warning und Write-Debug nicht protokolliert werden.
Insofern stimmt es nicht, dass "Write-Host" nur ein Alias für "[console].out.write" ist und auch wenn Wirte-Host sich auf "Write-Information" stützen mag, sind es dennoch unterschiedliche Streams.
Zudem sollten Sie die Prefixes "Warning, Verbose, Debug" beachten.
- about_Preference_Variables
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables
Übersicht
All die Erkenntnisse habe ich hier in eine Tabelle gebracht. Sie zeigt, welches Ausgaben in welchem Stream landen und ob diese per Default im Transcript landen und bei der Umleitung der Streams
- Transcript Default
Landen die Ausgaben im Transcript mit der Standardeinstellung - Transcript mit Umleitung
Landen die Ausgaben im Transcript, obwohl die Streams mit ">" in eigene Dateien umgeleitet wurden? - Transcript mit Preference
Nun sind alle Ausgaben mit "Continue" aktiviert, also auch Information, Debug, Warning. Welche landen nun im Transcript und welche im Stream
Write Cmdlet | Stream # | Beschreibung | Seit PowerShell | Transcript Default |
Transcript mit Umleitung |
Transcript mit Preference |
Transcript mit Preference und Umleitung |
---|---|---|---|---|---|---|---|
Write-Output |
1 |
Success Stream | 2.0 |
Ja |
Nein |
Nein |
Nein |
Write-Error |
2 |
Error Stream | 2.0 |
Ja |
Ja |
Ja |
Ja |
Write-Warning |
3 |
Warning Stream | 3.0 |
Ja |
Nein |
Ja |
Nein |
Write-Verbose |
4 |
Verbose Stream | 3.0 |
Nein |
Nein |
Ja |
Nein |
Write-Debug |
5 |
Debug Stream | 3.0 |
Nein |
Nein |
Ja |
Nein |
Write-Information |
6 |
Information Stream | 5.0 |
Nein |
Nein |
Ja |
Nein |
Write-Host |
6 |
Information Stream | 5.0 |
Ja |
Ja |
Ja |
Ja |
>* |
All Streams |
|
3.0 |
- |
- |
- |
- |
[console]::Out.WriteLine |
- |
Direkte Ausgabe |
1.0 |
|
|||
[console]::Error.WriteLine |
- |
Direkte Ausgabe |
1.0 |
|
|
|
|
Write-Progress |
Kein |
Kein Streamsupport |
|
Nein |
Nein |
Nein |
Nein |
Die Ausgaben ins Transcript orientieren sich an den Default-Einstellungen. Natürlich können Sie auch die Verbose, Debug und Information-Streams von "SilentlyContinue" auf "Continue" umstellen.
Sonderfall Write-Host"
- Auch wenn viele Artikel früher von Write-Host abgeraten haben, weil es die reine Lehre der PowerShell mit der Pipeline als einzige Ausgabe quasi verrät, war Write-Host immer noch erste Wahl zur Ausgabe von Zwischeninformationen auf die Konsole. Insbesondere bei Ausgaben mit Farben, die mit Write-Verbose (gelb) oder Write-Information (keine Farbe) nicht möglich sind, ist Write-Host immer noch unschlagbar. Seit PowerShell 5 und neuer nutzt Write-Host angeblich im Unterbau "Write-Information". Das stimmt aber nicht ganz, denn Write-Informationen schreibt nur etwas auf die Konsole, wenn $InformationPreference passend gesetzt ist.
Damit ist Write-Host immer noch mein bevorzugtes Werkzeug, um einfache Skripte mit einer Verarbeitungsanzeige zu versehen. Die Daten kann ich ja problemlos in die Pipeline senden.
Streams mit System.Management.Automation
Solange wir uns in einer einzelnen PowerShell bewegen, ist die Zuordnung der Streams noch einfach. Allerdings kennt die PowerShell ja auch eine "Parallelität" und Hintergrundprozesse. Mit Powershell Job, Start-ThreadJob oder PS Runspace. Ich kann z.B. folgenden Code "in den Hintergrund" senden.
Write-Host "Main1" $PSHost1 = [powershell]::Create() [void]$PSHost1.AddScript({ Start-Transcript .\transcript.txt Write-Host "0WriteHost" Write-Output "1WriteOutput" Write-Error "2WriteError" Write-Warning "3WriteWarning" Write-Verbose "4WriteVerbose" Write-Debug "5WriteDebug" Write-Information "Write6Information" "7Direktausgabe" [console]::Out.WriteLine("8ConsoleWrite") [console]::Error.WriteLine("9ConsoleError") Stop-Transcript start-sleep -seconds 5 }) Write-Host "Main2" $PSHost1.Invoke() Write-Host "Main3
Die Ausgabe sieht wie folgt aus. Zuerst sehen Sie, dass die Ausgaben von MAIN1, MAIN2, MAIN3 sequentiell erfolgen. Das Skript wurde also nicht im Hintergrund gestartet und einige Ausgaben wurden direkt auf der Konsole mit ausgegeben. Aber einige Ausgaben sind in der Variable RESULT gelandet.
Der Inhalte von "$result" ist das "PSHOST"-Objekt, welches über den Stream1 die "Write-Output" und "7Direktausgabe" liefert. Die anderen Streams sind aber im Property "Streams" abrufbar. Hier erscheint interessanterweise auch das Property "Progress" welches meines Wissens nach gar kein Stream ist.
Sie sehen aber auch, dass die Streams für Warning, Debug, Verbose und Progress in dem Beispiel leer sind, weil die "Preferences" nicht entsprechend gesetzt wurde. Nur der "Information"-Stream wird nicht nur durch Write-Host sondern auch durch Write-Information gefüllt, obwohl $InformationPreference = 'SilentlyContinue' eingestellt war.
Streams mit InvokeAsync()
Natürlich habe ich das soeben genutzte Script auch noch mal am Ende mit "$pshost.invokeasync()" im Hintergrund gestartet. Ich wollte ja wissen, was dann passiert. Die Ausgabe sah etwas anders aus, da $Result ein Array war, und das erste Element $result[0] ein System.Threading.Task-Objekt war. welches den Status des Hintergrundjobs ausgegeben hat. Ich habe natürlich bis zum Ende des Threads gewartet. Sie sehen am Anfang aber direkt die Rückgabe von "Main1", "Main2" und "Main3" und das Skript "testwriteoutputpshostasync.ps1" ist dann auch geendet.
In die dann weiter aktive Konsole zur Eingabe hat dann aber der Hintergrundthread mit dem "[console]::Out.WriteLine" und "[console]::Error.WriteLine" direkt geschrieben.
Die Ausgaben des Hintergrundthreads auf den Stream 1 (STDOUT) landeten dann aber nicht direkt in $Result sondern muss ich aus dem Property "Result" des Task-Objekts anrufen. Alle anderen Streams finde ich im Property "Streams" des Task-Objekts.
Lernkurve
Schon zu MSDOS-Zeiten gab es die Pipeline und auch Streams. Umleitungen wie 2>& etc. waren aber für die meisten Administratoren eher befremdlich. Die bis zu sechs Streams der PowerShell machen es nicht einfacher und viele Administratoren werden zurecht fragen, wozu das alles denn gut ein soll, wenn man mit "Pipe" immer nur STDOUT/STREAM1 an den nächsten Prozess übergeben kann.
Aus meiner Sicht sind die zusätzlichen Streams durchaus interessant, um unterschiedliche Informationen über getrennte Kanäle an ein anderes Programm zu übergeben. Der Standardstream 1 (STDOUT/STDIN) kann dann zur Übermittlung der reinen Daten dienen, während Diagnoseinformationen, Statusanzeigen oder Verbose-Details über die separaten Streams erfolgen kann. Denken Sie dabei aber daran, dass die Streams für Warning, Debug, Verbose und Progress nur gefüllt werden, wenn Sie die Preferences entsprechend angepasst haben. Nur der "Information"-Stream wird mit Write-Information auch gefüllt, wenn $InformationPreference = 'SilentlyContinue' ist.
Weitere Links
- PS Runspace
- Start-ThreadJob
- PS Job
- PowerShell Pipeline
- Write-Host Logging
- Write-Host Debugging
- about_Output_Streams
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams - PowerShell redirection operators
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection#powershell-redirection-operators - about_CommonParameters
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_commonparameters - about_Preference_Variables
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables - Write-Information
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-information - What's the difference between
"Write-Host", "Write-Output", or "[console]::WriteLine"?
https://stackoverflow.com/questions/8755497/whats-the-difference-between-write-host-write-output-or-consolewrite