Wie erfasse ich die Ausgabe eines externen Prozesses in PowerShell in eine Variable?

158

Ich möchte einen externen Prozess ausführen und dessen Befehlsausgabe in einer Variablen in PowerShell erfassen. Ich benutze derzeit Folgendes:

$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait

Ich habe bestätigt, dass der Befehl ausgeführt wird, aber ich muss die Ausgabe in einer Variablen erfassen. Dies bedeutet, dass ich den -RedirectOutput nicht verwenden kann, da dieser nur zu einer Datei umleitet.

Adam Bertram
quelle
3
In erster Linie: Verwenden Sie Start-Processdiese Option nicht , um (per Definition externe) Konsolenanwendungen synchron auszuführen. Rufen Sie sie einfach direkt auf , wie in jeder Shell. zu wissen : netdom /verify $pc /domain:hosp.uhhg.org. Auf diese Weise bleibt die Anwendung mit den Standard-Streams der aufrufenden Konsole verbunden, sodass die Ausgabe durch einfache Zuweisung erfasst werden kann $output = netdom .... Die meisten der unten angegebenen Antworten verzichten implizit Start-Processzugunsten einer direkten Ausführung.
mklement0
@ mklement0 außer vielleicht, wenn man den -CredentialParameter verwenden möchte
CJBS
@CJBS Ja, um mit einer anderen Benutzeridentität ausgeführt zu werden , ist die Verwendung von Start-Processein Muss - aber nur dann (und wenn Sie einen Befehl in einem separaten Fenster ausführen möchten). Und man sollte sich der unvermeidbaren Einschränkungen in diesem Fall bewusst sein: Keine Möglichkeit, die Ausgabe zu erfassen, außer als - nicht verschachtelter - Text in Dateien über -RedirectStandardOutputund -RedirectStandardError.
mklement0

Antworten:

161

Hast du es versucht:

$OutputVariable = (Shell command) | Out-String

JNK
quelle
Ich habe versucht, es mit "=" einer Variablen zuzuweisen, aber ich habe nicht versucht, die Ausgabe zuerst an Out-String weiterzuleiten. Ich werde es versuchen.
Adam Bertram
10
Ich verstehe nicht, was hier passiert und kann es nicht zum Laufen bringen. Ist "Shell" ein Powershell-Schlüsselwort? Wir verwenden also nicht das Cmdlet Start-Process? Können Sie bitte ein konkretes Beispiel geben (dh "Shell" und / oder "Befehl" durch ein echtes Beispiel ersetzen).
tödlicher Hund
@deadlydog Ersetzen Sie Shell Commanddurch alles, was Sie ausführen möchten. So einfach ist das.
JNK
1
@stej, du hast recht. Ich habe hauptsächlich klargestellt, dass der Code in Ihrem Kommentar eine andere Funktionalität hat als der Code in der Antwort. Anfänger wie ich können durch subtile Verhaltensunterschiede wie diese abwerfen!
Sam
1
@Atique Ich bin auf das gleiche Problem gestoßen. Es stellt sich heraus, dass ffmpeg manchmal in stderr anstelle von stdout schreibt, wenn Sie beispielsweise die -iOption verwenden, ohne eine Ausgabedatei anzugeben. 2>&1Die Lösung besteht darin, die Ausgabe wie in einigen anderen Antworten beschrieben umzuleiten.
jmbpiano
159

Hinweis: Der Befehl in der Frage verwendet Start-Process, wodurch die direkte Erfassung der Ausgabe des Zielprogramms verhindert wird. Im Allgemeinen verwenden Sie keine Start-ProcessKonsolenanwendungen synchron auszuführen - nur invoke sie direkt , wie in jeder Schale. Auf diese Weise bleibt die Anwendung mit den Standard-Streams der aufrufenden Konsole verbunden, sodass die Ausgabe durch einfache Zuweisung erfasst werden $output = netdom ...kann (siehe unten).

Grundsätzlich funktioniert das Erfassen der Ausgabe von externen Dienstprogrammen genauso wie bei PowerShell-nativen Befehlen (möglicherweise möchten Sie eine Auffrischung zum Ausführen externer Tools ):

$cmdOutput = <command>   # captures the command's success stream / stdout

Man beachte , dass $cmdOutputein empfängt Array von Objekten , wenn <command>produziert mehr als 1 Ausgangsobjekt , das in dem Fall eines externen Programms bedeutet einen String - Array enthält die Ausgabe der Programmzeilen .
Wenn Sie $cmdOutputimmer eine einzelne - möglicherweise mehrzeilige - Zeichenfolge erhalten möchten , verwenden Sie
$cmdOutput = <command> | Out-String


