Como capturar a saída em uma variável de um processo externo no PowerShell?


158

Gostaria de executar um processo externo e capturar sua saída de comando para uma variável no PowerShell. Atualmente, estou usando isso:

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

Confirmei que o comando está em execução, mas preciso capturar a saída em uma variável. Isso significa que não posso usar o -RedirectOutput porque isso redireciona apenas para um arquivo.


3
Primeiro e mais importante: não use Start-Processpara executar aplicativos de console (por definição externos) de forma síncrona - apenas invoque-os diretamente , como em qualquer shell; a saber: netdom /verify $pc /domain:hosp.uhhg.org. Isso mantém o aplicativo conectado aos fluxos padrão do console de chamada, permitindo que sua saída seja capturada por atribuição simples $output = netdom .... A maioria das respostas dadas abaixo renuncia implicitamente a Start-Processfavor da execução direta.
precisa saber é o seguinte

@ mklement0, exceto talvez se alguém quiser usar o -Credentialparâmetro
CJBS

@CJBS Sim, para executar com uma identidade de usuário diferente , o uso de Start-Processé obrigatório - mas somente então (e se você quiser executar um comando em uma janela separada). E deve-se estar ciente das limitações inevitáveis ​​nesse caso: Nenhuma capacidade de capturar saída, exceto como texto não intercalado nos arquivos , via -RedirectStandardOutpute -RedirectStandardError.
mklement0

Respostas:


161

Você tentou:

$OutputVariable = (Shell command) | Out-String


Tentei atribuí-lo a uma variável usando "=", mas não tentei canalizar a saída para Out-String primeiro. Vou tentar.
111111 Adam Bertram

10
Não entendo o que está acontecendo aqui e não consigo fazê-lo funcionar. "Shell" é uma palavra-chave do PowerShell? Então, na verdade, não usamos o cmdlet Start-Process? Você pode, por favor, dar um exemplo concreto (por exemplo, substituir "Shell" e / ou "comando" por um exemplo real).
deadlydog

@deadlydog Substitua Shell Commandpelo que você deseja executar. É simples assim.
JNK

1
@stej, você está certo. Esclareci principalmente que o código no seu comentário tinha uma funcionalidade diferente do código na resposta. Iniciantes como eu podem se surpreender com diferenças sutis em comportamentos como esses!
Sam

1
@Atique Corri para o mesmo problema. Acontece que o ffmpeg às vezes grava no stderr em vez de no stdout se, por exemplo, você usar a -iopção sem especificar um arquivo de saída. Redirecionar a saída usando 2>&1como descrito em algumas das outras respostas é a solução.
22417 jmbpiano

159

Nota: O comando na pergunta usa Start-Process, o que impede a captura direta da saída do programa de destino. Geralmente, não use Start-Processpara executar aplicativos de console de forma síncrona - apenas invoque-os diretamente , como em qualquer shell. Isso mantém o aplicativo conectado aos fluxos padrão do console de chamada, permitindo que sua saída seja capturada por atribuição simples $output = netdom ..., conforme detalhado abaixo.

Fundamentalmente , a captura de saída de utilitários externos funciona da mesma maneira que com os comandos nativos do PowerShell (você pode querer se atualizar sobre como executar ferramentas externas ):

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

Observe que $cmdOutputrecebe uma matriz de objetos se <command>produz mais de 1 objeto de saída , o que, no caso de um programa externo, significa uma matriz de cadeias contendo as linhas de saída do programa .
Se você deseja $cmdOutputsempre receber uma única sequência - potencialmente com várias linhas - , use
$cmdOutput = <command> | Out-String


Para capturar a saída em uma variável e imprimir na tela :

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

Ou, se <command>for um cmdlet ou uma função avançada , você pode usar o parâmetro comum
-OutVariable/-ov
:

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

Observe que -OutVariable, ao contrário dos outros cenários, sempre$cmdOutput é uma coleção , mesmo que apenas um objeto seja produzido. Especificamente, uma instância do tipo de matriz é retornada. Veja esta edição do GitHub para uma discussão sobre essa discrepância.[System.Collections.ArrayList]


Para capturar a saída de vários comandos , use uma subexpressão ( $(...)) ou chame um bloco de script ( { ... }) com &ou .:

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

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

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

