¿Cómo capturo el resultado en una variable desde un proceso externo en PowerShell?

158

Me gustaría ejecutar un proceso externo y capturar su salida de comando a una variable en PowerShell. Actualmente estoy usando esto:

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

He confirmado que el comando se está ejecutando, pero necesito capturar la salida en una variable. Esto significa que no puedo usar -RedirectOutput porque esto solo redirige a un archivo.

Adam Bertram
fuente
3
Primero y más importante: no lo use Start-Processpara ejecutar (por definición externo) aplicaciones de consola de forma síncrona; simplemente invoque directamente , como en cualquier shell; a saber: netdom /verify $pc /domain:hosp.uhhg.org. Al hacerlo, la aplicación se conecta a las transmisiones estándar de la consola de llamada, lo que permite capturar su salida mediante una simple asignación $output = netdom .... La mayoría de las respuestas dadas a continuación implícitamente renuncian Start-Processa favor de la ejecución directa.
mklement0
@ mklement0, excepto tal vez si uno quiere usar el -Credentialparámetro
CJBS
@CJBS Sí, para ejecutar con una identidad de usuario diferente , el uso de Start-Processes imprescindible, pero solo entonces (y si desea ejecutar un comando en una ventana separada). Y uno debe ser consciente de las limitaciones inevitables en ese caso: Sin capacidad de captura de salida, a menos que - no intercalada - texto en archivos , a través de -RedirectStandardOutputy -RedirectStandardError.
mklement0

Respuestas:

161

Has probado:

$OutputVariable = (Shell command) | Out-String

JNK
fuente
Traté de asignarlo a una variable usando "=" pero no intenté canalizar la salida a Out-String primero. Lo intentaré.
Adam Bertram
10
No entiendo lo que está sucediendo aquí y no puedo hacer que funcione. ¿Es "Shell" una palabra clave de powershell? Entonces, ¿en realidad no usamos el cmdlet Start-Process? ¿Puede dar un ejemplo concreto por favor (es decir, reemplazar "Shell" y / o "comando" con un ejemplo real).
deadlydog
@deadlydog Reemplace Shell Commandcon lo que quiera ejecutar. Es así de simple.
JNK
1
@stej, tienes razón. Principalmente aclaraba que el código en su comentario tenía una funcionalidad diferente al código en la respuesta. ¡Los principiantes como yo pueden confundirse con sutiles diferencias de comportamiento como estos!
Sam
1
@Atique me encontré con el mismo problema. Resulta que ffmpeg a veces escribirá en stderr en lugar de stdout si, por ejemplo, usa la -iopción sin especificar un archivo de salida. Redirigir la salida usando 2>&1como se describe en algunas de las otras respuestas es la solución.
jmbpiano
159

Nota: El comando en la pregunta utiliza Start-Process, lo que impide la captura directa de la salida del programa de destino. En general, no lo use Start-Processpara ejecutar aplicaciones de consola de forma síncrona, solo invocalas directamente , como en cualquier shell. Al hacerlo, la aplicación se conecta a las transmisiones estándar de la consola de llamada, lo que permite capturar su salida mediante una simple asignación $output = netdom ..., como se detalla a continuación.

Básicamente , la captura de resultados de utilidades externas funciona igual que con los comandos nativos de PowerShell (es posible que desee una actualización sobre cómo ejecutar herramientas externas ):

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

Tenga en cuenta que $cmdOutputrecibe una matriz de objetos si <command>produce más de 1 objeto de salida , que en el caso de un programa externo significa una matriz de cadena que contiene las líneas de salida del programa .
Si desea $cmdOutputrecibir siempre una sola cadena , potencialmente de varias líneas , use
$cmdOutput = <command> | Out-String


Para capturar la salida en una variable e imprimir en la pantalla :

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

O, si <command>es un cmdlet o una función avanzada , puede usar el parámetro común
-OutVariable/-ov
:

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

Tenga en cuenta que with -OutVariable, a diferencia de los otros escenarios, siempre$cmdOutput es una colección , incluso si solo se emite un objeto. Específicamente, se devuelve una instancia del tipo de matriz . Vea este tema de GitHub para una discusión de esta discrepancia.[System.Collections.ArrayList]


