Comment capturer la sortie dans une variable à partir d'un processus externe dans PowerShell?

158

Je voudrais exécuter un processus externe et capturer sa sortie de commande dans une variable dans PowerShell. J'utilise actuellement ceci:

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

J'ai confirmé que la commande s'exécute mais je dois capturer la sortie dans une variable. Cela signifie que je ne peux pas utiliser le -RedirectOutput car cela ne redirige que vers un fichier.

Adam Bertram
la source
3
D'abord et avant tout: ne pas utiliser Start-Processpour exécuter des applications de console (par définition externes) de manière synchrone - il suffit de les appeler directement , comme dans n'importe quel shell; à savoir: netdom /verify $pc /domain:hosp.uhhg.org. Cela permet de maintenir l'application connectée aux flux standard de la console appelante, ce qui permet de capturer sa sortie par simple affectation $output = netdom .... La plupart des réponses données ci-dessous renoncent implicitement Start-Processen faveur de l'exécution directe.
mklement0
@ mklement0 sauf peut-être si on veut utiliser le -Credentialparamètre
CJBS
@CJBS Oui, pour exécuter avec une identité d'utilisateur différente , l'utilisation de Start-Processest un must - mais seulement alors (et si vous voulez exécuter une commande dans une fenêtre séparée). Et il faut être conscient des limitations inévitables dans ce cas: aucune capacité à capturer la sortie, sauf en tant que texte - non entrelacé - dans des fichiers , via -RedirectStandardOutputet -RedirectStandardError.
mklement0

Réponses:

161

As-tu essayé:

$OutputVariable = (Shell command) | Out-String

JNK
la source
J'ai essayé de l'attribuer à une variable en utilisant "=" mais je n'ai pas essayé de diriger la sortie vers Out-String en premier. Je vais essayer.
Adam Bertram
10
Je ne comprends pas ce qui se passe ici et je n'arrive pas à le faire fonctionner. "Shell" est-il un mot-clé PowerShell? Nous n'utilisons donc pas réellement l'applet de commande Start-Process? Pouvez-vous s'il vous plaît donner un exemple concret (c.-à-d. Remplacer "Shell" et / ou "commande" par un exemple réel).
deadlydog
@deadlydog Remplacez Shell Commandpar ce que vous voulez exécuter. C'est si simple.
JNK
1
@stej, vous avez raison. Je clarifiais principalement que le code de votre commentaire avait des fonctionnalités différentes du code de la réponse. Les débutants comme moi peuvent être perturbés par de subtiles différences de comportement comme celles-ci!
Sam
1
@Atique J'ai rencontré le même problème. Il s'avère que ffmpeg écrira parfois dans stderr au lieu de stdout si, par exemple, vous utilisez l' -ioption sans spécifier de fichier de sortie. La 2>&1solution consiste à rediriger la sortie en utilisant comme décrit dans certaines des autres réponses.
jmbpiano
159

Remarque: La commande de la question utilise Start-Process, ce qui empêche la capture directe de la sortie du programme cible. En général, ne l'utilisez pas Start-Processpour exécuter des applications de console de manière synchrone - appelez-les simplement directement , comme dans n'importe quel shell. Cela permet de maintenir l'application connectée aux flux standard de la console appelante, ce qui permet de capturer sa sortie par simple affectation $output = netdom ..., comme détaillé ci-dessous.

Fondamentalement , la capture de la sortie d' utilitaires externes fonctionne de la même manière qu'avec les commandes natives PowerShell (vous voudrez peut-être un rappel sur la façon d'exécuter des outils externes ):

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

Notez que $cmdOutputreçoit un tableau d'objets si <command>produit plus d'un objet de sortie , ce qui dans le cas d'un programme externe signifie un tableau de chaînes contenant les lignes de sortie du programme .
Si vous voulez $cmdOutputtoujours recevoir une seule chaîne - potentiellement multiligne - , utilisez
$cmdOutput = <command> | Out-String


Pour capturer la sortie dans une variable et l' imprimer à l'écran :

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

Ou, s'il <command>s'agit d'une applet de commande ou d' une fonction avancée , vous pouvez utiliser le paramètre commun
-OutVariable/-ov
:

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

Notez que -OutVariable, contrairement aux autres scénarios, $cmdOutputest toujours une collection , même si un seul objet est généré. Plus précisément, une instance du [System.Collections.ArrayList]type de type tableau est renvoyée.
Consultez ce problème GitHub pour une discussion de cette différence.


Pour capturer la sortie de plusieurs commandes , utilisez une sous-expression ( $(...)) ou appelez un bloc de script ( { ... }) avec &ou .:

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

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

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

Notez que le besoin général de préfixer avec &(l'opérateur d'appel) une commande individuelle dont le nom / chemin est cité - par exemple, $cmdOutput = & 'netdom.exe' ...- n'est pas lié aux programmes externes en soi (cela s'applique également aux scripts PowerShell), mais est une exigence de syntaxe : PowerShell analyse une instruction qui commence par une chaîne entre guillemets en mode expression par défaut, alors que le mode argument est nécessaire pour appeler des commandes (applets de commande, programmes externes, fonctions, alias), ce qui &garantit.