So erfassen Sie die Ausgabe in einer Variablen und drucken auf dem Bildschirm :

<command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed

Wenn <command>es sich um ein Cmdlet oder eine erweiterte Funktion handelt, können Sie den allgemeinen Parameter
-OutVariable/ verwenden-ov
:

<command> -OutVariable cmdOutput   # cmdlets and advanced functions only

Beachten Sie, dass mit -OutVariable, im Gegensatz zu den anderen Szenarien $cmdOutputist immer eine Sammlung , auch wenn nur ein Objekt ausgegeben wird. Insbesondere wird eine Instanz vom Array-ähnlichen [System.Collections.ArrayList]Typ zurückgegeben.
In dieser GitHub-Ausgabe finden Sie eine Diskussion dieser Diskrepanz.


Verwenden Sie zum Erfassen der Ausgabe mehrerer Befehle entweder einen Unterausdruck ( $(...)) oder rufen Sie einen Skriptblock ( { ... }) mit &oder auf .:

$cmdOutput = $(<command>; ...)  # subexpression

$cmdOutput = & {<command>; ...} # script block with & - creates child scope for vars.

$cmdOutput = . {<command>; ...} # script block with . - no child scope

Beachten Sie, dass die allgemeine Notwendigkeit, &einem einzelnen Befehl (dessen Aufruf / Pfad) in Anführungszeichen steht (z. B.), $cmdOutput = & 'netdom.exe' ...nicht mit externen Programmen an sich zusammenhängt (dies gilt auch für PowerShell-Skripte), sondern eine Syntaxanforderung ist : PowerShell Analysiert eine Anweisung, die standardmäßig mit einer Zeichenfolge in Anführungszeichen im Ausdrucksmodus beginnt , während der Argumentmodus zum Aufrufen von Befehlen (Cmdlets, externe Programme, Funktionen, Aliase) erforderlich ist &.

Der Hauptunterschied zwischen $(...)und & { ... }/ und . { ... }besteht darin, dass erstere alle Eingaben im Speicher sammeln, bevor sie als Ganzes zurückgegeben werden, während letztere die Ausgabe streamen , die für die Eins-zu-Eins-Pipeline-Verarbeitung geeignet ist.


Weiterleitungen funktionieren grundsätzlich genauso (siehe jedoch die folgenden Einschränkungen):

$cmdOutput = <command> 2>&1 # redirect error stream (2) to success stream (1)

Bei externen Befehlen funktioniert jedoch mit größerer Wahrscheinlichkeit Folgendes wie erwartet:

$cmdOutput = cmd /c <command> '2>&1' # Let cmd.exe handle redirection - see below.