Para capturar la salida de múltiples comandos , use una subexpresión ( $(...)) o llame a un bloque de script ( { ... }) con &o .:

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

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

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

Tenga en cuenta que la necesidad general de prefijar con &(el operador de llamada) un comando individual cuyo nombre / ruta se cita , por ejemplo, $cmdOutput = & 'netdom.exe' ...no está relacionado con programas externos per se (se aplica igualmente a los scripts de PowerShell), pero es un requisito de sintaxis : PowerShell analiza una declaración que comienza con una cadena entrecomillada en modo de expresión por defecto, mientras que el modo de argumento es necesario para invocar comandos (cmdlets, programas externos, funciones, alias), que es lo que &garantiza.

La diferencia clave entre $(...)y & { ... }/ . { ... }es que el primero recopila toda la entrada en la memoria antes de devolverlo como un todo, mientras que el segundo transmite la salida, adecuada para el procesamiento de canalización uno por uno.


Las redirecciones también funcionan igual, fundamentalmente (pero vea las advertencias a continuación):

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

Sin embargo, para los comandos externos es más probable que lo siguiente funcione como se esperaba:

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

Consideraciones específicas para programas externos :

  • Los programas externos , debido a que operan fuera del sistema de tipos de PowerShell, solo devuelven cadenas a través de su flujo de éxito (stdout).

  • Si la salida contiene más de 1 línea , PowerShell la divide de manera predeterminada en una matriz de cadenas . Más exactamente, las líneas de salida se almacenan en una matriz de tipo [System.Object[]]cuyos elementos son cadenas ( [System.String]).

  • Si desea que la salida sea una sola cadena potencialmente de varias líneas , canalice aOut-String :
    $cmdOutput = <command> | Out-String

  • Redirigir stderr para stdout con el2>&1 fin de capturarlo también como parte de la secuencia de éxito, viene con advertencias :

    • Para hacer 2>&1fusionar stdout y stderr en la fuente , permita cmd.exemanejar la redirección , utilizando los siguientes modismos:
      $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>y sale después de que <command>haya terminado.
      • Tenga en cuenta las comillas simples 2>&1, lo que garantiza que se pase la redirección en cmd.exelugar de que PowerShell la interprete.
      • Tenga en cuenta que involucrar cmd.exesignifica que sus reglas para escapar de los personajes y expandir las variables de entorno entran en juego, de forma predeterminada, además de los requisitos de PowerShell; en PS v3 + puede usar parámetros especiales --%(el llamado símbolo de stop-parsing ) para desactivar la interpretación de los parámetros restantes por parte de PowerShell, excepto cmd.exelas referencias de variables de entorno de estilo como %PATH%.

      • Tenga en cuenta que dado que está combinando stdout y stderr en la fuente con este enfoque, no podrá distinguir entre líneas originadas por stdout y originadas por stderr en PowerShell; Si necesita esta distinción, use la propia 2>&1redirección de PowerShell ; consulte a continuación.

    • Use la 2>&1 redirección de PowerShell para saber qué líneas provienen de qué flujo :

      • La salida de Stderr se captura como registros de error ( [System.Management.Automation.ErrorRecord]), no cadenas, por lo que la matriz de salida puede contener una mezcla de cadenas (cada cadena representa una línea estándar) y registros de error (cada registro representa una línea stderr) . Tenga en cuenta que, según lo solicitado por 2>&1, tanto las cadenas como los registros de error se reciben a través del flujo de salida de éxito de PowerShell ).

      • En la consola, los registros de error se imprimen en rojo , y el primero produce, por defecto , una visualización de varias líneas , en el mismo formato en que se mostraría el error sin terminación de un cmdlet; los registros de error posteriores también se imprimen en rojo, pero solo imprimen su mensaje de error , en una sola línea .

      • Cuando se envía a la consola , las cadenas suelen aparecer primero en la matriz de salida, seguidas de los registros de error (al menos entre un lote de salida de líneas stdout / stderr "al mismo tiempo"), pero, afortunadamente, cuando captura la salida , está correctamente intercalado , utilizando el mismo orden de salida que obtendría sin 2>&1; en otras palabras: cuando se envía a la consola , la salida capturada NO refleja el orden en el que el comando externo generó las líneas stdout y stderr.

      • Si captura la salida completa en una sola cadena conOut-String , PowerShell agregará líneas adicionales , porque la representación de cadena de un registro de error contiene información adicional, como ubicación ( At line:...) y categoría ( + CategoryInfo ...); Curiosamente, esto solo se aplica al primer registro de error.

        • Para evitar este problema, aplique el .ToString()método para cada objeto de salida en lugar de la tubería a Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          en PS v3 + puede simplificar a:
          $cmdOutput = <command> 2>&1 | % ToString
          (Como beneficio adicional, si la salida no se captura, esto produce una salida correctamente entrelazada incluso cuando se imprime en la consola).
      • Alternativamente, filtrar los registros de error fuera y enviarlos a flujo de error de PowerShell conWrite-Error (como un bono, si la salida no se captura, lo que produce como salida correctamente intercalada, incluso cuando se imprime en la consola):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}