La principale différence entre $(...)et & { ... }/ . { ... }est que le premier collecte toutes les entrées en mémoire avant de les renvoyer dans leur ensemble, tandis que les seconds diffusent la sortie, adaptée au traitement en pipeline un par un.


Les redirections fonctionnent également de la même manière, fondamentalement (mais voir les mises en garde ci-dessous):

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

Cependant, pour les commandes externes, les éléments suivants sont plus susceptibles de fonctionner comme prévu:

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

Considérations spécifiques aux programmes externes :

  • Les programmes externes , car ils fonctionnent en dehors du système de types de PowerShell, ne renvoient que des chaînes via leur flux de réussite (stdout).

  • Si la sortie contient plus d'une ligne , PowerShell la divise par défaut en un tableau de chaînes . Plus précisément, les lignes de sortie sont stockées dans un tableau de type [System.Object[]]dont les éléments sont des chaînes ( [System.String]).

  • Si vous souhaitez que la sortie soit une chaîne unique , potentiellement multi-lignes , dirigez versOut-String :
    $cmdOutput = <command> | Out-String

  • La redirection de stderr vers stdout avec2>&1 , afin de le capturer également dans le cadre du flux de réussite, comporte des mises en garde :

    • Pour faire 2>&1fusionner stdout et stderr à la source , laissez cmd.exegérer la redirection , en utilisant les idiomes suivants:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cinvoque cmd.exeavec commande <command>et quitte après <command>avoir terminé.
      • Notez les guillemets simples autour 2>&1, ce qui garantit que la redirection est transmise au cmd.exelieu d'être interprétée par PowerShell.
      • Notez qu'impliquer cmd.exesignifie que ses règles pour échapper les caractères et développer les variables d'environnement entrent en jeu, par défaut en plus des propres exigences de PowerShell; dans PS v3 +, vous pouvez utiliser un paramètre spécial --%(le soi-disant symbole d'arrêt de l'analyse ) pour désactiver l'interprétation des paramètres restants par PowerShell, à l'exception cmd.exedes références de variable d'environnement -style telles que %PATH%.

      • Notez que puisque vous fusionnez stdout et stderr à la source avec cette approche, vous ne pourrez pas faire la distinction entre les lignes d'origine stdout et stderr dans PowerShell; si vous avez besoin de cette distinction, utilisez la propre 2>&1redirection de PowerShell - voir ci-dessous.

    • Utilisez la 2>&1 redirection de PowerShell pour savoir quelles lignes proviennent de quel flux :

      • La sortie Stderr est capturée sous forme d'enregistrements d'erreur ( [System.Management.Automation.ErrorRecord]) et non de chaînes, de sorte que le tableau de sortie peut contenir un mélange de chaînes (chaque chaîne représentant une ligne stdout) et d'enregistrements d'erreur (chaque enregistrement représentant une ligne stderr) . Notez que, comme demandé par 2>&1, les chaînes et les enregistrements d'erreur sont reçus via le flux de sortie de réussite de PowerShell ).

      • Dans la console, les enregistrements d'erreur s'impriment en rouge , et le premier par défaut produit un affichage multiligne , dans le même format que l'erreur sans fin d'une applet de commande afficherait; les enregistrements d'erreurs ultérieurs s'impriment également en rouge, mais n'impriment leur message d' erreur que sur une seule ligne .

      • Lors de la sortie vers la console , les chaînes viennent généralement en premier dans le tableau de sortie, suivies des enregistrements d'erreurs (au moins parmi un lot de lignes stdout / stderr sortant «en même temps»), mais, heureusement, lorsque vous capturez la sortie , il est correctement entrelacé , en utilisant le même ordre de sortie que vous obtiendriez sans 2>&1; en d'autres termes: lors de la sortie vers la console , la sortie capturée ne reflète PAS l'ordre dans lequel les lignes stdout et stderr ont été générées par la commande externe.

      • Si vous capturez la sortie entière dans une seule chaîne avecOut-String , PowerShell ajoutera des lignes supplémentaires , car la représentation sous forme de chaîne d'un enregistrement d'erreur contient des informations supplémentaires telles que location ( At line:...) et category ( + CategoryInfo ...); curieusement, cela ne s'applique qu'au premier enregistrement d'erreur.

        • Pour contourner ce problème, appliquez la .ToString()méthode pour chaque objet de sortie au lieu de la tuyauterie à Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          dans PS v3 +, vous pouvez simplifier comme suit:
          $cmdOutput = <command> 2>&1 | % ToString
          (En prime, si la sortie n'est pas capturée, cela produit une sortie correctement entrelacée même lors de l'impression sur la console.)
      • Vous pouvez également filtrer les enregistrements d'erreur sur et les envoyer au flux d'erreur de PowerShell avecWrite-Error (comme un bonus, si la sortie n'est pas capturé, cela produit la sortie correctement entrelacée même lors de l' impression à la console):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}
