Come acquisisco l'output in una variabile da un processo esterno in PowerShell?


158

Vorrei eseguire un processo esterno e acquisire l'output del comando in una variabile in PowerShell. Attualmente sto usando questo:

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

Ho confermato che il comando è in esecuzione ma devo catturare l'output in una variabile. Questo significa che non riesco a usare -RedirectOutput perché questo reindirizza solo a un file.


3
Prima di tutto: non utilizzare Start-Processper eseguire (per definizione esterne) applicazioni console in modo sincrono - basta invocarle direttamente , come in qualsiasi shell; vale a dire: netdom /verify $pc /domain:hosp.uhhg.org. In questo modo l'applicazione viene connessa ai flussi standard della console chiamante, consentendo di catturare l'output mediante una semplice assegnazione $output = netdom .... La maggior parte delle risposte fornite di seguito implicitamente rinuncia Start-Processa favore dell'esecuzione diretta.
mklement0

@ mklement0 tranne forse se si desidera utilizzare il -Credentialparametro
CJBS l'

@CJBS Sì, per funzionare con una diversa identità utente , l'uso di Start-Processè un must, ma solo allora (e se si desidera eseguire un comando in una finestra separata). E si dovrebbe essere consapevoli delle inevitabili limitazioni in quel caso: nessuna capacità di catturare l'output, tranne come testo non interlacciato in file , tramite -RedirectStandardOutpute -RedirectStandardError.
mklement0

Risposte:


161

Hai provato:

$OutputVariable = (Shell command) | Out-String


Ho provato ad assegnarlo a una variabile usando "=" ma non ho provato a reindirizzare l'output a Out-String. Ci proverò.
Adam Bertram,

10
Non capisco cosa sta succedendo qui e non riesco a farlo funzionare. "Shell" è una parola chiave PowerShell? Quindi non utilizziamo effettivamente il cmdlet Start-Process? Potete per favore dare un esempio concreto per favore (ad esempio sostituire "Shell" e / o "comando" con un esempio reale).
cane mortale,

@deadlydog Sostituisci Shell Commandcon quello che vuoi eseguire. È così semplice.
JNK,

1
@stej, hai ragione. Stavo principalmente chiarendo che il codice nel tuo commento aveva funzionalità diverse rispetto al codice nella risposta. I principianti come me possono essere respinti da sottili differenze di comportamento come questi!
Sam,

1
@Atique Ho riscontrato lo stesso problema. Si scopre che a volte ffmpeg scriverà su stderr invece che su stdout se, ad esempio, si utilizza l' -iopzione senza specificare un file di output. Reindirizzare l'output usando 2>&1come descritto in alcune delle altre risposte è la soluzione.
jmbpiano,

159

Nota: viene utilizzato il comando nella domanda Start-Process, che impedisce l'acquisizione diretta dell'output del programma di destinazione. Generalmente, non utilizzare Start-Processper eseguire le applicazioni della console in modo sincrono, ma invocarle direttamente , come in qualsiasi shell. In questo modo l'applicazione viene connessa ai flussi standard della console di chiamata, consentendo di catturare l'output mediante una semplice assegnazione $output = netdom ..., come descritto di seguito.

Fondamentalmente , catturare l'output da utility esterne funziona allo stesso modo dei comandi nativi di PowerShell (potresti voler avere un aggiornamento su come eseguire strumenti esterni ):

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

Si noti che $cmdOutputriceve una matrice di oggetti se <command>produce più di 1 oggetto di output , che nel caso di un programma esterno significa una matrice di stringhe contenente le linee di output del programma .
Se si desidera $cmdOutputricevere sempre una stringa singola , potenzialmente multilinea , utilizzare
$cmdOutput = <command> | Out-String


Per acquisire l' output in una variabile e stamparlo sullo schermo :

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

Oppure, se si <command>tratta di un cmdlet o di una funzione avanzata , è possibile utilizzare il parametro comune
-OutVariable/-ov
:

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

Si noti che con -OutVariable, a differenza degli altri scenari, $cmdOutputè sempre una raccolta , anche se viene emesso un solo oggetto. In particolare, [System.Collections.ArrayList]viene restituita un'istanza del tipo di matrice .
Vedi questo numero di GitHub per una discussione di questa discrepanza.


Per acquisire l'output da più comandi , utilizzare una sottoespressione ( $(...)) o chiamare un blocco di script ( { ... }) con &o .:

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

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

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