mklement0
fuente
Esto finalmente funcionó para mí después de que tomé mi ruta ejecutable Y mis argumentos para ello, los arrojé en una cadena y lo traté como mi <comando>.
Dan
2
@Dan: cuando PowerShell interpreta <command>, no debe combinar el ejecutable y sus argumentos en una sola cadena; con la invocación a través de cmd /custed puede hacerlo, y depende de la situación si tiene sentido o no. ¿A qué escenario se refiere y puede dar un ejemplo mínimo?
mklement0
Funciona: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ command '2> & 1'
Dan
1
@Dan: Sí, eso funciona, aunque no necesita la variable intermedia y la construcción explícita de la cadena con el +operador; lo siguiente también funciona: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell se encarga de pasar los elementos $Argscomo una cadena separada por espacios en ese caso, una característica llamada splatting .
mklement0
Finalmente una respuesta adecuada que funciona en PS6.1 +. El secreto en la salsa es de hecho la '2>&1'parte, y no encerrar en ()tantos guiones tienden a hacer.
not2qubit
24

Si desea redirigir también la salida del error, debe hacer lo siguiente:

$cmdOutput = command 2>&1

O, si el nombre del programa tiene espacios:

$cmdOutput = & "command with spaces" 2>&1
manojlds
fuente
44
¿Qué significa 2> y 1? 'ejecutar comando llamado 2 y poner su salida en ejecutar comando llamado 1'?
Richard
77
Significa "redirigir la salida de error estándar (descriptor de archivo 2) al mismo lugar donde va la salida estándar (descriptor de archivo 1)". Básicamente, redirige los mensajes normales y de error al mismo lugar (en este caso, la consola, si stdout no se redirige a otro lugar, como un archivo).
Giovanni Tirloni
11

O prueba esto. Capturará la salida en la variable $ scriptOutput:

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

$scriptOutput
Finzzownt
fuente
77
-1, innecesariamente complejo. $scriptOutput = & "netdom.exe" $params
CharlesB
8
Eliminando el out-null y esto es ideal para canalizar tanto el shell como una variable al mismo tiempo.
ferventcoder
10

Otro ejemplo de la vida real:

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

Tenga en cuenta que este ejemplo incluye una ruta (que comienza con una variable de entorno). Tenga en cuenta que las comillas deben rodear la ruta y el archivo EXE, ¡pero no los parámetros!

Nota: No olvide el &carácter delante del comando, pero fuera de las comillas.

La salida de error también se recopila.

Me llevó un tiempo hacer funcionar esta combinación, así que pensé en compartirla.

Davidiam
fuente
8

Intenté las respuestas, pero en mi caso no obtuve la salida en bruto. En cambio, se convirtió en una excepción de PowerShell.

El resultado bruto que obtuve con:

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

Tengo lo siguiente para trabajar:

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

$ resultado te da lo necesario

Faye Smelter
fuente
1

Esto me funcionó:

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

Si todo lo que intenta hacer es capturar la salida de un comando, esto funcionará bien.

Lo uso para cambiar la hora del sistema, ya que [timezoneinfo]::localsiempre produce la misma información, incluso después de haber realizado cambios en el sistema. Esta es la única forma en que puedo validar y registrar el cambio en la zona horaria:

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

Lo que significa que tengo que abrir una nueva sesión de PowerShell para volver a cargar las variables del sistema.

Kelly Davis
fuente