mklement0
la source
Cela a finalement fonctionné pour moi après avoir pris mon chemin d'exécutable ET mes arguments, les avoir jetés dans une chaîne et traité cela comme ma <commande>.
Dan
2
@Dan: Lorsque PowerShell interprète lui-même <command>, vous ne devez pas combiner l'exécutable et ses arguments dans une seule chaîne; avec l'invocation via cmd /cvous pouvez le faire, et cela dépend de la situation, que cela ait un sens ou non. De quel scénario parlez-vous et pouvez-vous donner un exemple minimal?
mklement0
Fonctionne: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ command '2> & 1'
Dan
1
@Dan: Oui, cela fonctionne, bien que vous n'ayez pas besoin de la variable intermédiaire et de la construction explicite de la chaîne avec l' +opérateur; ce qui suit fonctionne également: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell prend soin de transmettre les éléments de $Argssous forme de chaîne séparée par des espaces dans ce cas, une fonctionnalité appelée splatting .
mklement0
Enfin une bonne réponse qui fonctionne dans PS6.1 +. Le secret de la sauce est en effet la '2>&1'partie, et ne pas enfermer ()comme beaucoup de scripts ont tendance à le faire.
not2qubit
24

Si vous souhaitez également rediriger la sortie d'erreur, vous devez faire:

$cmdOutput = command 2>&1

Ou, si le nom du programme contient des espaces:

$cmdOutput = & "command with spaces" 2>&1
manojlds
la source
4
Que signifie 2> & 1? 'exécuter la commande appelée 2 et mettre sa sortie dans la commande d'exécution appelée 1'?
Richard
7
Cela signifie "rediriger la sortie d'erreur standard (descripteur de fichier 2) vers le même endroit où la sortie standard (descripteur de fichier 1) va". Fondamentalement, redirige les messages normaux et d'erreur vers le même endroit (dans ce cas, la console, si stdout n'est pas redirigé ailleurs - comme un fichier).
Giovanni Tirloni
11

Ou essayez ceci. Il capturera la sortie dans la variable $ scriptOutput:

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

$scriptOutput
Finzzownt
la source
7
-1, inutilement complexe. $scriptOutput = & "netdom.exe" $params
CharlesB
8
Suppression de la valeur out-null, ce qui est idéal pour rediriger vers le shell et une variable en même temps.
ferventcoder
10

Un autre exemple concret:

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

Notez que cet exemple inclut un chemin (qui commence par une variable d'environnement). Notez que les guillemets doivent entourer le chemin et le fichier EXE, mais pas les paramètres!

Remarque: n'oubliez pas le &caractère devant la commande, mais en dehors des guillemets.

La sortie d'erreur est également collectée.

Il m'a fallu un certain temps pour que cette combinaison fonctionne, alors j'ai pensé que je la partagerais.

Davidiam
la source
8

J'ai essayé les réponses, mais dans mon cas, je n'ai pas obtenu la sortie brute. Au lieu de cela, il a été converti en une exception PowerShell.

Le résultat brut que j'ai obtenu avec:

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

J'ai fait fonctionner les éléments suivants:

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

$ result vous donne le nécessaire

Fonderie Faye
la source
1

Cette chose a fonctionné pour moi:

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

Si tout ce que vous essayez de faire est de capturer la sortie d'une commande, cela fonctionnera bien.

Je l'utilise pour changer l'heure du système, car je [timezoneinfo]::localproduit toujours les mêmes informations, même après que vous ayez apporté des modifications au système. C'est la seule façon dont je peux valider et enregistrer le changement de fuseau horaire:

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

Cela signifie que je dois ouvrir une nouvelle session PowerShell pour recharger les variables système.

Kelly Davis
la source