Überlegungen zu externen Programmen:

  • Externe Programme geben , da sie außerhalb des PowerShell-Typsystems ausgeführt werden, immer nur Zeichenfolgen über ihren Erfolgsstrom (stdout) zurück.

  • Wenn die Ausgabe mehr als eine Zeile enthält , teilt PowerShell sie standardmäßig in ein Array von Zeichenfolgen auf . Genauer gesagt werden die Ausgabezeilen in einem Array vom Typ gespeichert, [System.Object[]]dessen Elemente Zeichenfolgen ( [System.String]) sind.

  • Wenn die Ausgabe eine einzelne , möglicherweise mehrzeilige Zeichenfolge sein soll , leiten Sie Folgendes anOut-String :
    $cmdOutput = <command> | Out-String

  • Das Umleiten von stderr zu stdout mit2>&1 , um es auch als Teil des Erfolgsstroms zu erfassen, ist mit folgenden Einschränkungen verbunden :

    • Um 2>&1stdout und stderr an der Quelle zusammenzuführen , lassen Sie cmd.exedie Umleitung mit den folgenden Redewendungen ablaufen:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cwird cmd.exemit Befehl aufgerufen <command>und nach <command>Beendigung beendet.
      • Beachten Sie die einfachen Anführungszeichen, um 2>&1sicherzustellen, dass die Umleitung an cmd.exePowerShell übergeben und nicht von PowerShell interpretiert wird.
      • Beachten Sie, dass das Einbeziehen cmd.exebedeutet, dass die Regeln für das Escapezeichen von Zeichen und das Erweitern von Umgebungsvariablen standardmäßig zusätzlich zu den eigenen Anforderungen von PowerShell ins Spiel kommen. In PS v3 + können Sie spezielle Parameter --%(das sogenannte Stop-Parsing-Symbol ) verwenden, um die Interpretation der verbleibenden Parameter durch PowerShell zu cmd.exedeaktivieren , mit Ausnahme von Verweisen auf Umgebungsvariablen im Stil wie %PATH%.

      • Da Sie mit diesem Ansatz stdout und stderr an der Quelle zusammenführen , können Sie in PowerShell nicht zwischen Zeilen mit stdout- und stderr-Ursprung unterscheiden . Wenn Sie diese Unterscheidung benötigen, verwenden Sie die PowerShell-eigene 2>&1Umleitung - siehe unten.

    • Verwenden Sie die 2>&1 Umleitung von PowerShell, um festzustellen, welche Zeilen von welchem ​​Stream stammen :

      • Die Stderr- Ausgabe wird als Fehlerdatensätze ( ) und nicht als Zeichenfolgen erfasst[System.Management.Automation.ErrorRecord] , sodass das Ausgabearray eine Mischung aus Zeichenfolgen (jede Zeichenfolge repräsentiert eine Standardzeile) und Fehlerdatensätzen (jeder Datensatz repräsentiert eine Standardzeile) enthalten kann . Beachten Sie, dass auf Anforderung 2>&1sowohl die Zeichenfolgen als auch die Fehlerdatensätze über den Erfolgsausgabestream von PowerShell empfangen werden .

      • In der Konsole werden die Fehlerdatensätze rot gedruckt , und der erste erzeugt standardmäßig eine mehrzeilige Anzeige in demselben Format, in dem der nicht terminierende Fehler eines Cmdlets angezeigt wird. nachfolgende Fehlersätze in rot gedruckt werden als gut, aber nur ihre Fehler drucken Nachricht , auf einer einzigen Zeile .

      • Bei der Ausgabe an die Konsole , die Saiten der Regel kommen zuerst in der Ausgangsanordnung, die durch die Fehlersätze gefolgt (zumindest bei einer Charge von stdout / stderr Linien Ausgang „zur gleichen Zeit“), aber, zum Glück, wenn Sie erfassen die Ausgabe Es ist richtig verschachtelt und verwendet dieselbe Ausgabereihenfolge, die Sie ohne erhalten würden 2>&1. Mit anderen Worten: Bei der Ausgabe an die Konsole spiegelt die erfasste Ausgabe NICHT die Reihenfolge wider, in der stdout- und stderr-Zeilen vom externen Befehl generiert wurden.

      • Wenn Sie die gesamte Ausgabe in einer einzelnen Zeichenfolge mit erfassenOut-String , fügt PowerShell zusätzliche Zeilen hinzu , da die Zeichenfolgendarstellung eines Fehlerdatensatzes zusätzliche Informationen wie location ( At line:...) und category ( + CategoryInfo ...) enthält. Seltsamerweise gilt dies nur für den ersten Fehlerdatensatz.

        • Wenden Sie die .ToString()Methode auf jedes Ausgabeobjekt an, um dieses Problem zu umgehen, anstatt auf Folgendes zu verweisen Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          In PS v3 + können Sie Folgendes vereinfachen:
          $cmdOutput = <command> 2>&1 | % ToString
          (Wenn die Ausgabe nicht erfasst wird, wird als Bonus auch beim Drucken auf der Konsole eine ordnungsgemäß verschachtelte Ausgabe erstellt.)
      • Alternativ können Sie die Fehleraufzeichnungen herausfiltern und an den Fehlerstrom von PowerShell senden mitWrite-Error (als Bonus führt dies dazu, dass die Ausgabe auch beim Drucken auf der Konsole ordnungsgemäß verschachtelt wird, wenn die Ausgabe nicht erfasst wird):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}