Observe que a necessidade geral de prefixar com &(o operador de chamada) um comando individual cujo nome / caminho é citado - por exemplo, $cmdOutput = & 'netdom.exe' ...- não está relacionado a programas externos per se (ele se aplica igualmente aos scripts do PowerShell), mas é um requisito de sintaxe : PowerShell analisa uma instrução que começa com uma string entre aspas no modo de expressão por padrão, enquanto o modo de argumento é necessário para chamar comandos (cmdlets, programas externos, funções, aliases), o que &garante.

A principal diferença entre $(...)e & { ... }/ . { ... }é que o primeiro coleta toda a entrada na memória antes de devolvê-lo como um todo, enquanto o último transmite a saída, adequado para o processamento de pipeline um por um.


Os redirecionamentos também funcionam da mesma maneira, fundamentalmente (mas veja as advertências abaixo):

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

No entanto, para comandos externos, é mais provável que o seguinte funcione conforme o esperado:

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

Considerações específicas para programas externos :

  • Programas externos , porque operam fora do sistema de tipos do PowerShell, sempre retornam seqüências de caracteres por meio do fluxo de sucesso (stdout).

  • Se a saída contiver mais de 1 linha , o PowerShell, por padrão, a dividirá em uma matriz de seqüências de caracteres . Mais precisamente, as linhas de saída são armazenadas em uma matriz do tipo [System.Object[]]cujos elementos são cadeias de caracteres ( [System.String]).

  • Se você deseja que a saída seja uma única sequência potencialmente com várias linhas , canalize paraOut-String :
    $cmdOutput = <command> | Out-String

  • O redirecionamento do stderr para o stdout2>&1 , para capturá-lo também como parte do fluxo de sucesso, vem com advertências :

    • Para fazer 2>&1stdout merge e stderr na fonte , vamos cmd.exelidar com o redirecionamento , usando as seguintes expressões:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cchama cmd.execom o comando <command>e sai após a <command>conclusão.
      • Observe as aspas simples 2>&1, o que garante que o redirecionamento seja passado para, em cmd.exevez de ser interpretado pelo PowerShell.
      • Observe que envolver cmd.exesignifica que suas regras para escapar caracteres e expandir variáveis ​​de ambiente entram em jogo, por padrão, além dos próprios requisitos do PowerShell; no PS v3 +, você pode usar um parâmetro especial --%(o chamado símbolo de análise de parada ) para desativar a interpretação dos parâmetros restantes pelo PowerShell, exceto para cmd.exereferências de variáveis ​​de ambiente no estilo, como %PATH%.

      • Observe que, como você está mesclando stdout e stderr na fonte com essa abordagem, não será possível distinguir entre linhas originadas por stdout e originadas por stderr no PowerShell; se você precisar dessa distinção, use o próprio 2>&1redirecionamento do PowerShell - veja abaixo.

    • Use o 2>&1 redirecionamento do PowerShell para saber quais linhas vieram de qual fluxo :

      • A saída Stderr é capturada como registros de erro ( [System.Management.Automation.ErrorRecord]), não cadeias, portanto a matriz de saída pode conter uma mistura de cadeias (cada cadeia representando uma linha stdout) e registros de erro (cada registro representando uma linha stderr) . Observe que, conforme solicitado por 2>&1, as seqüências de caracteres e os registros de erro são recebidos pelo fluxo de saída de sucesso do PowerShell ).

      • No console, os registros de erro são impressos em vermelho , e o primeiro, por padrão, produz exibição em várias linhas , no mesmo formato que o erro sem fim de um cmdlet seria exibido; os registros de erro subsequentes também são impressos em vermelho, mas apenas imprimem sua mensagem de erro em uma única linha .

      • Ao enviar para o console , as sequências geralmente vêm primeiro na matriz de saída, seguidas pelos registros de erro (pelo menos entre um lote de linhas stdout / stderr emitidas "ao mesmo tempo"), mas, felizmente, quando você captura a saída , é intercalado adequadamente , usando a mesma ordem de saída que você obteria sem 2>&1; em outras palavras: ao enviar para o console , a saída capturada NÃO reflete a ordem na qual as linhas stdout e stderr foram geradas pelo comando externo.

      • Se você capturar toda a saída em uma única string comOut-String , o PowerShell adicionará linhas extras , porque a representação da string de um registro de erro contém informações adicionais, como local ( At line:...) e categoria ( + CategoryInfo ...); curiosamente, isso se aplica apenas ao primeiro registro de erro.

        • Para contornar esse problema, aplicar o .ToString()método para cada objeto em vez de tubulação de saída Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          no PS v3 +, você pode simplificar:
          $cmdOutput = <command> 2>&1 | % ToString
          (Como bônus, se a saída não for capturada, isso produzirá uma saída adequadamente intercalada, mesmo ao imprimir no console).
      • Alternativamente, filtrar os registros de erro fora e enviá-los para fluxo de erro do PowerShell comWrite-Error (como um bônus, se a saída não é capturado, este produz uma saída propriamente intercalado mesmo quando a impressão para o console):

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