Si noti che la necessità generale di aggiungere un prefisso &(l'operatore di chiamata) a un singolo comando il cui nome / percorso è citato , ad esempio, $cmdOutput = & 'netdom.exe' ...non è correlato ai programmi esterni di per sé (si applica ugualmente agli script di PowerShell), ma è un requisito di sintassi : PowerShell analizza un'istruzione che inizia con una stringa tra virgolette in modalità espressione per impostazione predefinita, mentre la modalità argomento è necessaria per invocare comandi (cmdlet, programmi esterni, funzioni, alias), che è ciò che &garantisce.

La differenza chiave tra $(...)e & { ... }/ . { ... }è che il primo raccoglie tutti gli input in memoria prima di restituirli nel loro insieme, mentre i secondi trasmettono l'output, adatto per l'elaborazione di una pipeline uno per uno.


Anche i reindirizzamenti funzionano allo stesso modo, fondamentalmente (ma vedi le avvertenze di seguito):

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

Tuttavia, per i comandi esterni è più probabile che i seguenti funzionino come previsto:

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

Considerazioni specifiche per i programmi esterni :

  • I programmi esterni , poiché operano al di fuori del sistema di tipi di PowerShell, restituiscono sempre le stringhe solo attraverso il loro flusso di successo (stdout).

  • Se l'output contiene più di 1 riga , PowerShell per impostazione predefinita lo suddivide in una matrice di stringhe . Più precisamente, le righe di output sono memorizzate in una matrice di tipo i [System.Object[]]cui elementi sono stringhe ( [System.String]).

  • Se si desidera che l'output sia una stringa singola , potenzialmente multilinea , pipe aOut-String :
    $cmdOutput = <command> | Out-String

  • Il reindirizzamento di stderr su stdout2>&1 , in modo da catturarlo anche come parte del flusso di successo, viene fornito con avvertenze :

    • Per 2>&1unire stdout e stderr all'origine , gestiamo il reindirizzamento , usando i seguenti modi di direcmd.exe :
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cinvoca cmd.execon comando <command>ed esce al <command>termine.
      • Nota le virgolette singole in giro 2>&1, che assicurano che il reindirizzamento venga passato cmd.exeanziché essere interpretato da PowerShell.
      • Si noti che coinvolgere cmd.exesignifica che entrano in gioco le sue regole per sfuggire ai caratteri e espandere le variabili di ambiente, per impostazione predefinita oltre ai requisiti di PowerShell; in PS v3 + è possibile utilizzare un parametro speciale --%(il cosiddetto simbolo di arresto dell'analisi ) per disattivare l'interpretazione dei parametri rimanenti da PowerShell, ad eccezione di cmd.exeriferimenti a variabili di ambiente in stile come %PATH%.

      • Nota che dal momento che stai fondendo stdout e stderr alla fonte con questo approccio, non sarai in grado di distinguere tra le linee originate da stdout e originate da stderr in PowerShell; se hai bisogno di questa distinzione, usa il 2>&1reindirizzamento di PowerShell, vedi sotto.

    • Usa il 2>&1 reindirizzamento di PowerShell per sapere quali linee provengono da quale stream :

      • L' output di Stderr viene acquisito come record di errore ( [System.Management.Automation.ErrorRecord]), non stringhe, quindi l' array di output può contenere un mix di stringhe (ciascuna stringa che rappresenta una riga stdout) e record di errore (ogni record che rappresenta una riga stderr) . Si noti che, come richiesto da 2>&1, sia le stringhe che i record di errore vengono ricevuti tramite il flusso di output di successo di PowerShell ).

      • Nella console, i record di errore vengono stampati in rosso e il primo per impostazione predefinita produce una visualizzazione a più righe , nello stesso formato che verrà visualizzato l'errore non terminante di un cmdlet; i successivi record di errore vengono stampati anche in rosso, ma stampano il loro messaggio di errore solo su una riga .

      • Quando si esegue l' output alla console , le stringhe in genere vengono prima nell'array di output, seguite dai record di errore (almeno tra un batch di righe stdout / stderr output "allo stesso tempo"), ma, fortunatamente, quando si acquisisce l'output , è opportunamente interfogliato , utilizzando lo stesso ordine di output senza cui si otterrebbe 2>&1; in altre parole: quando si invia alla console , l'output catturato NON riflette l'ordine in cui le linee stdout e stderr sono state generate dal comando esterno.

      • Se si acquisisce l'intero output in una singola stringa conOut-String , PowerShell aggiungerà ulteriori righe , poiché la rappresentazione in formato stringa di un record di errore contiene informazioni aggiuntive come location ( At line:...) e category ( + CategoryInfo ...); curiosamente, questo vale solo per il primo record di errore.

        • Per aggirare questo problema, applicare il .ToString()metodo di ogni oggetto uscita invece di tubazioni a Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          in PS v3 + puoi semplificare a:
          $cmdOutput = <command> 2>&1 | % ToString
          (Come bonus, se l'output non viene catturato, questo produce output correttamente interfogliati anche quando si stampa sulla console.)
      • In alternativa, filtrare i record di errore fuori e li invia al flusso di errore di PowerShell conWrite-Error (come bonus, se l'uscita non viene catturato, questo produce in uscita correttamente Interleaved anche quando si stampa alla console):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}

Questo alla fine ha funzionato per me dopo che ho preso il mio percorso eseguibile E i miei argomenti per esso, li ho lanciati in una stringa e li ho trattati come il mio <comando>.
Dan

2
@Dan: quando PowerShell stesso interpreta <command>, non è necessario combinare l'eseguibile e i suoi argomenti in un'unica stringa; con l'invocazione tramite cmd /cte può farlo, e dipende dalla situazione se ha senso o no. A quale scenario ti riferisci e puoi fare un esempio minimo?
mklement0

Funziona: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ command '2> & 1'
Dan

1
@ Dan: Sì, funziona, sebbene non sia necessaria la variabile intermedia e la costruzione esplicita della stringa con l' +operatore; funziona anche il seguente: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell si occupa di passare gli elementi $Argscome una stringa separata dallo spazio in quel caso, una funzione chiamata splatting .
mklement0

Finalmente una risposta corretta che funziona in PS6.1 +. Il segreto nella salsa è davvero la '2>&1'parte, e non racchiusa in ()come molti script tendono a fare.
not2qubit

24

Se si desidera reindirizzare anche l'output dell'errore, è necessario:

$cmdOutput = command 2>&1

Oppure, se il nome del programma contiene spazi:

$cmdOutput = & "command with spaces" 2>&1

4
Cosa significa 2> & 1? 'comando di comando chiamato 2 e mettere il suo output nel comando di comando chiamato 1'?
Richard

7
Significa "reindirizzare l'output di errore standard (descrittore di file 2) nella stessa posizione in cui sta andando l'output standard (descrittore di file 1)". Fondamentalmente, reindirizza i messaggi normali e di errore nello stesso posto (in questo caso la console, se stdout non viene reindirizzato da qualche altra parte, come un file).
Giovanni Tirloni,

11

Oppure prova questo. Catturerà l'output nella variabile $ scriptOutput:

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

$scriptOutput

7
-1, inutilmente complesso. $scriptOutput = & "netdom.exe" $params
CharlesB,

8
Rimuovere out-null e questo è ottimo per il piping sia alla shell che a una variabile contemporaneamente.
ferventcoder,

10

Un altro esempio di vita reale:

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

Si noti che questo esempio include un percorso (che inizia con una variabile di ambiente). Nota che le virgolette devono circondare il percorso e il file EXE, ma non i parametri!

Nota: non dimenticare il &carattere davanti al comando, ma al di fuori delle virgolette.

Viene anche raccolto l'output dell'errore.

Mi ci è voluto un po 'per far funzionare questa combinazione, quindi ho pensato di condividerla.


8

Ho provato le risposte, ma nel mio caso non ho ottenuto l'output non elaborato. Invece è stato convertito in un'eccezione di PowerShell.

Il risultato grezzo che ho ottenuto con:

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

2

Ho ottenuto quanto segue per funzionare:

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

$ result ti dà il necessario


1

Questa cosa ha funzionato per me:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)

0

Se tutto ciò che stai cercando di fare è acquisire l'output da un comando, allora funzionerà bene.

Lo uso per modificare l'ora del sistema, poiché [timezoneinfo]::localproduce sempre le stesse informazioni, anche dopo aver apportato modifiche al sistema. Questo è l'unico modo in cui posso convalidare e registrare la modifica nel fuso orario:

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

Ciò significa che devo aprire una nuova sessione di PowerShell per ricaricare le variabili di sistema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.