mklement0
quelle
Dies funktionierte schließlich für mich, nachdem ich meinen ausführbaren Pfad UND meine Argumente dafür genommen, sie in eine Zeichenfolge geworfen und als meinen <Befehl> behandelt hatte.
Dan
2
@ Dan: Wenn Powershell selbst interpretiert <command>, Sie nicht müssen die ausführbare Datei und ihre Argumente in einer einzigen Zeichenfolge kombinieren; mit Aufruf über cmd /cSie können tun so, und es hängt von der Situation , ob es Sinn macht oder nicht. Auf welches Szenario beziehen Sie sich und können Sie ein minimales Beispiel geben?
mklement0
Funktioniert: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ Befehl '2> & 1'
Dan
1
@Dan: Ja, das funktioniert, obwohl Sie die Zwischenvariable und die explizite Konstruktion der Zeichenfolge mit dem +Operator nicht benötigen . Folgendes funktioniert ebenfalls: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell sorgt dafür, dass die Elemente $Argsin diesem Fall als durch Leerzeichen getrennte Zeichenfolge übergeben werden . Diese Funktion wird als Splatting bezeichnet .
mklement0
Endlich eine richtige Antwort, die in PS6.1 + funktioniert. Das Geheimnis in der Sauce ist in der Tat der '2>&1'Teil, und nicht ()so viele Skripte enthalten, wie es normalerweise der Fall ist .
not2qubit
24

Wenn Sie auch die Fehlerausgabe umleiten möchten, müssen Sie Folgendes tun:

$cmdOutput = command 2>&1

Oder wenn der Programmname Leerzeichen enthält:

$cmdOutput = & "command with spaces" 2>&1
Manojlds
quelle
4
Was bedeutet 2> & 1? 'Befehl run 2 ausführen und Ausgabe in Befehl run 1 ausführen'?
Richard
7
Dies bedeutet "Umleiten der Standardfehlerausgabe (Dateideskriptor 2) an dieselbe Stelle, an der die Standardausgabe (Dateideskriptor 1) ausgeführt wird". Leitet normale und Fehlermeldungen grundsätzlich an dieselbe Stelle um (in diesem Fall die Konsole, wenn stdout nicht an eine andere Stelle umgeleitet wird - wie eine Datei).
Giovanni Tirloni
11

Oder versuchen Sie es. Die Ausgabe wird in der Variablen $ scriptOutput erfasst:

& "netdom.exe" $params | Tee-Object -Variable scriptOutput | Out-Null

$scriptOutput
Finzzownt
quelle
7
-1, unnötig komplex. $scriptOutput = & "netdom.exe" $params
CharlesB
8
Das Entfernen der out-null und dies ist ideal, um gleichzeitig sowohl an die Shell als auch an eine Variable weiterzuleiten.
Ferventcoder
10

Ein weiteres Beispiel aus dem wirklichen Leben:

$result = & "$env:cust_tls_store\Tools\WDK\x64\devcon.exe" enable $strHwid 2>&1 | Out-String

Beachten Sie, dass dieses Beispiel einen Pfad enthält (der mit einer Umgebungsvariablen beginnt). Beachten Sie, dass die Anführungszeichen den Pfad und die EXE-Datei umgeben müssen, nicht jedoch die Parameter!

Hinweis: Vergessen Sie nicht das &Zeichen vor dem Befehl, sondern außerhalb der Anführungszeichen.

Die Fehlerausgabe wird ebenfalls erfasst.

Es hat eine Weile gedauert, bis diese Kombination funktioniert hat, also dachte ich, ich würde sie teilen.

Davidiam
quelle
8

Ich habe die Antworten ausprobiert, aber in meinem Fall habe ich die Rohausgabe nicht erhalten. Stattdessen wurde es in eine PowerShell-Ausnahme konvertiert.

Das rohe Ergebnis, mit dem ich kam:

$rawOutput = (cmd /c <command> 2`>`&1)
Sevenforce
quelle
2

Ich habe folgendes zum Arbeiten:

$Command1="C:\\ProgramData\Amazon\Tools\ebsnvme-id.exe"
$result = & invoke-Expression $Command1 | Out-String

$ Ergebnis gibt Ihnen das Notwendige

Faye Smelter
quelle
1

Dieses Ding hat bei mir funktioniert:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)
Alex R.
quelle
0

Wenn Sie nur versuchen, die Ausgabe eines Befehls zu erfassen, funktioniert dies gut.

Ich verwende es zum Ändern der Systemzeit, da [timezoneinfo]::localimmer die gleichen Informationen erzeugt werden, auch nachdem Sie Änderungen am System vorgenommen haben. Nur so kann ich die Änderung der Zeitzone validieren und protokollieren:

$NewTime = (powershell.exe -command [timezoneinfo]::local)
$NewTime | Tee-Object -FilePath $strLFpath\$strLFName -Append

Das bedeutet, dass ich eine neue PowerShell- Sitzung öffnen muss , um die Systemvariablen neu zu laden.

Kelly Davis
quelle