Isso acabou funcionando para mim depois que eu peguei meu caminho executável E meus argumentos para ele, joguei-os em uma string e o tratei como meu <comando>.
Dan

2
@ Dan: Quando o próprio PowerShell interpreta <command>, você não deve combinar o executável e seus argumentos em uma única sequência; com a invocação via cmd /cvocê pode fazê-lo, e isso depende da situação, faça sentido ou não. A que cenário você está se referindo e você pode dar um exemplo mínimo?
usar o seguinte comando

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

1
@ Dan: Sim, isso funciona, embora você não precise da variável intermediária e da construção explícita da string com o +operador; o seguinte também funciona: cmd /c c:\mycommand.exe $Args '2>&1'- O PowerShell se encarrega de passar os elementos $Argscomo uma sequência separada por espaço, nesse caso, um recurso chamado splatting .
precisa saber é o seguinte

Finalmente, uma resposta adequada que funciona no PS6.1 +. O segredo do molho é realmente a '2>&1'parte, e não está incluído em ()muitos roteiros.
precisa saber é o seguinte

24

Se você deseja redirecionar também a saída de erro, é necessário:

$cmdOutput = command 2>&1

Ou, se o nome do programa tiver espaços:

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

4
O que significa 2> & 1? 'comando run chamado 2 e colocar sua saída no comando run chamado 1'?
Richard

7
Significa "redirecionar a saída de erro padrão (descritor de arquivo 2) para o mesmo local para onde está indo a saída padrão (descritor de arquivo 1)". Basicamente, redireciona as mensagens normais e de erro para o mesmo local (nesse caso, o console, se o stdout não for redirecionado para outro lugar - como um arquivo).
Giovanni Tirloni

11

Ou tente isso. Ele capturará a saída na variável $ scriptOutput:

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

$scriptOutput

7
-1, desnecessariamente complexo. $scriptOutput = & "netdom.exe" $params
charlesb

8
Remover o nulo de saída e isso é ótimo para canalizar para o shell e uma variável ao mesmo tempo.
ferventcoder

10

Outro exemplo da vida real:

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

Observe que este exemplo inclui um caminho (que começa com uma variável de ambiente). Observe que as aspas devem envolver o caminho e o arquivo EXE, mas não os parâmetros!

Nota: Não esqueça o &caractere na frente do comando, mas fora das aspas.

A saída de erro também é coletada.

Demorei um pouco para que essa combinação funcionasse, então pensei em compartilhá-la.


8

Tentei as respostas, mas no meu caso não obtive a saída bruta. Em vez disso, foi convertido em uma exceção do PowerShell.

O resultado bruto que obtive com:

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

2

Eu tenho o seguinte para trabalhar:

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

O resultado $ fornece as informações necessárias


1

Essa coisa funcionou para mim:

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

0

Se tudo o que você está tentando fazer é capturar a saída de um comando, isso funcionará bem.

Eu o uso para alterar a hora do sistema, pois [timezoneinfo]::localsempre produz as mesmas informações, mesmo depois de você fazer alterações no sistema. Esta é a única maneira de validar e registrar a alteração no fuso horário:

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

Significando que eu tenho que abrir uma nova sessão do PowerShell para recarregar as variáveis ​​do sistema.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.