Powershell: Les arguments des exécutables externes ne sont pas correctement échappés

Créé le 21 août 2016  ·  170Commentaires  ·  Source: PowerShell/PowerShell

Étapes à suivre pour reproduire

  1. écrire un programme C native.exe qui acquiert ARGV
  2. Exécutez native.exe "`"a`""

    Comportement prévisible

ARGV [1] == "a"

Comportement réel

ARGV [1] == a

Données d'environnement

Windows 10 x64

Name                           Value
----                           -----
PSVersion                      5.1.14393.0
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.14393.0
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
Committee-Reviewed WG-Engine

Commentaire le plus utile

Créer un opérateur spécial pour cela n'a aucun sens pour un shell de ligne de commande, puisque son travail principal est de lancer des programmes et de leur transmettre des arguments. Introduire un nouvel opérateur qui fait system() pour ce travail est comme Matlab introduisant un moyen d'appeler calc.exe car il a un bogue dans son arithmétique. Ce qui devrait plutôt être fait est que:

  • L'équipe pwsh se prépare à une nouvelle version majeure qui corrige les éléments de la ligne de commande, déplaçant le comportement actuel derrière une applet de commande intégrée.
  • En tant que solution provisoire, la prochaine version de pwsh obtient une applet de commande intégrée qui utilise le nouveau comportement correct pour le passage de la ligne de commande.

La même chose s'applique à Start-Process . (En fait, c'est un très bon candidat pour la "nouvelle" cmdlet avec des options comme -QuotingBehavior Legacy ...) Voir # 13089.

Tous les 170 commentaires

Pourquoi pensez-vous que "\"a\"" le comportement attendu? Ma compréhension des échappements PowerShell indique que le comportement réel est le comportement correct et attendu. " "a "" est une paire de guillemets entourant une paire échappée de guillemets entourant un a , donc PowerShell interprète la paire externe non échappée comme" ceci est un argument de chaîne "et donc les laisse tomber, puis interprète la paire échappée comme des guillemets échappés et les garde ainsi, vous laissant avec "a" . À aucun moment un \ n'a été ajouté à la chaîne.

Le fait que Bash utilise \ comme caractère d'échappement n'est pas pertinent. Dans PowerShell, le caractère d'échappement est un backtick. Voir les caractères d'échappement PowerShell.

Si vous voulez passer littéralement "\"a\"" , je pense que vous utiliseriez:

> echo `"\`"a\`"`"
"\"a\""

@andschwa
Oui, les échappements fonctionnent bien pour les applets de commande internes, mais les choses deviennent étranges lors de la communication avec des binaires natifs , en particulier sous Windows.
Lors de l'exécution de native.exe " "a "" , l'ARGV [1] doit être

"a"

(trois caractères)

au lieu de

a

(un caractère).

Actuellement, pour que native.exe reçoive correctement un ARGV avec deux guillemets et un caractère a , vous devez utiliser cet appel étrange:

native.exe "\`"a\`""

Ah, je vois. Réouverture.

Par curiosité, que se passe-t-il si vous essayez une version utilisant # 1639?

@andschwa La même chose. Vous DEVEZ double-esacpe pour satisfaire à la fois PowerShell et CommandLineToArgvW . Cette ligne:

native.exe "`"a`""

résultats un StartProcess égalivent à cmd

native.exe ""a""

@ be5invis @douglaswth est-ce résolu via https://github.com/PowerShell/PowerShell/pull/2182?

Non, nous devons encore ajouter une barre oblique inverse avant un guillemet double échappé par un backtick? Cela ne résout pas le problème du double échappement. (Autrement dit, nous devons échapper un guillemet double pour PowerShell et CommandLineToArgvW.)

Puisque " "a "" est égal à '"a"' , suggérez-vous que native.exe '"a"' aboutisse à "\"a\"" ?

Cela semble être une demande de fonctionnalité qui, si elle est mise en œuvre, pourrait interrompre un grand nombre de scripts PowerShell déjà existants qui utilisent le double échappement requis, donc une attention extrême serait requise avec toute solution.

@vors Oui.
@douglaswth Le double échappement est vraiment idiot: pourquoi avons-nous besoin des échappées «intérieures» faites à l'ère DOS?

@vors @douglaswth
Il s'agit d'un code C utilisé pour afficher les résultats GetCommandLineW et CommandLineToArgvW:

#include <stdio.h>
#include <wchar.h>
#include <Windows.h>

int main() {
  LPWSTR cmdline = GetCommandLineW();
  wprintf(L"Command Line : %s\n", cmdline);

  int nArgs;
  LPWSTR *szArglist = CommandLineToArgvW(cmdline, &nArgs);
  if (NULL == szArglist) {
    wprintf(L"CommandLineToArgvW failed\n");
    return 0;
  } else {
    for (int i = 0; i < nArgs; i++) {
      wprintf(L"argv[%d]: %s\n", i, szArglist[i]);
    }
  }
  LocalFree(szArglist);
}

Voici le résultat

$ ./a "a b"
Command Line : "Z:\playground\ps-cmdline\a.exe" "a b"
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a b

$ ./a 'a b'
Command Line : "Z:\playground\ps-cmdline\a.exe" "a b"
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a b

$ ./a 'a"b'
Command Line : "Z:\playground\ps-cmdline\a.exe" a"b
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: ab

$ ./a 'a"b"c'
Command Line : "Z:\playground\ps-cmdline\a.exe" a"b"c
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: abc

$ ./a 'a\"b\"c'
Command Line : "Z:\playground\ps-cmdline\a.exe" a\"b\"c
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a"b"c

@ be5invis Je ne suis pas en désaccord avec vous sur le fait que le double échappement est ennuyeux, mais je dis simplement qu'une modification de cela devrait être rétrocompatible avec l'utilisation des scripts PowerShell existants.

Combien sont-ils? Je ne pense pas que les scénaristes soient au courant de ces doubles citations. C'est un bogue, pas une fonctionnalité, et il n'est pas documenté.

???? iPhone

? 2016? 9? 21 ?? 01: 58? Douglas Thrift < [email protected] [email protected] > ???

@ be5i nvishttps: //github.com/be5invis Je ne suis pas en désaccord avec vous sur le fait que le double échappement est ennuyeux, mais je dis simplement qu'une modification de cela devrait être rétrocompatible avec ce que les scripts PowerShell existants utilisent.

Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur Gi tHubhttps: //github.com/PowerShell/PowerShell/issues/1995#issuecomment -248381045, ou désactivez la lecture en mode silencieux.

PowerShell existe depuis 9 ans, il existe donc très probablement un bon nombre de scripts. J'ai trouvé beaucoup d'informations sur la nécessité de double échapper à StackOverflow et à d'autres sources lorsque j'en ai rencontré le besoin, donc je ne sais pas si je suis d'accord avec vos affirmations selon lesquelles personne ne sait ce qu'il faut ou si cela n'est pas documenté .

Pour le contexte supplémentaire, j'aimerais parler un peu de la mise en œuvre.
PowerShell appelle l'API .NET pour générer un nouveau processus, qui appelle une API Win32 (sous Windows).

Ici, PS crée StartProcessInfo qui utilise
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/NativeCommandProcessor.cs#L1063

L'API fournie prend une seule chaîne pour les arguments, puis elle est ré-analysée dans un tableau d'arguments pour effectuer l'exécution.
Les règles de cette nouvelle analyse ne sont pas contrôlées par PowerShell. C'est une API Win32 (et heureusement, elle est cohérente dans les règles dotnet core et unix).
En particulier, ce contrat décrit le comportement \ et " .

Bien que PowerShell puisse essayer d'être plus intelligent et de fournir une expérience plus agréable, le comportement actuel est cohérent avec cmd et bash: vous pouvez copier une ligne exécutable native à partir d'eux et l'utiliser dans PowerShell et cela fonctionne de la même manière.

@ be5invis Si vous connaissez un moyen d'améliorer l'expérience de manière insécable, veuillez aligner les détails. Pour les changements de rupture, nous aurions besoin d'utiliser le processus RFC, comme décrit dans https://github.com/PowerShell/PowerShell/blob/master/docs/dev-process/breaking-change-contract.md

Cela s'applique à Windows, mais lors de l'exécution de commandes sous Linux ou Unix, il est étrange de devoir doubler les guillemets d'échappement.

Sur Linux, les processus n'ont pas une seule ligne de commande mais plutôt un tableau d'arguments.
Par conséquent, les arguments dans PowerShell doivent être les mêmes que ceux qui sont passés à l'exécutable, au lieu de fusionner tous les arguments, puis de les redécouper.

Même sous Windows, le comportement actuel est incohérent:
Si un argument ne contient aucun espace, il est transmis sans changement.
Si un argument contient des espaces, s'il sera entouré de guillemets, pour le garder ensemble via CommandLineToArgvW call. => L'argument est modifié pour répondre à l'exigence de CommandLineToArgvW .
Mais si l'argument contient des guillemets, ceux-ci ne sont pas échappés. => L'argument n'est pas modifié, bien que CommandLineToArgvW exige.

Je pense que les arguments ne devraient jamais être modifiés, ou toujours être modifiés pour répondre aux exigences de CommandLineToArgvW , mais pas dans la moitié des cas.

Concernant la rupture du contrat:
Comme je n'ai pas trouvé de documentation officielle sur la double fuite, je considérerais cela comme la catégorie "Bucket 2: Reasonable Gray Area", donc il y a des chances de changer cela, ou est-ce que je me trompe?

@vors C'est extrêmement ennuyeux si votre argument est une variable ou autre chose: vous devez l'échapper manuellement avant de l'envoyer dans une application native.
Un opérateur "auto-échappement" peut aider. comme ^"a " " -> "a\ " "`

Je pense que @TSlivede l' a

Je pense que les arguments ne devraient jamais être modifiés, ou toujours être modifiés pour répondre aux exigences de CommandLineToArgvW, mais pas dans la moitié des cas.

Je ne suis pas sûr du seau, mais même le seau «changement de rupture» pourrait potentiellement être modifié. Nous voulons améliorer PowerShell, mais la rétrocompatibilité est l'une de nos plus grandes priorités. C'est pourquoi ce n'est pas si facile.
Nous avons une grande communauté et je suis convaincu que nous pouvons trouver un consensus.

Quelqu'un voudrait-il lancer un processus RFC?

Il vaudrait la peine d'étudier l'utilisation de P / Invoke au lieu de .Net pour démarrer un processus si cela évite à PowerShell d'ajouter des guillemets aux arguments.

@lzybkr pour autant que je sache, PInvoke n'aiderait pas.
Et c'est là que les API Unix et Windows sont différentes:

https://msdn.microsoft.com/en-us/library/20y988d2.aspx (traite les espaces comme des séparateurs)
https://linux.die.net/man/3/execvp (ne traite pas les espaces comme des séparateurs)

Je ne suggérais pas de changer l'implémentation Windows.

J'essaierais d'éviter d'avoir un comportement spécifique à la plate-forme ici. Cela nuira à la portabilité des scripts.
Je pense que nous pouvons envisager de changer le comportement des fenêtres de manière insécable. Ie avec la variable de préférence. Et puis nous pouvons avoir différents défauts ou quelque chose comme ça.

Nous parlons d'appeler des commandes externes - quelque peu dépendant de la plate-forme de toute façon.

Eh bien, je pense que cela ne peut pas être vraiment indépendant de la plate-forme, car Windows et Linux ont juste différentes façons d'appeler des exécutables. Sous Linux, un processus obtient un tableau d'arguments tandis que sous Windows, un processus ne reçoit qu'une seule ligne de commande (une chaîne).
(comparez les plus basiques
CreateProcess -> ligne de commande (https://msdn.microsoft.com/library/windows/desktop/ms682425)
et
execve -> tableau de commandes (https://linux.die.net/man/2/execve)
)

Comme Powershell ajoute ces guillemets lorsque les arguments contiennent des espaces, il me semble que PowerShell essaie ** de transmettre les arguments d'une manière, que CommandLineToArgvW divise la ligne de commande en arguments qui ont été initialement donnés dans PowerShell. (De cette façon, un programme c typique obtient les mêmes arguments dans son tableau argv qu'une fonction PowerShell obtient comme $ args.)
Cela correspondrait parfaitement au simple passage des arguments à l'appel système linux (comme suggéré via p / invoke).

** (et échoue, car il n'échappe pas aux guillemets)

PS: Que faut-il pour démarrer un processus RFC?

Exactement - PowerShell essaie de s'assurer que CommandLineToArgvW produit la commande correcte et _after_ analysant ce que PowerShell a déjà analysé.

Cela a été un problème de longue date sur Windows, je vois une raison d'apporter cette difficulté à * nix.

Pour moi, cela ressemble à un détail d'implémentation, pas vraiment besoin d'une RFC. Si nous avons changé de comportement dans Windows PowerShell, cela pourrait justifier une RFC, mais même dans ce cas, le bon changement pourrait être considéré comme une correction de bogue (éventuellement risquée).

Oui, je pense aussi que le changer sous Linux pour utiliser un appel système direct rendrait tout le monde plus heureux.

Je pense toujours que cela devrait également être changé sur Windows,
(Peut-être en ajoutant une variable de préférence pour ceux qui ne veulent pas changer leurs scripts)
parce que c'est juste faux maintenant - c'est un bug. Si cela était corrigé, un appel système direct sur Linux ne serait même pas nécessaire, car tout argument atteindrait le processus suivant inchangé.

Mais comme il y a des exécutables, qui divisent la ligne de commande d'une manière, incompatible avec CommandLineToArgvW , j'aime l'idée de @ be5invis d'un opérateur pour les arguments - mais je ne

Ce problème vient de nous être posé aujourd'hui lorsque quelqu'un a essayé la commande suivante dans PowerShell et dissipait PowerShell alors que cela ne fonctionnait pas, mais CMD l'a fait:

wmic useraccount where name='username' get sid

De PSCX echoargs, wmic.exe voit ceci:

94> echoargs wmic useraccount where name='tso_bldadm' get sid
Arg 0 is <wmic>
Arg 1 is <useraccount>
Arg 2 is <where>
Arg 3 is <name=tso_bldadm>
Arg 4 is <get>
Arg 5 is <sid>

Command line:
"C:\Users\hillr\Documents\WindowsPowerShell\Modules\Pscx\3.2.2\Apps\EchoArgs.exe" wmic useraccount where name=tso_bldadm get sid

Alors, quelle API CMD.exe utilise pour appeler le processus / former la ligne de commande? D'ailleurs, que fait -% pour que cette commande fonctionne?

@rkeithhill CreateProcessW . appel direct. vraiment.

Pourquoi Powershell se comporte-t-il différemment dans ces deux situations? Plus précisément, il encapsule de manière incohérente les arguments contenant des espaces entre guillemets.

# Desired argv[1] is 4 characters: A, space, double-quote, B
$ .\echoargs.exe 'A \"B'
<"C:\test\echoargs.exe" "A \"B">
<A "B>
# Correct!

# Desired argv value is 4 characters: A, double-quote, space, B
$ .\echoargs.exe 'A\" B'
<"C:\test\echoargs.exe" A\" B>
<A"> <B>
# Wrong...

Il ne semble y avoir ni rime ni raison. Dans la première situation, cela enveloppe mon argument avec des guillemets, mais dans la deuxième situation, ce n'est pas le cas. J'ai besoin de savoir exactement quand il sera et ne sera pas enveloppé de guillemets doubles afin que je puisse manuellement envelopper (ou non) mon script.

.echoargs.exe est créé en compilant ce qui suit avec cl echoargs.c

// echoargs.c
#include <windows.h>
#include <stdio.h>
int wmain(int argc, WCHAR** argv) {
    wprintf(L"<%ls>\n", GetCommandLineW());
    for(int i = 1; i < argc; i++) {
        wprintf(L">%s< ", argv[i]);
    }
    wprintf(L"\n");
}

EDIT: Voici mon $ PSVersionTable:

Name                           Value
----                           -----
PSVersion                      5.1.15063.296
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.296
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Le comportement concernant les citations a changé plusieurs fois, je suggère donc d'utiliser quelque chose comme ceci:

Edit: formulaire mis à jour ci-dessous
Ancienne version:

# call helper

function Run-Native($command) {
    $env:commandlineargumentstring=($args | %{'"'+ ($_ -replace '(\\*)"','$1$1\"' -replace '(\\*)$','$1$1') + '"'}) -join ' ';
    & $command --% %commandlineargumentstring%
}

# some test cases

Run-Native .\echoargs.exe 'A "B' 'A" B'
Run-Native .\echoargs.exe 'A "B'
Run-Native .\echoargs.exe 'A" B'
Run-Native .\echoargs.exe 'A\" B\\" \'

Production:

<"C:\test\echoargs.exe"  "A \"B" "A\" B">
<A "B> <A" B>

<"C:\test\echoargs.exe"  "A \"B">
<A "B>

<"C:\test\echoargs.exe"  "A\" B">
<A" B>

<"C:\test\echoargs.exe"  "A\\\" B\\\\\" \\">
<A\" B\\" \>

Le premier -replace double les antislashs devant les guillemets et ajoute un backslash supplémentaire, pour échapper à la qoute.
Le deuxième -replace double les contre-obliques à la fin de l'argument, de sorte que le guillemet fermant ne soit pas échappé.

Cela utilise --% (PS v3 et supérieur), qui est AFAIK le seul moyen fiable de transmettre des guillemets aux exécutables natifs.


Éditer:

Version mise à jour de Run-Native , maintenant appelée Invoke-NativeCommand (comme suggéré )

function Invoke-NativeCommand() {
    $command, [string[]] $argsForExe = $args
    if($argsForExe.Length -eq 0){
        & $command
    } else {
        $env:commandlineargumentstring=($argsForExe | %{
            if($_ -match '^[\w\d\-:/\\=]+$'){
                $_ #don't quote nonempty arguments consisting of only letters, numbers, or one of -:/\=
            } else {
                $_ <# double backslashes in front of quotes and escape quotes with backslash #> `
                    -replace '(\\*)"','$1$1\"' `
                   <# opening quote after xxx= or after /xxx: or at beginning otherwise #> `
                    -replace '^([\w\d]+=(?=.)|[/-][\w\d]+[:=](?=.)|^)','$1"' `
                   <# double backslashes in front of closing quote #> `
                    -replace '(\\*)$','$1$1' `
                   <# add closing quote #> `
                    -replace '$','"'
            }
        }) -join ' ';
        & $command --% %commandlineargumentstring%
    }
}

(avec un peu d'inspiration de l' IPE )

  • ne cite pas de simples commutateurs
  • fonctionne toujours avec des arguments vides
  • fonctionne si aucun argument n'est présent
  • devrait principalement fonctionner avec msiexec, cmdkey, etc ...
  • fonctionne toujours pour les programmes, qui suivent les règles communes
  • n'utilise pas "" non standard comme échappé " - ne fonctionnera donc toujours pas pour les guillemets incorporés dans les arguments .bat ou msiexec

Merci, je ne connaissais pas --% . existe-t-il un moyen de le faire sans divulguer la variable d'environnement au processus natif? (et à tous les processus qu'il pourrait invoquer)

Existe-t-il un module PowerShell qui implémente une applet Run-Native que tout le monde peut utiliser? Cela ressemble à quelque chose qui devrait être sur la galerie Powershell. Si c'était assez bon, cela pourrait servir de base à une RFC.

"fuite" semble que vous êtes préoccupé par la sécurité. Notez cependant que la ligne de commande est de toute façon visible par les processus enfants. (Par exemple: gwmi win32_process |select name,handle,commandline|Format-Table sous Windows et ps -f sous Linux)

Si vous souhaitez toujours éviter une variable d'environnement, vous pourrez peut-être construire quelque chose en utilisant invoke-expression.

Concernant la RFC:
Je ne pense pas qu'une telle commande devrait être nécessaire, mais plutôt le comportement par défaut:

https://github.com/PowerShell/PowerShell-RFC/issues/90

Je suis d'accord que le comportement par défaut de PowerShell doit être corrigé. J'avais supposé avec pessimisme que cela ne changerait jamais pour des raisons de compatibilité descendante, c'est pourquoi j'ai suggéré d'écrire un module. Cependant, j'aime beaucoup la façon dont votre RFC permet à l'ancien comportement d'échappement d'être réactivé via une variable de préférence.

Permettez-moi de résumer la discussion, avec juste la bonne dose d'opinion:

  • Il est clair que nous avons un problème de compatibilité descendante, donc l'ancien comportement doit continuer à rester disponible.

  • La proposition RFC de @TSlivede en tient compte tout en indiquant de manière louable _la voie vers le futur_.
    Malheureusement, sa proposition languit en tant que _PR_ au moment de la rédaction de cet article, et elle n'a même pas encore été acceptée en tant que _draft_ RFC.


Par _le futur_, je veux dire:

  • PowerShell est un shell à part entière qui, espérons-le, se débarrassera bientôt de son bagage lié à cmd.exe , donc les seules considérations qui importent lorsqu'il s'agit d'appeler des utilitaires externes (exécutables qui sont (généralement) des applications console / terminal) sont :

    • Les arguments à transmettre doivent être spécifiés par les règles d'analyse en mode argument de _PowerShell_.

    • Quel que soit le résultat _literals_ de ce processus, il doit être passé tel quel à l'exécutable cible, en tant qu'arguments _individuels_.

    • En d'autres termes: en tant qu'utilisateur, tout ce sur quoi vous devriez vous concentrer est le résultat de l'analyse de _PowerShell_, et pour pouvoir compter sur ce résultat transmis tel quel, PowerShell prenant soin de tout derrière-le -codage de scènes - si nécessaire.


_Mettre en œuvre_ l'avenir:

  • Sur _Windows_:

    • Pour des raisons _historiques_, Windows ne permet _pas_ de passer des arguments _ comme un tableau de littéraux_ à l'exécutable cible; à la place, une chaîne _single_ encodant _tous_ arguments utilisant _pseudo syntaxe_ de shell_ est nécessaire. Pire encore, c'est à l'exécutable cible individuel d'interpréter cette seule chaîne et de la diviser en arguments.

    • Le mieux que PowerShell puisse faire est de former cette chaîne unique - dans les coulisses, après avoir effectué sa séparation _ propre_ de la ligne de commande en arguments individuels - d'une manière _prédictible et normalisée_.

    • La proposition RFC de @TSlivede propose justement cela, en suggérant que PowerShell synthétise la ligne de commande pseudo shell de manière à ce que le moteur d'exécution de Windows C / C ++ récupère les arguments d'entrée tels quels lors de son analyse :

      • Étant donné que c'est finalement à chaque exécutable cible d'interpréter la ligne de commande, il n'y a pas de garantie que cela fonctionnera dans tous les cas, mais ces règles sont le choix le plus judicieux, car la plupart des utilitaires existants utilisent ces conventions.

      • Les seules exceptions notables sont les _batch files_, qui pourraient recevoir un traitement spécial, comme le suggère la proposition RFC.

  • Sur les plates-formes _Unix_:

    • Strictement parlant, les problèmes qui affectent l'analyse des arguments Windows _ne doivent jamais se poser_, car les appels natifs de la plate-forme pour créer de nouveaux processus _acceptent les arguments comme des tableaux de littéraux_ - quels que soient les arguments avec lesquels PowerShell se termine après avoir effectué son analyse _ propre_ doivent simplement être transmis .
      Pour citer @lzybkr : "Je ne vois aucune raison de ramener cette difficulté à * nix."

    • Malheureusement, en raison des limitations actuelles de .NET Core (CoreFX), ces problèmes _do_ entrent en jeu, car l'API CoreFX force inutilement l'anarchie de l'argument _Windows_ à passer également dans le monde Unix, en exigeant l'utilisation d'une pseudo ligne de commande même sur Unix.

    • J'ai créé ce problème CoreFX pour demander que ce problème soit résolu.

    • En attendant, étant donné que CoreFX divise la pseudo ligne de commande en arguments basés sur les règles C / C ++ citées ci-dessus, la proposition de @TSlivede devrait également fonctionner sur les plates-formes Unix.

Comme https://github.com/PowerShell/PowerShell/issues/4358 a été fermé en tant que duplicata de ceci, voici un bref résumé de ce problème:

Si un argument d'un exécutable externe avec une barre oblique inverse de fin contient un espace, il est actuellement naïvement entre guillemets (ajoutez une citation avant et après l'argument). Tout exécutable qui suit les règles habituelles interprète cela comme ceci:
D' après le commentaire de @ mklement0 :

Le 2ème " dans ".\test 2\" , car il est précédé de \ est interprété comme un "échappé", ce qui fait que le reste de la chaîne - malgré une fermeture alors manquante "est interprété comme faisant partie du même argument.

Exemple:
(d' après le commentaire de @akervinen )

PS X:\scratch> .\ps-args-test.exe '.\test 2\'
Argument reçu: .\test 2"

Le problème se produit très souvent, car PSReadLine ajoute une barre oblique inverse à la fin de la saisie semi-automatique des répertoires.

Puisque corefx semble ouvert à la production de l'API dont nous avons besoin, je reporte cela à 6.1.0. Pour la version 6.0.0, je vais voir si nous pouvons corriger # 4358

@TSlivede J'ai pris votre fonction, je l'ai renommée en Invoke-NativeCommand (car Run n'est pas un verbe valide) et j'ai ajouté un alias ^ et l'ai publié en tant que module sur PowerShellGallery:

install-module NativeCommand -scope currentuser
^ ./echoargs 'A "B' 'A" B'

@ SteveL-MSFT:

C'est bien d'avoir une solution provisoire, mais une solution moins lourde serait - en attendant une solution CoreFX - d'implémenter les règles officielles bien définies de citations / analyses d' arguments,

Si nous ne résolvons que le problème \" , le passage d'arguments est toujours fondamentalement cassé, même dans des scénarios simples tels que les suivants:

PS> bash -c 'echo "hi there"'
hi    # !! Bash sees the following tokens:  '-c', 'echo hi', 'there'

Je pense qu'à ce stade, il y a un accord suffisant sur ce que devrait être le comportement afin que nous n'ayons pas besoin d'un processus RFC complet, n'est-ce pas?

La seule décision en suspens est de savoir comment gérer les problèmes de compatibilité descendante dans _Windows_.

@ mklement0 @ SteveL-MSFT
Sommes-nous déjà rompus la compatibilité?

La seule décision en suspens est de savoir comment gérer les problèmes de compatibilité descendante dans Windows.

Ouais, mais c'est la partie la plus difficile, non?

@ be5invis qu'entendez-vous par "compatibilité déjà cassée"?

De plus, si CoreFX est sur le point de trouver un correctif dans leur couche, je préfère ne pas créer de transition dans notre couche avant eux.

Et comme quelqu'un l'a dit ci-dessus dans le fil de discussion, c'est ennuyeux, mais c'est aussi assez bien documenté dans la communauté. Je ne suis pas sûr que nous devrions le casser deux fois dans les deux prochaines versions.

@joeyaiello :

Le correctif pour # 4358 n'est-il pas déjà un changement majeur pour ceux qui ont contourné le problème en doublant le \ final "c:\tmp 1\\" ? En d'autres termes: si vous limitez les changements à ce correctif, _deux_ changements de rupture sont garantis: celui-ci maintenant, et un autre plus tard après le passage à la future API CoreFx; et bien que cela puisse également se produire si un palliatif complet était mis en œuvre maintenant, il est peu probable, étant donné ce que nous savons de ce changement à venir.

À l'inverse, cela peut entraver l'adoption sous Unix si des scénarios de citations courants tels que
bash -c 'echo "hi there"' ne fonctionne pas correctement.

Cependant, je me rends compte que la résolution de ce problème est un changement radical beaucoup plus important.

@ PowerShell / powershell-Committee a discuté de cela et a convenu qu'au minimum, l'utilisation de --% devrait avoir le même comportement que bash en ce que les guillemets sont échappés afin que la commande native les reçoive. Ce qui est encore ouvert au débat, c'est si cela devrait être le comportement par défaut sans utiliser --%

Remarque:

  • Je suppose qu'un appel à un exécutable shell réel est nécessaire lors de l'utilisation de --% sur _Unix_, par opposition à essayer de _émuler_ le comportement du shell, ce qui se passe sur _Windows_. L'émulation n'est pas difficile sous Windows, mais serait beaucoup plus difficile sous Unix, étant donné les nombreuses autres fonctionnalités qui auraient besoin d'être émulées.

  • L'utilisation d'un shell réel soulève alors la question du shell à utiliser: alors que bash est omniprésent, son comportement par défaut n'est pas conforme à POSIX et n'est pas requis par POSIX d'être présent, donc pour la portabilité d'autres langages de script appellent /bin/sh , l'exécutable shell décrété par POSIX (qui _peut_ être Bash fonctionnant en mode de compatibilité (par exemple, sur macOS), mais certainement pas obligé (par exemple, Dash sur Ubuntu)).

On peut soutenir que nous devrions également cibler /bin/sh - ce qui signifie cependant que certaines fonctionnalités de Bash - notamment l'expansion d'accolades, certaines variables automatiques, ... - ne seront pas disponibles


Utilisation de --%

J'utiliserai la commande echoargs --% 'echo "hi there"' comme exemple ci-dessous.

le même comportement que bash en ce que les guillemets sont échappés afin que la commande native les reçoive.

La manière de procéder _à l'avenir, une fois que l'API CoreFX a été étendue_ serait de ne pas effectuer d'échappements du tout, et de procéder comme suit:

  • Créez un processus comme suit:

    • /bin/sh comme exécutable, (effectivement) assigné à ProcessStartInfo.FileName .

    • Le tableau suivant de jetons d'argument _literal_, _individual_ comme ProcessStartInfo.ArgumentList :

    • -c comme 1er argument

    • echoargs 'echo "hi there"' comme deuxième argument - c'est-à-dire que la ligne de commande d'origine a utilisé _litéralement_, exactement comme spécifié, sauf que --% été supprimé.

En effet, la ligne de commande est transmise via _as-is_ à l'exécutable du shell, qui peut alors effectuer _ son_ analyse.

Je comprends que, en l'absence actuelle d'un moyen basé sur un tableau pour passer des arguments littéraux, nous devons combiner -c et echoargs 'echo "hi there"' dans une chaîne _single_ _ avec échappement_, malheureusement _seulement pour le bénéfice du CoreFX API_, qui, quand vient le temps de créer le processus réel, _ inverse_ cette étape et divise la chaîne unique en jetons littéraux - et s'assurer que cette inversion aboutit toujours à la liste d'origine des jetons littéraux est la partie difficile.

Encore une fois: La seule raison d'impliquer de s'échapper ici est due à la limitation actuelle de CoreFX.
Pour travailler avec cette limitation, la seule chaîne, échappée suivante doit donc être affecté au .Arguments propriété d'un ProcessStartInfo par exemple, avec l'échappement effectué , comme indiqué par Parsing C ++ Arguments de ligne de

  • /bin/sh comme exécutable, (effectivement) assigné à ProcessStartInfo.FileName .
  • La chaîne d'échappement unique suivante comme valeur de ProcessStartInfo.Arguments :
    -c "echoargs 'echo \"hi there\"'"

## Comportement par défaut

Ce qui est encore ouvert au débat est de savoir si cela devrait être le comportement par défaut sans utiliser -%

Le comportement par défaut sous Unix devrait être très différent:

  • Aucune considération d'échappatoire autre que celle de PowerShell ne devrait jamais entrer en jeu (sauf sur _Windows_, où cela ne peut être évité, malheureusement; mais là, les règles MS C ++ sont la voie à suivre, _à appliquer dans les coulisses_; à défaut, --% fournit une trappe d'échappement).

  • Quels que soient les arguments avec lesquels _PowerShell_ se termine, après sa propre analyse syntaxique, doivent être passés comme un _ tableau de littéraux_ , via la propriété ProcessStartInfo.ArgumentList venir.

Appliqué à l'exemple sans --% : echoargs 'echo "hi there"' :

  • PowerShell effectue son analyse habituelle et se termine avec les 2 arguments suivants:

    • echoargs
    • echo "hi there" (guillemets simples - qui n'avaient qu'une fonction syntaxique à _PowerShell_, supprimés)
  • ProcessStartInfo est ensuite renseigné comme suit, avec la prochaine extension CoreFX en place:

    • echoargs comme valeur de la propriété (effective) .FileName
    • _Literal_ echo "hi there" comme seul élément à ajouter à l'instance Collection<string> exposée par .ArgumentList .

Encore une fois, en l'absence de .ArgumentList ce n'est pas encore une option, mais en attendant, le même échappement auxiliaire compatible MS C ++ comme décrit ci-dessus pourrait être utilisé.

@ SteveL-MSFT
Comme je l'ai déjà mentionné dans Faire fonctionner le symbole d'arrêt de l'analyse (-%) sous Unix (# 3733), je déconseille fortement de changer le comportement de --% .

Si une fonctionnalité spéciale pour /bin/sh -c est nécessaire, veuillez utiliser un symbole différent et laisser --% tel quel!

@TSlivede :

_Si_ quelque chose --% -_like_ est implémenté sous Unix - et avec le globbing natif et une foule généralement plus avertie en ligne de commande sous Unix, je perçois moins un besoin - alors en choisissant un _ symbole différent_ - comme --$ - a probablement du sens (désolé, j'avais perdu la trace de tous les aspects de ce long débat sur plusieurs questions).

Différents symboles serviraient également de rappels visuellement visibles que le comportement non portable spécifique à la plate-forme est appelé.

Cela laisse la question de savoir ce que PowerShell doit faire lorsqu'il rencontre --% sous Unix et --$ sous Windows.

Je vais bien laisser --% tel quel. Introduire quelque chose comme --$ qui appelle / bin / sh et je suppose que cmd.exe sur Windows peut être un bon moyen de résoudre ce problème.

Aucune chance de créer une applet de commande pour ces comportements?

@iSazonov suggérez -vous quelque chose comme Invoke-Native ? Je ne suis pas sûr que je sois fan de ça.

Oui, comme Start-Native .
Pour plaisanter :-), n'aimez-vous pas les applets de commande dans PowerShell?

Dans Build.psm1, nous avons Start-NativeExecution avec un lien vers https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/

@ SteveL-MSFT

Je vais bien partir -% tel quel.

Je pense que nous sommes tous d'accord pour dire que --% doit continuer à se comporter comme il le fait sur _Windows_.

Sur _Unix_, en revanche, ce comportement n'a aucun sens, comme j'ai essayé de le démontrer ici - en bref:

  • les guillemets simples ne sont pas gérés correctement
  • la seule façon de référencer la variable d'environnement est comme _cmd.exe-style_ ( %var% )
  • les fonctionnalités natives importantes telles que le globbing et le fractionnement de mots ne fonctionnent pas.

La principale motivation pour introduire --% était, si je comprends bien, d'activer la _rutilisation des cmd.exe lignes de commande_ en l'état.

  • En tant que tel, --% est inutile sous Unix avec son comportement actuel.
  • _Si_ nous voulions une fonctionnalité _analogique_ sous Unix, elle devrait être basée sur /bin/sh -c , comme proposé ci-dessus, en utilisant probablement un symbole différent.

Je ne pense pas qu'il y ait un besoin pour une fonctionnalité basée sur cmd /c sur Windows, car --% a cela _ presque_ couvert, sans doute d'une manière _ assez bonne_.
@TSlivede a souligné que toutes les fonctionnalités du shell ne sont pas émulées, mais en pratique cela ne semble pas être un problème (par exemple, les substitutions de valeurs variables telles que %envVar:old=new% ne sont pas prises en charge, ^ n'est pas un caractère d'échappement., et l'utilisation de --% est limitée à une commande _single_ - pas d'utilisation des opérateurs de redirection et de contrôle de cmd.exe ; cela dit, je ne pense pas que --% n'a jamais été conçu pour émuler la commande entière _lines_).

En tant que tel, quelque chose comme --$ - s'il est implémenté - serait la _counterpart_ Unix à --% .

Dans tous les cas, au moins la rubrique d'aide about_Parsing mérite un avertissement ostentatoire que --% sera inutile sous Unix dans tous les cas sauf quelques-uns.

@iSazonov Il peut être judicieux d'avoir Start-Native pour gérer certains scénarios spécifiques, mais nous devrions essayer d'améliorer PowerShell afin que l'utilisation d'exes natifs soit plus naturelle et prévisible

@ PowerShell / powershell-Committee a examiné cela et convient que --% devrait signifier traiter les arguments comme ils le feraient sur leurs plates-formes pertinentes, ce qui signifie qu'ils se comportent différemment sous Windows et Linux, mais cohérents dans Windows et cohérents sous Linux. Il serait plus déroutant pour l'utilisateur d'introduire un nouveau sceau. Nous laisserons la mise en œuvre à l'ingénieur sur la façon de l'activer.

Les scripts multiplateformes devraient être conscients de cette différence de comportement, mais il semble peu probable que les utilisateurs y parviennent. Si les commentaires des utilisateurs indiquent qu'il y a un besoin en raison d'une utilisation plus multiplateforme, nous pouvons alors revenir sur l'introduction d'un nouveau sigil.

Pour la deuxième fois, j'ai été mordu par ces différences d'argument de chaîne natives vs cmdlets lors de l'utilisation de

Voici le résultat des appels PowerShell (echo.exe provient de "C: \ Program Files \ Git \ usrbin \ echo.exe")

Maintenant, je sais que je devrais faire attention à cette bizarrerie " :

> echo.exe '"test'
test

Mais cette bizarrerie me dépasse ...

echo.exe '^\+.+;'
^\+.+;
echo.exe '^\+.*;'
^+.*;

Dans le second cas, je dois mettre le double \ pour passer \ à la commande native, dans le premier cas, je n'ai pas besoin de le faire 😑

Je comprends que ce serait un changement radical pour changer ce comportement afin qu'il n'y ait pas de différence entre les applets de commande et les commandes natives. Cependant, je pense que des bizarreries comme celle-ci dissuadent les gens d'utiliser PowerShell comme shell par défaut.

@mpawelski Je viens d'essayer cela sur ma boîte Windows avec git 2.20.1.vfs.1.1.102.gdb3f8ae et cela ne me concerne pas avec 6.2-RC.1. Ran it plusieurs fois et il fait constamment écho ^\+.+;

@ SteveL-MSFT, je pense que @mpawelski a accidentellement spécifié la même commande deux fois. Vous verrez le problème si vous passez '^\"+.*;' par exemple - notez la partie \" - avec l'attente - raisonnable - que le contenu de la chaîne entre guillemets simples sera transmis tel quel , de sorte que le programme cible externe voit ^\"+.*; comme

# Note how the "\" char. is eaten.
PS> bash -c 'printf %s "$1"' - '^\"+.*;'
^"+.*; 

#"# Running the very same command from Bash does NOT exhibit the problem:
$ bash -c 'printf %s "$1"' - '^\"+.*;'
^\"+.*; 

-% était, si je comprends bien, pour permettre la réutilisation des lignes de commande cmd.exe existantes telles quelles.

Ce n'est pas tout à fait ça. --% été introduit car de nombreux utilitaires de ligne de commande Windows prennent des arguments qui sont interprétés par PowerShell, ce qui fubarise complètement l'appel de l'utilitaire. Cela peut rapidement gêner les gens s'ils ne peuvent plus facilement utiliser leurs utils natifs préférés. Si j'avais un quart pour chaque fois que j'ai répondu à des questions sur les commandes exe natives SO et ailleurs RE qui ne fonctionnent pas correctement à cause de ce problème, je pourrais probablement emmener la famille dîner à Qoba. :-) Par exemple, tf.exe autorise ; dans le cadre de la spécification d'un espace de travail. Git permet {} et @~1 , etc, etc, etc.

--% été ajouté pour dire à PowerShell de NE PAS analyser le reste de la ligne de commande - envoyez-le simplement "tel quel" à l'exe natif. Avec le seul frottement, autorisez les variables en utilisant la syntaxe cmd env var. C'est un peu moche mais mec, c'est vraiment très utile encore sous Windows.

RE en faisant une applet de commande, je ne suis pas sûr de voir comment cela fonctionne. --% est un signal adressé à l'analyseur pour qu'il supprime l'analyse jusqu'à EOL.

Franchement, en tant qu'utilisateur de longue date de PowerShell et de cette fonctionnalité en particulier, il me semble logique d'utiliser le même opérateur sur d'autres plates-formes pour signifier simplement - abattre l'analyse jusqu'à EOL. Il y a la question de savoir comment autoriser une certaine forme de substitution de variable. Même si cela semble un peu moche, sous macOS / Linux, vous pouvez prendre% envvar% et remplacer la valeur de n'importe quelle variable env correspondante. Ensuite, il pourrait être portable entre les plates-formes.

Le fait est que si vous ne le faites pas, vous vous retrouvez avec un code conditionnel - pas exactement ce que j'appellerais portable:

$env:Index = 1
if ($IsWindows) {
    git show --% @~%Index%
}
else {
    git show --$ @~$Index
}

Je préférerais ce travail sur toutes les plateformes:

$env:Index = 1
git show --% @~%Index%

Le comportement sous Windows doit rester tel quel en raison de la compatibilité.

@rkeithhill

Ce n'est pas tout à fait ça.

Par processus d'élimination, les lignes de commande antérieures à PowerShell dans le monde Windows ont été écrites pour cmd.exe - et c'est précisément ce que --% intention d'émuler , comme en témoigne ce qui suit:

  • --% développe cmd.exe -style %...% références de variables d'environnement, telles que %USERNAME% (si aucun shell et aucune logique spéciale n'étaient impliqués, ces jetons seraient passés _textuellement_)

  • directement de la bouche du PowerShell (si vous voulez; emphase ajoutée):

Le Web regorge de lignes de commande écrites pour Cmd.exe . Ces lignes de commande fonctionnent assez souvent dans PowerShell, mais lorsqu'elles incluent certains caractères, par exemple un point-virgule (;), un signe dollar ($) ou des accolades, vous devez apporter des modifications, en ajoutant probablement des guillemets. Cela semblait être la source de nombreux maux de tête mineurs.

Pour aider à résoudre ce scénario, nous avons ajouté une nouvelle façon «d'échapper» à l'analyse des lignes de commande. Si vous utilisez un paramètre magique -%, nous arrêtons notre analyse normale de votre ligne de commande et passons à quelque chose de beaucoup plus simple. Nous ne correspondons pas aux citations. Nous ne nous arrêtons pas au point-virgule. Nous n'étendons pas les variables PowerShell. Nous développons les variables d'environnement si vous utilisez la syntaxe Cmd.exe (par exemple% TEMP%). En dehors de cela, les arguments jusqu'à la fin de la ligne (ou du tuyau, si vous utilisez la tuyauterie) sont transmis tels quels. Voici un exemple:

Notez que cette approche ne convient pas au monde _Unix_ (pour récapituler ci-dessus):

  • Le globbing d'arguments sans guillemets tels que *.txt ne fonctionnera pas.

  • Les shells traditionnels (de type POSIX) dans le monde Unix n'utilisent _pas_ %...% pour faire référence aux variables d'environnement; ils attendent la syntaxe $... .

  • Il n'y a (heureusement) aucune ligne de commande _raw_ dans le monde Unix: tout exécutable externe doit recevoir un _array_ de _literals_, donc c'est toujours PowerShell ou CoreFx qui doit d'abord analyser la ligne de commande en arguments.

  • Les shells traditionnels (de type POSIX) dans le monde Unix acceptent les chaînes '...' (entre guillemets simples), que --% ne reconnaît pas - voir # 10831.

Mais même dans le monde Windows, --% a des limitations sévères et non évidentes :

  • De toute évidence, vous ne pouvez pas référencer directement les variables PowerShell dans votre ligne de commande si vous utilisez --% ; la seule solution de contournement - lourde - consiste à définir temporairement des variables _environnement_, que vous devez ensuite référencer avec %...% .
  • Vous ne pouvez pas placer la ligne de commande dans (...) - car la fermeture ) est interprétée comme une partie littérale de la ligne de commande.
  • Vous ne pouvez pas suivre la ligne de commande avec ; et une autre instruction - car le ; est interprété comme une partie littérale de la ligne de commande.
  • Vous ne pouvez pas utiliser --% dans un bloc de script sur une seule ligne - car la fermeture } est interprétée comme une partie littérale de la ligne de commande.
  • Vous ne pouvez pas utiliser les redirections - car elles sont traitées comme une partie littérale de la ligne de commande - cependant, vous pouvez utiliser cmd --% /c ... > file , pour laisser cmd.exe gérer la redirection.
  • Vous ne pouvez pas utiliser de caractères de continuation de ligne - ni ceux de PowerShell ( ` ) ni ceux de cmd.exe ( ^ ) - ils seront traités comme des _literals_.

    • --% analyse uniquement (au plus) jusqu'à la fin de la ligne.


Heureusement, nous avons déjà _do_ une syntaxe multiplateforme: _la syntaxe propre à PowerShell_.

Oui, utiliser cela vous oblige à savoir ce que _PowerShell_ considère comme des métacaractères, qui est un _superset_ à la fois de ce que cmd.exe et des shells de type POSIX tels que Bash considèrent des métacaractères, mais c'est le prix à payer pour une plate-forme plus riche. expérience de ligne de commande agnostique.

Il est donc dommage que PowerShell gère si mal la citation des caractères " - ce qui est le sujet même de ce problème et qui est résumé dans ce numéro de documentation .

@rkeithhill , j'ai commencé un peu une tangente; laissez-moi essayer de le fermer:

  • --% fait déjà _est_ implémenté à partir de PowerShell Core 6.2.0; par exemple,
    /bin/echo --% %HOME% affiche la valeur de la variable d'environnement HOME ; par contre,
    /bin/ls --% *.txt ne fonctionnera pas comme prévu, car le *.txt est passé comme un littéral.

  • En fin de compte, lorsque nous n'utilisons pas --% , nous devons aider les utilisateurs à diagnostiquer à quoi ressemble le tableau de ligne de commande / d'arguments qui est construit _en fin de compte_ (ce qui nous ramène au vénérable # 1761):

    • Votre echoArgs.exe utile fait exactement cela, et dans le problème lié, vous avez raisonnablement demandé que cette fonctionnalité fasse partie de PowerShell lui-même.
    • @ SteveL-MSFT a réfléchi à l'inclusion de la ligne de commande résultante dans l'enregistrement d'erreur.

Enfin, pour appliquer mon argument précédent - en utilisant la propre syntaxe de PowerShell comme étant intrinsèquement portable - à votre exemple:

# Works cross-platform, uses PowerShell syntax 
# Note: No need for an aux. *environment* variable (which should be cleaned up afterward)
$Index = 1
git show "@~$Index"

# Alternative, quoting just the '@'
git show `@~$Index

Oui, cela nécessite que vous sachiez qu'un token initial @ est un métacaractère que vous devez donc citer, mais à mesure que PowerShell est de plus en plus utilisé, la prise de conscience de ces exigences devrait également se généraliser.

Pour info, ce problème devrait être presque le même sous Windows et les likes Unix. L'implémentation CoreFx d'Unix SDProcess a une chose appelée ParseArgumentsIntoList , qui implémente CommandLineToArgvW presque exactement sans _setargv commutateurs (et avec les "" entre guillemets → " fonctionnalité). Unix ne devrait pas être un problème supplémentaire car dans la forme actuelle, il est cassé de la même manière que Windows.

_setargv n'est pas quelque chose que tous les programmes utilisent après tout, et cela ne vaut probablement pas la peine de le considérer car, eh bien, cela est un peu effacé par les changements de comportement entre les versions CRT . Le mieux que nous puissions et devrions faire est de tout entourer de guillemets doubles, d'ajouter de jolis backslahs, et c'est tout.

Un autre exemple où les arguments ne sont pas analysés correctement:

az "myargs&b"

Dans ce cas, az obtient myargs et b est tenté d'être exécuté en tant que nouvelle commande.

La solution de contournement est: az -% "myargs & b"

@ SteveL-MSFT, nous pouvons supprimer l'étiquette Waiting - DotNetCore , étant donné que la fonctionnalité requise - la propriété ProcessStartInfo.ArgumentList basée sur la collection est disponible depuis .NET Core 2.1

@TSlivede est au courant de la nouvelle méthode et prévoit de l'utiliser, mais le RFC associé, https://github.com/PowerShell/PowerShell-RFC/pull/90 , languit malheureusement.

Je suggère que nous continuions la discussion sur la mise en œuvre là-bas.

Cependant, dans la discussion RFC, @joeyaiello parle de faire des changements une fonctionnalité expérimentale, mais il devient de plus en plus clair pour moi que vous ne pouvez pas corriger le comportement de citation sans casser massivement le code existant:

Quiconque a dû _travailler autour_:

  • l'incapacité de passer un argument de chaîne vide ( foo.exe "" passe actuellement _no_ arguments)
  • la suppression efficace inattendue des guillemets doubles en raison du manque d'échappatoire automatique des guillemets doubles incorporés ( foo.exe '{ "foo": "bar" }' étant passé comme échappé incorrectement "{ "foo": "bar" }" )
  • les bizarreries de certaines CLI telles que msiexec qui n'acceptent pas certains arguments entre guillemets _ comme un tout_ ( foo.exe foo="bar none" étant passé comme "foo=bar none" ).
    Remarque: C'est msiexec qui est à blâmer ici, et avec les modifications proposées appliquées, passer le formulaire de devis requis foo="bar none" exigera alors --% .

sera en difficulté, car les solutions de contournement seront _brégées_ avec les modifications proposées appliquées.

Par conséquent, une question supplémentaire est:

  • Comment pouvons-nous rendre le comportement correct disponible au moins en tant que fonctionnalité _opt-in_?

  • Un problème fondamental avec de tels mécanismes - typiquement, par variable de préférence, mais de plus en plus aussi par les instructions using - est la portée dynamique de PowerShell; c'est-à-dire que le comportement opté sera également appliqué par défaut au code appelé _from_ son code opté, ce qui est problématique.

    • Il est peut-être temps d'introduire généralement une portée _lexique_ des fonctionnalités, une généralisation de la proposition using strict à portée lexicale dans la RFC en mode strict lexical - également languissante - créée par @lzybkr.

    • Quelque chose comme un using preference ProperArgumentQuoting portée lexicale? (Nom évidemment négociable, mais j'ai du mal à en trouver un).

Compte tenu de toutes ces mises en garde, et du fait que c'est également un problème avec l'invocation directe, où nous ne pouvons pas simplement ajouter un nouveau paramètre, je suis fermement en faveur de la rupture de l'ancien comportement.

Oui, ça cassera probablement beaucoup. Mais vraiment, uniquement parce qu'il était si complètement brisé au départ. Je ne pense pas qu'il soit particulièrement possible de donner la priorité au maintien de ce qui équivaut à un énorme tas de solutions de contournement pour un comportement cassé plutôt qu'à une fonctionnalité qui fonctionne réellement.

En tant que nouvelle version majeure, je pense que la v7 est vraiment la seule chance que nous aurons de rectifier cette situation correctement pendant un certain temps, et nous devrions en profiter. À condition de sensibiliser les utilisateurs, je ne pense pas que la transition sera globalement mal reçue.

Si nous pensons qu'un changement radical est inévitable, alors peut-être devrions-nous concevoir la solution idéale, en tenant compte du fait qu'elle devrait être facile à imprimer dans une session interactive et qu'il est possible d'avoir une version de script qui fonctionne mieux sur toutes les plates-formes.

D'accord, @ vexx32 : le maintien du comportement existant, même si ce n'est que par défaut, restera un problème permanent. Les utilisateurs ne s'attendent pas à ce que le consentement soit nécessaire pour être avec, et, une fois qu'ils le font, ils sont susceptibles d'oublier à l'occasion et / ou de ressentir la nécessité de l'appliquer à chaque fois.

Un shell qui ne transmet pas de manière fiable des arguments aux programmes externes échoue à l'un de ses mandats principaux.

Vous avez certainement _mon_ vote pour apporter le changement de rupture, mais je crains que les autres ne se sentent différemment, notamment parce que la v7 est présentée comme permettant aux utilisateurs de WinPS à long terme de migrer vers PSCore.


@iSazonov : https://github.com/PowerShell/PowerShell-RFC/pull/90 décrit la solution correcte.

Pour récapituler son esprit:

PowerShell, en tant que shell, doit analyser les arguments en fonction de _ ses_ règles, puis transmettre les valeurs d'argument étendues résultantes _verbatim_ au programme cible - les utilisateurs ne devraient jamais avoir à réfléchir à la manière dont PowerShell fait que cela se produise; tout ce dont ils devraient avoir à se soucier, c'est d'avoir la bonne syntaxe _PowerShell_.

  • Sur les plates-formes de type Unix, ProcessStartInfo.ArgumentList nous donne maintenant un moyen de l'implémenter parfaitement, étant donné que le _array_ des valeurs d'argument étendues peut être passé _as-is_ au programme cible, car c'est ainsi que le passage d'arguments - raisonnablement - fonctionne dans ce monde.

  • Sous Windows, nous devons faire face à la triste réalité de l'anarchie qu'est l'analyse de la ligne de commande Windows , mais, en tant que shell, il nous incombe de le faire fonctionner dans les coulisses, autant que possible_, ce que le RFC décrit - bien que je viens de découvrir une ride qui utilise uniquement ProcessStartInfo.ArgumentList pas assez bien, malheureusement (en raison de la généralisation des _batch files_ comme points d'entrée CLI, comme le montre az[.cmd] de @ SteveL-MSFT --% .

Peut-être que PSSA peut aider à atténuer le changement de rupture en avertissant les utilisateurs qu'ils utilisent un format d'argument qui sera modifié.

Je pense que nous devons envisager d'adopter quelque chose comme les fonctionnalités optionnelles pour avancer sur ce changement de rupture avec d' autres .

Y a-t-il vraiment une valeur à maintenir le comportement existant? Autre que la rétrocompatibilité, je veux dire.

Je ne pense pas que cela vaille la peine de maintenir deux chemins de code pour cela, simplement pour conserver une implémentation cassée car certains anciens codes pourraient en avoir besoin de temps en temps. Je ne pense pas qu'il soit déraisonnable de s'attendre à ce que folx mette à jour son code de temps en temps. 😅

Doublement si nous nous attendons à ce que la PS7 remplace WinPS; la seule raison pour laquelle je peux voir pour conserver le comportement pour la v7 est si nous nous attendons à ce que les gens utilisent le même script pour exécuter des commandes à la fois sur 5.1 et 7, ce qui (espérons-le) devrait être un cas assez rare si la PS7 remplace bien la 5.1.

Et même dans ce cas, il ne serait pas trop difficile pour les utilisateurs de tenir compte des deux. À condition que nous ne changions pas la syntaxe réelle du langage, il devrait être assez facile de faire quelque chose comme ceci:

if ($PSVersionTable.PSVersion.Major -lt 7) {
    # use old form
}
else {
    # use new form
}

À condition que nous rendions les utilisateurs conscients de la différence, je pense que ce serait un soulagement bienvenu de la douleur que cela a été de gérer des exécutables natifs étranges dans PS jusqu'à présent. 😄

Comme @TylerLeonhardt l'a mentionné dans la discussion sur les fonctionnalités optionnelles - l'implémentation signifie que vous maintenez désormais _multiple_ implémentations distinctes dont chacune doit être maintenue et testée, ainsi que la maintenance et le test du framework de fonctionnalités optionnelles. Cela ne semble pas vraiment en valoir la peine, tbh.

La rétrocompatibilité avec https://github.com/PowerShell/PowerShell/issues/1761 et https://github.com/PowerShell/PowerShell/issues/10675. «Réparer» l'interopérabilité avec les commandes natives est quelque chose que j'aimerais résoudre pour vNext. Donc, si quelqu'un voit des problèmes existants ou nouveaux dans cette catégorie, cc-moi et je le taguerai de manière appropriée (ou si vous avez l'autorisation de triage, marquez-le comme les autres).

Les fonctionnalités optionnelles sont des modules :-) Avoir des fonctionnalités optionnelles dans Engine est un gros casse-tête pour le support, spécialement pour le support Windows. Nous pourrions modulariser Engine en réduisant les dépendances internes et en remplaçant les API internes par des API publiques - après cela, nous pourrions implémenter facilement des fonctionnalités optionnelles dans Engine.

@iSazonov l' une des choses que mon équipe examinera dans vNext est de rendre le moteur plus modulaire :)

Quelle est la solution recommandée ici pour les utilisateurs finaux?

C'est artificiel, mais c'est le moyen le plus simple d'obtenir la bonne gestion * ArgumentList à partir du framework .NET lui-même:

Add-Type -AssemblyName "System"
function startProcess([string] $FileName, [string[]] $ArgumentList) {
    $proc = ([System.Diagnostics.Process]::new())
    $proc.StartInfo.UseShellExecute = $false
    $proc.StartInfo.FileName = $FileName
    $proc.StartInfo.CreateNoWindow = $true
    foreach ($a in $ArgumentList) { $proc.StartInfo.ArgumentList.Add($a) }
    $proc.Start()
    return $proc
}

startProcess -FileName 'C:\Program Files\nodejs\node.exe' -ArgumentList '-e','console.log(process.argv.join(''\n''))','--','abc" \" messyString'

Bien sûr, vous pouvez le rendre moins artificiel à utiliser ici en utilisant des paramètres de position et des astuces get-command .

* AVIS DE NON-RESPONSABILITÉ: Il n'existe pas de méthode correcte unique pour analyser une ligne de commande sous Windows. Par «correct», j'entends le style MSVCRT de conversion cmdline from / to argv, implémenté par .NET sur toutes les plates-formes pour la gestion d'ArgumentList, le traitement main (string[] args) et les appels de spawn externes sous Unix. Cet exemple est fourni EN L'ÉTAT sans aucune garantie d'interopérabilité générale. Voir aussi la section "ligne de commande Windows" de la documentation proposée de NodeJS child_process .

@ Artoria2e5 C'est exactement la conclusion à laquelle je suis arrivé. System.Diagnostics.Process est le seul moyen fiable d'exécuter un exécutable externe, mais l'échappement d'arguments peut devenir délicat en raison des règles stdlib:

2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal " 
N backslashes ==> N backslashes

En conséquence, j'ai mis au point la logique suivante pour échapper les arguments les envelopper dans des guillemets doubles " pour l'exécution du processus:
https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L244

De plus, il peut être difficile d'obtenir STDOUT et STDERR en temps réel pendant l'exécution de l'exécutable externe, j'ai donc créé ce package

https://github.com/choovick/ps-invoke-externalcommand

que j'utilise beaucoup sous Windows, Linux et Mac et jusqu'à présent sans problèmes et je peux passer des arguments avec une nouvelle ligne et d'autres caractères spéciaux.

GitHub
Contribuez au développement de choovick / ps-invoke-externalcommand en créant un compte sur GitHub.
GitHub
Contribuez au développement de choovick / ps-invoke-externalcommand en créant un compte sur GitHub.

@choovick toute la redirection stdio est géniale!

Je suis cependant légèrement en désaccord sur la partie qui s'échappe, car il existe déjà une chose qui le fait pour vous, appelée ArgumentList. Je comprends que c'est un ajout relatif récent (?), Et cela devient décevant puisque MS a oublié de mettre un initialiseur String, String [] pour SDProcessStartInfo. (Y a-t-il une place pour ces… propositions d'interface .NET?)


bavardage

Ma fonction d'échappement de cet exemple NodeJS est un peu différente de la vôtre: elle utilise l'échappement non documenté (mais trouvé dans .NET core et MSVCRT) "" escape pour les guillemets. Cela simplifie en quelque sorte le travail de sélection de la barre oblique inverse. Je l'ai fait principalement parce qu'il était utilisé pour le tout puissant cmd, qui ne comprend pas que \" ne doit pas décompresser le reste de la chaîne. Au lieu de me débattre avec \^" j'ai pensé que je serais mieux avec quelque chose qui est en usage secret depuis le début des temps.

@ Artoria2e5 Malheureusement, ArgumentList n'est pas disponible dans PowerShell 5.1 sous Windows, en utilisant votre exemple, je reçois:

`` Vous ne pouvez pas appeler une méthode sur une expression à valeur nulle.
Dans C: Users \ yser \ dev \ test.ps1: 7 car : 37

  • ... oreach ($ a dans $ ArgumentList) {$ proc.StartInfo.ArgumentList.Add ($ a)}
  • ~ ~ ~ ~ ~ ~ ~ ~

    • CategoryInfo: InvalidOperation: (:) [], RuntimeException

    • FullyQualifiedErrorId: InvokeMethodOnNull

      ''

Depuis la logique d'échappement d'argument personnalisé ...

bavardage

en ce qui concerne `\ ^" `dans NodeJS, je pense que j'ai dû le faire il y a plusieurs années :) et je pense que cela a fonctionné

System.Diagnostics.Process est le seul moyen fiable d'exécuter un exécutable externe

Le problème est que vous n'obtiendrez pas l'intégration avec les flux de sortie de PowerShell et que vous n'obtiendrez pas de comportement de streaming dans le pipeline.

Voici le résumé des solutions de contournement requises si vous souhaitez toujours laisser PowerShell effectuer l'appel (ce qui est certainement préférable) :

  • Si vous avez besoin de passer des arguments avec _embedded_ " chars., _Double_les sous Windows, si possible, ou \ -échappez-les:

    • Sous Windows, lors de l'appel de _batch files_ et si vous savez que le programme cible comprend "" comme un " , utilisez $arg -replace '"', '""'

      • L'utilisation de "" est préférable sous Windows (cela évite le problème Windows PowerShell et fonctionne avec les CLI qui utilisent des fichiers batch comme _stubs_, tels que Node.js et Azure), mais tous les exécutables ne le prennent pas en charge (notamment pas Ruby et Perl).
    • Sinon (toujours sous Unix), utilisez $arg -replace '"', '\"'

    • Remarque: dans _Windows PowerShell_, cela ne fonctionne toujours pas correctement si la valeur contient également des _spaces_, car la présence de littéral \" dans la valeur ne déclenche pas de manière situationnelle _pas_ les guillemets doubles, contrairement à PowerShell Core; par exemple, en passant '3\" of snow' pauses.

    • De plus, avant l'échappement ci-dessus, vous devez doubler l'instance \ qui précède immédiatement " , si elles doivent être traitées comme des littéraux:

      • $arg = $arg -replace '(\\+)"', '$1$1"'
  • Si vous devez passer un argument _empty_, passez '""' .

    • '' -eq $arg ? '""' : $arg (alternative à WinPS: ($arg, '""')['' -eq $arg]
  • Windows PowerShell uniquement, ne faites pas cela dans PS Core (où le problème a été résolu):

    • Si votre argument _contient des espaces_ et _se termine dans_ (un ou plusieurs) \ , doublez l'instance (s) de fin \ .

      • if ($arg -match ' .*?(\\+)$') { $arg = $arg + $Matches[1] }
  • Si cmd / un fichier batch est invoqué avec des arguments qui n'ont _pas_ d'espaces (donc _pas_ déclenchant une double guillemet automatique par PowerShell) mais contenant n'importe lequel des &|<>^,; (par exemple, a&b ), utilisez _embedded entourant les guillemets doubles_ pour vous assurer que PowerShell transmet un jeton entre guillemets et ne rompt donc pas l'appel cmd / batch-file:

    • $arg = '"' + $arg + '"'
  • Si vous devez gérer des exécutables mal comportés tels que msiexec.exe , mettez l'argument entre guillemets:

    • 'foo="bar none"'

Comme indiqué, ces solutions de contournement seront _brégées_, une fois le problème sous-jacent résolu.


Vous trouverez ci-dessous une fonction simple (non avancée) iep (pour "invoquer un programme externe"), qui:

  • Effectue tous les échappements décrits ci-dessus, y compris la casse spéciale automatique pour msiexec et préférant "" échappant à \" selon le programme cible.

    • L'idée est que vous pouvez passer n'importe quel argument en vous concentrant uniquement sur la syntaxe de chaîne de _PowerShell_ et en vous appuyant sur la fonction pour effectuer l'échappement nécessaire afin que la valeur textuelle que PowerShell voit soit également vue par le programme cible.

    • Dans PowerShell _Core_, cela devrait fonctionner de manière assez robuste; dans Windows PowerShell, vous avez toujours des cas extrêmes avec des guillemets doubles incorporés qui se cassent si l'échappement \" doit être utilisé (comme indiqué ci-dessus).

  • Préserve la syntaxe d'appel des commandes shell.

    • Ajoutez simplement iep  à votre ligne de commande.

  • Comme le ferait une invocation directe, il:

    • s'intègre aux flux de PowerShell

    • envoie la sortie ligne par ligne à travers le pipeline

    • définit $LASTEXITCODE fonction du code de sortie du programme externe; cependant, $? ne peut _pas_ être invoqué.

Remarque: la fonction est volontairement minimaliste (pas de déclarations de paramètres, pas d'aide en ligne de commande, nom court (irrégulier)), car elle est censée être aussi discrète que possible: ajoutez simplement iep à votre ligne de commande, etc. devrait marcher.

Exemple d'appel utilisant EchoArgs.exe (installable via Chocolatey à partir d'une session _elevated_ avec choco install echoargs -y ):

PS> iep echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'
Arg 0 is <>
Arg 1 is <a&b>
Arg 2 is <3" of snow>
Arg 3 is <Nat "King" Cole>
Arg 4 is <c:\temp 1\>
Arg 5 is <a \" b>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "" a&b "3\" of snow" "Nat \"King\" Cole" "c:\temp 1\\" "a \\\" b"

Ce qui précède montre la sortie PowerShell Core. Notez comment tous les arguments ont été correctement transmis comme vu textuellement par PowerShell, y compris l'argument vide.

Dans Windows PowerShell, l'argument 3" of snow ne sera pas passé correctement, car l'échappement \" est utilisé en raison de l'appel d'un exécutable inconnu (comme indiqué ci-dessus).

Pour vérifier que les fichiers batch passent correctement les arguments, vous pouvez créer echoargs.cmd comme wrapper pour echoargs.exe :

'@echoargs.exe %*' | Set-Content echoargs.cmd

Invoquer en tant que iep .\echoargs.cmd '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'

Puisqu'un fichier batch est maintenant appelé, "" -escaping est utilisé, ce qui résout le problème 3" of snow lors de l'appel depuis Windows PowerShell.

La fonction fonctionne également sur les plates-formes de type Unix, que vous pouvez vérifier en créant un script shell sh nommé echoargs :

@'
#!/bin/sh
i=0; for a; do printf '%s\n' "\$$((i+=1))=[$a]"; done
'@ > echoargs; chmod a+x echoargs

Invoquer en tant que iep ./echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'


Important : Une version plus complète de cette fonction a depuis été publiée sous le nom ie ( I nvoke (external) E xecutable) in Je viens de publier un module Native , que je vous encourage à utiliser au lieu. Installez le module avec
Install-Module Native -Scope CurrentUser .
Le module contient également une commande ins ( Invoke-NativeShell ) qui répond au cas d'utilisation discuté dans # 13068 - voir https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -671572939 pour plus de détails.

Code source de la fonction iep (utilisez plutôt le module Native - voir ci-dessus):

function iep {

  Set-StrictMode -Version 1
  if (-not (Test-Path Variable:IsCoreClr)) { $IsCoreCLR = $false }
  if (-not (Test-Path Variable:IsWindows)) { $IsWindows = $env:OS -eq 'Windows_NT' }

  # Split into executable name/path and arguments.
  $exe, [string[]] $argsForExe = $args

  # Resolve to the underlying command (if it's an alias) and ensure that an external executable was specified.
  $app = Get-Command -ErrorAction Stop $exe
  if ($app.ResolvedCommand) { $app = $app.ResolvedCommand }
  if ($app.CommandType -ne 'Application') { Throw "Not an external program, non-PS script, or batch file: $exe" }

  if ($argsForExe.Count -eq 0) {
    # Argument-less invocation
    & $exe
  }
  else {
    # Invocation with arguments: escape them properly to pass them through as literals.
    # Decide whether to escape embedded double quotes as \" or as "", based on the target executable.
    # * On Unix-like platforms, we always use \"
    # * On Windows, we use "" where we know it's safe to do. cmd.exe / batch files require "", and Microsoft compiler-generated executables do too, often in addition to supporting \",
    #   notably including Python and Node.js
    #   However, notable interpreters that support \" ONLY are Ruby and Perl (as well as PowerShell's own CLI, but it's better to call that with a script block from within PowerShell).
    #   Targeting a batch file triggers "" escaping, but in the case of stub batch files that simply relay to a different executable, that could still break
    #   if the ultimate target executable only supports \" 
    $useDoubledDoubleQuotes = $IsWindows -and ($app.Source -match '[/\\]?(?<exe>cmd|msiexec)(?:\.exe)?$' -or $app.Source -match '\.(?<ext>cmd|bat|py|pyw)$')
    $doubleQuoteEscapeSequence = ('\"', '""')[$useDoubledDoubleQuotes]
    $isMsiExec = $useDoubledDoubleQuotes -and $Matches['exe'] -eq 'msiexec'
    $isCmd = $useDoubledDoubleQuotes -and ($Matches['exe'] -eq 'cmd' -or $Matches['ext'] -in 'cmd', 'bat')
    $escapedArgs = foreach ($arg in $argsForExe) {
      if ('' -eq $arg) { '""'; continue } # Empty arguments must be passed as `'""'`(!), otherwise they are omitted.
      $hasDoubleQuotes = $arg.Contains('"')
      $hasSpaces = $arg.Contains(' ')
      if ($hasDoubleQuotes) {
        # First, always double any preexisting `\` instances before embedded `"` chars. 
        # so that `\"` isn't interpreted as an escaped `"`.
        $arg = $arg -replace '(\\+)"', '$1$1"'
        # Then, escape the embedded `"` chars. either as `\"` or as `""`.
        # If \" escaping is used:
        # * In PS Core, use of `\"` is safe, because its use triggers enclosing double-quoting (if spaces are also present).
        # * !! In WinPS, sadly, that isn't true, so something like `'foo="bar none"'` results in `foo=\"bar none\"` -
        #   !! which - due to the lack of enclosing "..." - is seen as *2* arguments by the target app, `foo="bar` and `none"`.
        #   !! Similarly, '3" of snow' would result in `3\" of snow`, which the target app receives as *3* arguments, `3"`, `of`, and `snow`.
        #   !! Even manually enclosing the value in *embedded* " doesn't help, because that then triggers *additional* double-quoting.
        $arg = $arg -replace '"', $doubleQuoteEscapeSequence
    }
      elseif ($isMsiExec -and $arg -match '^(\w+)=(.* .*)$') { 
        # An msiexec argument originally passed in the form `PROP="value with spaces"`, which PowerShell turned into `PROP=value with spaces`
        # This would be passed as `"PROP=value with spaces"`, which msiexec, sady, doesn't recognize (`PROP=valueWithoutSpaces` works fine, however).
        # We reconstruct the form `PROP="value with spaces"`, which both WinPS And PS Core pass through as-is.
        $arg = '{0}="{1}"' -f $Matches[1], $Matches[2]
      }
      # As a courtesy, enclose tokens that PowerShell would pass unquoted in "...", 
      # if they contain cmd.exe metachars. that would break calls to cmd.exe / batch files.
      $manuallyDoubleQuoteForCmd = $isCmd -and -not $hasSpaces -and $arg -match '[&|<>^,;]'
      # In WinPS, double trailing `\` instances in arguments that have spaces and will therefore be "..."-enclosed,
      # so that `\"` isn't mistaken for an escaped `"` - in PS Core, this escaping happens automatically.
      if (-not $IsCoreCLR -and ($hasSpaces -or $manuallyDoubleQuoteForCmd) -and $arg -match '\\') {
        $arg = $arg -replace '\\+$', '$&$&'
      }
      if ($manuallyDoubleQuoteForCmd) {
        # Wrap in *embedded* enclosing double quotes, which both WinPS and PS Core pass through as-is.
        $arg = '"' + $arg + '"'
      }
      $arg
    }
    # Invoke the executable with the properly escaped arguments.
    & $exe $escapedArgs
  }
}

@ mklement0 Impressionnant, mais voici quelques couples qui ne fonctionnent pas pour moi sous Windows:

iep echoargs 'somekey="value with spaces"' 'te\" st'

Arg 0 is <somekey="value>
Arg 1 is <with>
Arg 2 is <spaces">
Arg 3 is <te\">
Arg 4 is <st>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" somekey=\"value with spaces\" te\\\" st

voici mon tableau d'arguments de test :)

$Arguments = @(
    'trippe slash at the end \\\',
    '4 slash at the end \\\\',
    '\\servername\path\',
    'path=\\servername\path\',
    'key="\\servername\pa th\"',
    '5 slash at the end \\\\\',
    '\\" double slashed double quote',
    'simple',
    'white space',
    'slash at the end \',
    'double slash at the end \\',
    'trippe slash at the end \\\',
    'trippe slash at the end with space \\\ ',
    '\\" double slashed double quote',
    'double slashed double quote at the end \\"',
    '\\\" triple slashed double quote',
    'triple slashed double quote at the end \\\"',
    # slash
    'single slashes \a ^ \: \"',
    'path="C:\Program Files (x86)\test\"'
    # quotes
    'double quote " and single quote ''',
    # windows env var syntax
    "env var OS: %OS%",
    # utf16
    ('"utf16 ETHIOPIC WORDSPACE: \u1361"' | ConvertFrom-Json),
    # special chars
    "newLine`newLine"
    "tab`tab"
    "backspace`bbackspace"
    "carriage`rafter",
    "formFeed`fformFeed",
    # JSON Strings
    @"
[{"_id":"5cdab57e4853ea7b5a707070","index":0,"guid":"25319946-950e-4fe8-9586-ddd031cbb0fc","isActive":false,"balance":"`$2,841.15","picture":"http://placehold.it/32x32","age":39,"eyeColor":"blue","name":{"first":"Leach","last":"Campbell"},"company":"EMOLTRA","email":"[email protected]","phone":"+1 (864) 412-3166","address":"127 Beadel Street, Vivian, Vermont, 1991","about":"Ex labore non enim consectetur id ullamco nulla veniam Lorem velit cillum aliqua amet nostrud. Occaecat ipsum do est qui sint aliquip anim culpa laboris tempor amet. Aute sint anim est sint elit amet nisi veniam culpa commodo nostrud cupidatat in ex.","registered":"Monday, August 25, 2014 4:04 AM","latitude":"-12.814443","longitude":"75.880149","tags":["pariatur","voluptate","sint","Lorem","eiusmod"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Lester Bender"},{"id":1,"name":"Concepcion Jarvis"},{"id":2,"name":"Elsie Whitfield"}],"greeting":"Hello, Leach! You have 10 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57e8cd0ac577ab534a4","index":1,"guid":"0be10c87-6ce7-46c4-8dd6-23b1d9827538","isActive":false,"balance":"`$1,049.56","picture":"http://placehold.it/32x32","age":33,"eyeColor":"green","name":{"first":"Lacey","last":"Terrell"},"company":"XSPORTS","email":"[email protected]","phone":"+1 (858) 511-2896","address":"850 Franklin Street, Gordon, Virginia, 4968","about":"Eiusmod nostrud mollit occaecat Lorem consectetur enim pariatur qui eu. Proident aliqua sunt incididunt Lorem adipisicing ea esse do ullamco excepteur duis qui. Irure labore cillum aliqua officia commodo incididunt esse ad duis ea. Occaecat officia officia laboris veniam id dolor minim magna ut sit. Aute quis occaecat eu veniam. Quis exercitation mollit consectetur magna officia sit. Irure ullamco laborum cillum dolore mollit culpa deserunt veniam minim sunt.","registered":"Monday, February 3, 2014 9:19 PM","latitude":"-82.240949","longitude":"2.361739","tags":["nostrud","et","non","eiusmod","qui"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Meyers Dillard"},{"id":1,"name":"Jacobson Franco"},{"id":2,"name":"Hunt Hernandez"}],"greeting":"Hello, Lacey! You have 8 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57eae2f9bc5184f1768","index":2,"guid":"3c0de017-1c2a-470e-87dc-5a6257e8d9d9","isActive":true,"balance":"`$3,349.49","picture":"http://placehold.it/32x32","age":20,"eyeColor":"green","name":{"first":"Knowles","last":"Farrell"},"company":"DAYCORE","email":"[email protected]","phone":"+1 (971) 586-2740","address":"150 Bath Avenue, Marion, Oregon, 991","about":"Eiusmod sint commodo eu id sunt. Labore esse id veniam ea et laborum. Dolor ad cupidatat Lorem amet. Labore ut commodo amet commodo. Ipsum reprehenderit voluptate non exercitation anim nostrud do. Aute incididunt ad aliquip aute mollit id eu ea. Voluptate ex consequat velit commodo anim proident ea anim magna amet nisi dolore.","registered":"Friday, September 28, 2018 7:51 PM","latitude":"-11.475201","longitude":"-115.967191","tags":["laborum","dolor","dolor","magna","mollit"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Roxanne Griffith"},{"id":1,"name":"Walls Moore"},{"id":2,"name":"Mattie Carney"}],"greeting":"Hello, Knowles! You have 8 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57e80ff4c4085cd63ef","index":3,"guid":"dca20009-f606-4b99-af94-ded6cfbbfa38","isActive":true,"balance":"`$2,742.32","picture":"http://placehold.it/32x32","age":26,"eyeColor":"brown","name":{"first":"Ila","last":"Hardy"},"company":"OBLIQ","email":"[email protected]","phone":"+1 (996) 556-2855","address":"605 Hillel Place, Herald, Delaware, 9670","about":"Enim eiusmod laboris amet ex laborum do dolor qui occaecat ex do labore quis sunt. Veniam magna non nisi ipsum occaecat anim ipsum consectetur ex laboris aute ut consectetur. Do eiusmod tempor dolore eu in dolore qui anim non et. Minim amet exercitation in in velit proident sint aliqua Lorem reprehenderit labore exercitation.","registered":"Friday, April 21, 2017 6:33 AM","latitude":"64.864232","longitude":"-163.200794","tags":["tempor","eiusmod","mollit","aliquip","aute"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Duncan Guy"},{"id":1,"name":"Jami Maxwell"},{"id":2,"name":"Gale Hutchinson"}],"greeting":"Hello, Ila! You have 7 unread messages.","favoriteFruit":"banana"},{"_id":"5cdab57ef1556326f77730f0","index":4,"guid":"f2b3bf60-652f-414c-a5cf-094678eb319f","isActive":true,"balance":"`$2,603.20","picture":"http://placehold.it/32x32","age":27,"eyeColor":"brown","name":{"first":"Turner","last":"King"},"company":"DADABASE","email":"[email protected]","phone":"+1 (803) 506-2511","address":"915 Quay Street, Hinsdale, Texas, 9573","about":"Consequat sunt labore tempor anim duis pariatur ad tempor minim sint. Nulla non aliqua veniam elit officia. Ullamco et irure mollit nulla do eiusmod ullamco. Aute officia elit irure in adipisicing et cupidatat dolor in sint elit dolore labore. Id esse velit nisi culpa velit adipisicing tempor sunt. Eu sunt occaecat ex pariatur esse.","registered":"Thursday, May 21, 2015 7:44 PM","latitude":"88.502961","longitude":"-119.654437","tags":["Lorem","culpa","labore","et","nisi"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Leanne Lawson"},{"id":1,"name":"Jo Shepard"},{"id":2,"name":"Effie Barnes"}],"greeting":"Hello, Turner! You have 6 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57e248f8196e1a60d05","index":5,"guid":"875a12f0-d36a-4e7b-aaf1-73f67aba83f8","isActive":false,"balance":"`$1,001.89","picture":"http://placehold.it/32x32","age":38,"eyeColor":"blue","name":{"first":"Petty","last":"Langley"},"company":"NETUR","email":"[email protected]","phone":"+1 (875) 505-2277","address":"677 Leonard Street, Ticonderoga, Utah, 1152","about":"Nisi do quis sunt nisi cillum pariatur elit dolore commodo aliqua esse est aute esse. Laboris esse mollit mollit dolor excepteur consequat duis aute eu minim tempor occaecat. Deserunt amet amet quis adipisicing exercitation consequat deserunt sunt voluptate amet. Ad magna quis nostrud esse ullamco incididunt laboris consectetur.","registered":"Thursday, July 31, 2014 5:16 PM","latitude":"-57.612396","longitude":"103.91364","tags":["id","labore","deserunt","cillum","culpa"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Colette Mullen"},{"id":1,"name":"Lynnette Tanner"},{"id":2,"name":"Vickie Hardin"}],"greeting":"Hello, Petty! You have 9 unread messages.","favoriteFruit":"banana"},{"_id":"5cdab57e4df76cbb0db9be43","index":6,"guid":"ee3852fe-c597-4cb6-a336-1466e8978080","isActive":true,"balance":"`$3,087.87","picture":"http://placehold.it/32x32","age":33,"eyeColor":"brown","name":{"first":"Salas","last":"Young"},"company":"PLAYCE","email":"[email protected]","phone":"+1 (976) 473-2919","address":"927 Elm Place, Terlingua, North Carolina, 2150","about":"Laborum laboris ullamco aliquip occaecat fugiat sit ex laboris veniam tempor tempor. Anim quis veniam ad commodo culpa irure est esse laboris. Fugiat nostrud elit mollit minim. Velit est laborum ut quis anim velit aute enim culpa amet ipsum.","registered":"Thursday, October 1, 2015 10:59 AM","latitude":"-57.861212","longitude":"69.823065","tags":["eu","est","et","proident","nisi"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Day Solomon"},{"id":1,"name":"Stevens Boyd"},{"id":2,"name":"Erika Mayer"}],"greeting":"Hello, Salas! You have 10 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57ed3c91292d30e141d","index":7,"guid":"ef7c0beb-8413-4f39-987f-022c4e8ec482","isActive":false,"balance":"`$2,612.45","picture":"http://placehold.it/32x32","age":36,"eyeColor":"brown","name":{"first":"Gloria","last":"Black"},"company":"PULZE","email":"[email protected]","phone":"+1 (872) 513-2364","address":"311 Guernsey Street, Hatteras, New Mexico, 2241","about":"Laborum sunt exercitation ea labore ullamco dolor pariatur laborum deserunt adipisicing pariatur. Officia velit duis cupidatat eu officia magna magna deserunt do. Aliquip cupidatat commodo duis aliquip in aute dolore occaecat esse ad. Incididunt est magna in pariatur ut do ex sit minim cupidatat culpa. Voluptate eu veniam cupidatat exercitation.","registered":"Friday, June 26, 2015 7:59 AM","latitude":"38.644208","longitude":"-45.481555","tags":["sint","ea","anim","voluptate","elit"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Abby Walton"},{"id":1,"name":"Elsa Miranda"},{"id":2,"name":"Carr Abbott"}],"greeting":"Hello, Gloria! You have 5 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57edc91491fb70b705d","index":8,"guid":"631ff8a0-ce4c-4111-b1e4-1d112f4ecdc7","isActive":false,"balance":"`$2,550.70","picture":"http://placehold.it/32x32","age":25,"eyeColor":"brown","name":{"first":"Deirdre","last":"Huber"},"company":"VERBUS","email":"[email protected]","phone":"+1 (871) 468-3420","address":"814 Coles Street, Bartonsville, Tennessee, 7313","about":"Ipsum ex est culpa veniam voluptate officia consectetur quis et irure proident pariatur non. In excepteur est aliqua duis duis. Veniam consectetur cupidatat reprehenderit qui qui aliqua.","registered":"Monday, April 1, 2019 2:33 AM","latitude":"-75.702323","longitude":"45.165458","tags":["labore","aute","nisi","laborum","laborum"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Genevieve Clarke"},{"id":1,"name":"Black Sykes"},{"id":2,"name":"Watson Hudson"}],"greeting":"Hello, Deirdre! You have 8 unread messages.","favoriteFruit":"strawberry"}]
"@
)

System.Diagnostics.Process a des limitations lorsqu'il est utilisé, c'est pourquoi j'ai dû écrire mon propre runner avec des capacités pour capturer STDOUT STDERR et la combinaison de puis en variables, ce qui est suffisant pour mes cas d'utilisation, mais pas parfait.

Edit: comme vous l'avez dit, il semble y avoir des cas extrêmes dans Windows Poweshell 5. J'ai testé sur Powershell 6 et cela fonctionne très bien! Malheureusement, je dois gérer Poweshell 5 jusqu'à ce que 7 le prenne en charge dans le cas de Windows ...

Impressionnant, mais voici quelques couples qui ne fonctionnent pas pour moi sous windows:

Oui, ces limitations s'appliquent à _Windows PowerShell_, avec des exécutables pour lesquels la prise en charge de "" échappement de " ne peut pas être supposée, comme détaillé dans mon commentaire précédent.
Si vous êtes prêt à assumer la prise en charge "" échappement de

Et, pour confirmer ce que vous avez dit dans votre modification: Dans PowerShell _Core_:

  • iep echoargs 'somekey="value with spaces"' 'te\" st' fonctionne bien.
  • Votre tableau d'arguments de test semble également fonctionner correctement.

Si vous êtes prêt à assumer la prise en charge "" échappement de

Merci, je vais essayer dans un proche avenir. Je traite avec sqlcmd à l'occasion et il ne prend pas en charge "" à coup sûr. Dans ce cas, il est facile de fournir une option pour ignorer la logique d'échappement pour des arguments spécifiques.

La façon dont Windows analyse les arguments de ligne de commande peut être trouvée dans Analyse des arguments de ligne de commande C ++ :

Le code de démarrage Microsoft C / C ++ utilise les règles suivantes lors de l'interprétation des arguments donnés sur la ligne de commande du système d'exploitation:

  • Les arguments sont délimités par un espace blanc, qui est soit un espace, soit une tabulation.

  • Le caractère caret (^) n'est pas reconnu comme un caractère d'échappement ou un délimiteur. Le caractère est entièrement géré par l'analyseur de ligne de commande dans le système d'exploitation avant d'être passé au tableau argv dans le programme.

  • Une chaîne entourée de guillemets doubles (" chaîne ") est interprétée comme un argument unique, quel que soit l'espace blanc qu'il contient. Une chaîne entre guillemets peut être incorporée dans un argument.

  • Un guillemet double précédé d'une barre oblique inverse (\ ") est interprété comme un caractère de guillemet double littéral (").

  • Les barres obliques inverses sont interprétées littéralement, à moins qu'elles ne précèdent immédiatement un guillemet double.

  • Si un nombre pair de barres obliques inverses est suivi d'un guillemet double, une barre oblique inverse est placée dans le tableau argv pour chaque paire de barres obliques inverses, et le guillemet double est interprété comme un délimiteur de chaîne.

  • Si un nombre impair de barres obliques inverses est suivi d'un guillemet double, une barre oblique inverse est placée dans le tableau argv pour chaque paire de barres obliques inverses, et le guillemet double est "échappé" par la barre oblique inverse restante, provoquant un littéral guillemets doubles (") à placer dans argv .

Ceci est également discuté dans les fonctions _exec, _wexec :

Les espaces incorporés dans des chaînes peuvent provoquer un comportement inattendu; par exemple, en passant _exec la chaîne "hi there" , le nouveau processus recevra deux arguments, "hi" et "there" . Si l'intention était que le nouveau processus ouvre un fichier nommé «salut», le processus échouerait. Vous pouvez éviter cela en citant la chaîne: "\"hi there\"" .

Comment Python appelle les exécutables natifs

Le subprocess.Popen Python peut gérer correctement l'échappement en convertissant args en chaîne de la manière décrite dans Conversion d'une séquence d'arguments en chaîne sous Windows . L'implémentation est subprocess.list2cmdline .

J'espère que cela peut éclairer la façon dont PowerShell peut gérer cela de manière plus élégante, au lieu d'utiliser --% ou un double échappement (en suivant la syntaxe PowerShell et CMD). Les solutions de contournement actuelles bogue vraiment les clients Azure CLI (qui est basé sur python.exe).

Nous n'avons toujours pas de méthode claire et concise pour comprendre comment gérer ce problème. Quelqu'un de l'équipe Powershell peut-il faire la lumière si cela est simplifié avec la v7?

Eh bien, la bonne nouvelle est que l'un des objectifs de PS 7.1 est de faciliter l'invocation de commandes natives. À partir d'un article de blog sur les investissements 7.1 :

La plupart des commandes natives fonctionnent très bien depuis PowerShell, cependant, dans certains cas, l'analyse des arguments n'est pas idéale (comme la gestion des guillemets correctement). L'intention est de permettre aux utilisateurs de couper des exemples de lignes de commande pour tout outil natif populaire, de le coller dans PowerShell, et cela fonctionne simplement sans avoir besoin d'échapper spécifiquement à PowerShell.

Alors peut-être (espérons-le) que cela sera résolu dans la version 7.1

Merci, @rkeithhill , mais pas:

L'intention est de permettre aux utilisateurs de couper des exemples de lignes de commande pour tout outil natif populaire, de le coller dans PowerShell, et cela fonctionne simplement sans avoir besoin d'échapper spécifiquement à PowerShell.

Cela ressemble-t-il à une autre interprétation du concept conceptuellement problématique --% (symbole d'arrêt de l'analyse)?

@ SteveL-MSFT, pouvez-vous nous en dire plus sur cette fonctionnalité à venir?


Pour récapituler, le correctif proposé ici est que tout ce sur quoi vous devez vous concentrer est de satisfaire les exigences de syntaxe de _PowerShell_ et que PowerShell prend en charge toutes les échappées _en coulisses_ (sous Windows; sous Unix, ce n'est même plus nécessaire, maintenant que .NET le permet nous pour passer un tableau de jetons verbatim au programme cible) - mais il ne fait aucun doute que le correctif devrait être opt-in, si la compatibilité descendante doit être maintenue.

Avec ce correctif, il sera toujours nécessaire de manipuler des lignes de commande pour d'autres shells, mais ce sera plus simple - et, par rapport à --% , vous conservez toute la puissance de la syntaxe de PowerShell et des extensions de variables (expressions avec (...) , redirections, ...):

  • Vous devez remplacer \" par `" , mais _uniquement dans "..." _.

  • Vous devez être conscient que _aucun (autre) shell_ n'est impliqué, de sorte que les références de variable d'environnement de style Bash telles que $USER ne fonctionneront _pas_ (elles seront interprétées comme des variables _PowerShell_), sauf si vous les remplacez par syntaxe PowerShell équivalente, $env:USER .

    • En aparté: --% essaie de compenser cela en - notamment _invariably_ - en développant cmd.exe -style des références de variables d'environnement telles que %USERNAME% , mais notez que non seulement ce n'est pas le cas. Le support des références de style Bash ( $USER ) est passé _verbatim_ au programme cible, mais étend également de manière inattendue les références de style cmd.exe sur les plates-formes de type Unix, et ne reconnaît pas '...' -quoting.
    • Voir ci-dessous pour une alternative qui _does_ implique le shell natif de la plate-forme respective.
  • Vous devez être conscient que PowerShell a des métacaractères supplémentaires qui nécessitent des guillemets / échappements pour une utilisation textuelle; ceux-ci sont (notez que @ n'est problématique que comme _first_ char.):

    • pour les shells de type POSIX (par exemple, Bash): @ { } ` (et $ , si vous voulez empêcher l'expansion initiale par PowerShell)
    • pour cmd.exe : ( ) @ { } # `
    • Individuellement ` échapper à ces caractères. est suffisant (par exemple, printf %s `@list.txt ).

Un exemple un peu artificiel:

Prenez la ligne de commande Bash suivante:

# Bash
$ printf '"%s"\n' "3\" of snow"
"3" of snow"        # output

Avec le correctif proposé en place, il suffit de remplacer les instances \" à l'intérieur de l'argument "..." -enclosed par `" :

# PowerShell - WISHFUL THINKING
PS> printf '"%s"\n' "3`" of snow"
"3" of snow"        # output

Autrement dit, vous n'avez pas besoin de vous soucier de l'incorporation de " intérieur de '...' , et à l'intérieur de "..." vous suffit de les échapper pour rendre _PowerShell_ heureux (ce que vous pouvez également faire avec "" ici).

La fonction iep ci-dessus implémente cette correction (comme un palliatif), de sorte que iep printf '"%s"\n' "3`" of snow" fonctionne comme prévu.


Comparez cela avec le comportement actuel, cassé, où vous devez sauter à travers les cercles suivants pour que la commande fonctionne comme dans Bash (inexplicablement besoin d'un cycle _additional_ d'échappement avec \ ):

# PowerShell - messy workaround to compensate for the current, broken behavior.
PS> printf '\"%s\"\n' "3\`" of snow"
"3" of snow"        # output

Avec le correctif en place, ceux qui veulent utiliser une ligne de commande donnée _as-is_ via le _default shell_ de la plate-forme, pourront utiliser une _here-string_ verbatim pour passer à sh -c (ou bash -c ) / cmd /c ; par exemple:

# PowerShell - WISHFUL THINKING
PS> sh -c @'
printf '"%s"\n' "3\" of snow"
'@
"3" of snow"  # output

Notez que l'utilisation de --% ne fonctionne _pas_ ici ( printf --% '"%s"\n' "3\" of snow" ), et l'avantage supplémentaire de l'approche basée sur les chaînes ici est que les différentes limitations de --% ne s'appliquent pas , notamment l'impossibilité d'utiliser une redirection de sortie ( > ).

Si vous passez à une chaîne de caractères _double_ entre guillemets ( @"<newline>....<newline>"@ ), vous pouvez même incorporer des variables et des expressions _PowerShell_, contrairement à --% ; cependant, vous devez ensuite vous assurer que les valeurs développées ne cassent pas la syntaxe du shell cible.

Nous pouvons penser à une applet de commande dédiée avec un alias succinct (par exemple, Invoke-NativeShell / ins ) pour de tels appels (de sorte que sh -c / cmd /c n'a pas besoin être spécifié), mais pour passer des lignes de commande complexes telles quelles, je ne pense pas à un moyen d'utiliser des chaînes ici:

# PowerShell - WISHFUL THINKING
# Passes the string to `sh -c` / `cmd /c` for execution, as appropriate.
# Short alias: ins
PS> Invoke-NativeShell @'
printf '"%s"\n' "3\" of snow"
'@
"3" of snow"  # output

Bien sûr, si vous comptez sur les fonctionnalités du shell natif de la plate-forme, de tels appels seront par définition spécifiques à la plate-forme [-family] - ils ne fonctionneront pas sur les plates-formes Windows et Unix.

C'est la raison pour laquelle il est préférable de s'appuyer sur PowerShell _alone_, avec sa _ propre syntaxe_ sur le long terme : il offre une expérience multiplateforme prévisible pour appeler des programmes externes - même si cela signifie que vous ne pouvez pas utiliser de lignes de commande conçues pour d'autres shells comme- est; à mesure que PowerShell gagne en popularité, je m'attends à ce que la douleur de découvrir et de connaître les modifications requises diminue, et j'attends de plus en plus de documentation pour montrer les versions PowerShell des lignes de commande (aussi).

  • Vous devez remplacer \" par `" , mais _uniquement dans "..." _.

Cela ne fonctionne pas partout.

# working everywhere but polluted with \
❯ node -e 'console.log(\"hey\")'
hey

# working in Node:
❯ node -e 'console.log(`"hey`")'
hey

# not working in Julia:
❯ julia -e 'print(`"hey`")'
`hey`

# not working anywhere:
❯ node -e "console.log(`"hey`")"
❯ node -e "console.log("hey")"
❯ node -e "console.log(""hey"")"
❯ node -e 'console.log(""hey"")'

Syntaxe Bash:

❯ node -e 'console.log("hey")'
hey

Suggestion Powershell:

Si l'équipe PowerShell recherche simplement un symbole, cela ne rompt pas la syntaxe précédente, pourquoi ne pas utiliser quelque chose comme un backticks `pour un comportement de type Bash, qui échappe automatiquement les littéraux et permet l'interpolation de chaînes. Ceci est également similaire à la syntaxe de JavaScript.

❯ node -e `console.log("hey")`
hey

❯ $a=hey 
❯ node -e `console.log($hey)`
hey

pourquoi ne pas utiliser quelque chose comme un backticks `pour un comportement de type Bash

Les backticks sont déjà utilisés pour échapper des trucs, le même but que les backslahes dans le shell POSIX. Le commentaire dont vous parlez le souligne déjà. Nous avons utilisé tous les trucs ressemblant à des citations ASCII. L'ajout d'un préfixe $ aux chaînes de caractères normales peut fonctionner, mais je ne pense pas que cela ait suffisamment de sens.


La façon dont Windows analyse les arguments de ligne de commande peut être trouvée à l'adresse ...

Le problème est que Windows MSVCR ne fait pas que cela: il gère les cas d'angle de manière non documentée . Le truc "" est si solidement défini qu'ils l'ont même mis dans CoreFX lorsqu'ils ont porté .NET sur Unix. Mais de toute façon, c'est toujours assez bon pour s'échapper, du moins jusqu'à ce que quelqu'un demande un globbing.

Il y a aussi le problème classique de tout le monde le faisant différemment, mais nous n'avons pas à nous en soucier car nous avons toujours .NET pour la ligne de commande brute.

pourquoi ne pas utiliser quelque chose comme un backticks `pour un comportement de type Bash

Les backticks sont déjà utilisés pour échapper des trucs, le même but que les backslahes dans le shell POSIX. Nous avons utilisé tous les trucs ressemblant à des citations ASCII.

Il existe une possibilité d'utiliser une combinaison de symboles si l'analyseur n'est pas capable de détecter qu'ici `introduit une chaîne. Quelque chose comme '' pourrait même fonctionner.

@aminya voici une solution si vous n'utilisez pas d'anciennes fenêtres Powershell 5.1 et 6+:
https://github.com/PowerShell/PowerShell/issues/1995#issuecomment -562334606

Comme je dois gérer PowerShell 5,1 sur Windows et 6+ sur linux / mac, j'ai ma propre implémentation qui fonctionne sans problème depuis des années qui permet de travailler avec des outils comme kubectl, helm, terraform et autres passant des JSON complexes objets dans les paramètres:
https://github.com/choovick/ps-invoke-externalcommand

GitHub
Contribuez au développement de choovick / ps-invoke-externalcommand en créant un compte sur GitHub.

@choovick une implémentation un peu plus courte a été donnée ci-dessus dans ce fil , je suis toujours à trouver un cas où cela échouerait pour moi. Cela fonctionne dans PS à partir de la v3.

La fonction Run-Native @AndrewSav @TSlivede est très intelligente et concise, et fonctionne également de manière fiable dans _Windows PowerShell_; quelques points à noter: par nécessité, le processus enfant voit le aux. commandlineargumentstring variable d'environnement (probablement rarement, voire jamais, un problème en pratique), les invocations sans argument ne sont actuellement pas gérées correctement (facilement corrigées et même pas un problème, si vous vous assurez de n'utiliser que la fonction _avec_ arguments, ce à quoi il sert), _tous_ les arguments sont entre guillemets (par exemple, quelque chose comme 42 est passé comme "42" ), ce qui sur Windows peut (malheureusement) avoir des effets secondaires pour programmes qui interprètent différemment les arguments entre guillemets doubles (ou partiellement double-guillemets, comme dans le cas msiexec ).

@aminya , la seule raison pour laquelle node -e 'console.log(`"hey`")' (en quelque sorte) fonctionne est à cause du comportement actuel, cassé (voir ci-dessous). Je suppose que ce que vous vouliez passer était _verbatim_ console.log("hey") , qui, si PowerShell sur Windows échappait correctement aux choses comme proposé ici, vous passeriez tel quel node -e 'console.log("hey")' . Cela devrait _automatiquement_ être traduit en node -e "console.log(\"hey\")" (guillemets doubles, \ échappé verbatim " ) _en coulisses_.

Compte tenu de la longueur de ce fil, laissez-moi essayer de récapituler:
Vous ne devriez jamais avoir à vous soucier que des exigences de syntaxe de _PowerShell_, et c'est le travail de PowerShell _en tant que shell_ de vous assurer que les _valeurs d'argument de verbatim_ qui résultent de l'analyse syntaxique de PowerShell sont transmises au programme externe _as-is_.

  • Sur les plates-formes de type Unix, maintenant que nous avons la prise en charge de .NET Core, cela est trivial, car les valeurs textuelles peuvent simplement être passées telles quelles en tant qu'éléments d'un _array_ d'arguments, ce qui permet aux programmes d'y recevoir nativement des arguments. .
  • Sous Windows, les programmes externes reçoivent des arguments sous la forme d'une _ chaîne de ligne de commande unique_ (une décision de conception historique regrettable) et doivent effectuer leur propre analyse de ligne de commande. Le passage de plusieurs arguments dans le cadre d'une seule chaîne nécessite des règles de citation et d'analyse afin de délimiter correctement les arguments; alors qu'il s'agit en fin de compte d'un free-for-all (les programmes sont libres d'analyser comme ils le souhaitent), la convention de syntaxe la plus largement utilisée (comme indiqué précédemment et également proposée dans la RFC malheureusement ce que les compilateurs C / C ++ de Microsoft implémentent , il est donc logique d'y aller.

    • _Update_: Même sous Windows, nous pouvons profiter de la propriété collection-of-verbatim-tokens ArgumentList de System.Diagnostics.ProcessStartInfo dans .NET Core: sous Windows, elle est automatiquement traduite en une commande correctement citée et échappée -line chaîne au démarrage du processus; cependant, pour les _batch files_, nous pouvons encore avoir besoin d'un traitement spécial - voir https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -552231174

La mise en œuvre de ce qui précède est sans aucun doute un changement de rupture massif, elle nécessite donc vraisemblablement un opt-in.
Je pense que continuer avec cela est un must, si nous voulons nous débarrasser de tous les maux de tête actuels de citations, qui continuent à venir et entravent l'adoption de PowerShell, en particulier dans le monde Unix.


Comme pour node -e 'console.log(`"hey`")' : à l'intérieur de '...' , n'utilisez pas ` - sauf si vous voulez que ce caractère soit transmis tel quel. Parce que PowerShell _ne_ n'échappe actuellement pas aux caractères verbatim " . dans votre argument comme \" dans les coulisses, ce que node voit sur la ligne de commande est console.log(`"hey`") , qui est analysé comme deux chaînes littérales directement adjacentes: sans guillemets console.log(` et "hey`" . Après avoir supprimé les " , qui ont une fonction _syntactique_ car ils ne sont pas \ échappés, le code JavaScript exécuté est finalement console.log(`hey`) , et cela ne fonctionne que parce qu'un `....` jeton inclus est une forme de littéral de chaîne en JavaScript, à savoir un _template literal_.

@AndrewSav J'ai testé avec mon objet de test fou et cela a fonctionné pour moi! Solution très élégante, testée sous Windows 5.1 et PS 7 sous linux. Je suis d'accord pour que tout soit double, je ne traite pas de msiexec ou sqlcmd également connu pour traiter " explicitement.

Mon implémentation personnelle a également une logique d'échappement simple similaire à celle que vous mentionnez: https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L278

mais j'ai écrit un tas de code pour afficher et capturer les threads STDOUT et STDERR en temps réel dans ce module ... Cela peut probablement être grandement simplifié, mais je n'en avais pas besoin ...

@ mklement0 ce fil ne se terminera jamais (: nous devons soit fournir le module PowerShell publié dans la galerie ps qui conviendra à la plupart des cas d'utilisation et sera assez simple à utiliser, soit attendre les prochaines améliorations du shell.

GitHub
Contribuez au développement de choovick / ps-invoke-externalcommand en créant un compte sur GitHub.

@ mklement0 ce fil ne se terminera jamais (: nous devons soit fournir le module PowerShell publié dans la galerie ps qui conviendra à la plupart des cas d'utilisation et sera assez simple à utiliser, soit attendre les prochaines améliorations du shell.

Si l'équipe PowerShell décide de ne pas corriger cela dans le programme lui-même, je dirais alors qu'un shell qui ne peut pas exécuter des programmes externes de manière native et correctement ne sera pas le shell de mon choix! Ce sont les éléments de base qui manquent à PowerShell.

@aminya oui, j'ai trouvé que ce problème était très ennuyeux lorsque je devais y passer. Mais des fonctionnalités comme ci-dessous en valaient la peine.

  • Cadre de paramètres flexible, facile à construire des CMDlets fiables.
  • Modules et référentiels de modules internes pour partager une logique commune dans toute l'organisation, évitant ainsi beaucoup de duplication de code et la centralisation des fonctionnalités de base qui peuvent être refactorisées en un seul endroit.
  • Plateforme croisée. J'ai des gens qui exécutent mes outils sous Windows dans PS 5.1 et sous Linux / mac dans PS 6/7

J'espère vraiment que l'équipe PS améliorera cela à l'avenir pour le rendre moins compliqué.

La mise en œuvre de ce qui précède est sans aucun doute un changement de rupture massif, elle nécessite donc vraisemblablement un opt-in.

Voulez-vous dire implémenter https://github.com/PowerShell/PowerShell-RFC/pull/90?

@aminya , je suis d'accord que l'appel de programmes externes avec des arguments est un mandat central d'un shell et doit fonctionner correctement.

Un module, comme suggéré par @choovick , a toujours du sens pour _Windows PowerShell_, qui est en mode de maintenance de correctifs critiques de sécurité uniquement.
En complément, si / quand la rubrique d'aide conceptuelle proposée sur l'appel de programmes externes est écrite - voir https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152 - une fonction d'aide qui corrige les problèmes dans les versions antérieures / Windows PowerShell, comme celui de @TSlivede ci-dessus, pourrait y être posté directement.

@iSazonov Oui, mettre en œuvre https://github.com/PowerShell/PowerShell-RFC/pull/90 est ce que je voulais dire.

Quant au fil de discussion sans fin et aux problèmes de rupture:

La dernière réponse officielle à la RFC liée était ce commentaire de @joeyaiello du 8 juillet 2019 (italiques ajoutés):

mais nous pensons que cette [RFC] a beaucoup de sens quel que soit le comportement existant et sans tenir compte de la rupture de celui- ci. Maintenant que nous avons des fonctionnalités expérimentales, nous pensons qu'il est parfaitement raisonnable d'aller l'implémenter aujourd'hui derrière un indicateur de fonctionnalité expérimentale, et nous pouvons déterminer plus tard s'il s'agit d'un comportement opt-in vs opt-out , s'il y a une transition chemin, et si une variable de préférence est le bon mécanisme pour l'activer et la désactiver.

_Personnellement_, cela ne me dérangerait pas de corriger le comportement _par défaut_, même si c'est un changement radical; la RFC propose en effet cela et suggère un opt-in si vous voulez le comportement _old_ (cassé).

Je soupçonne que ceux qui ont du code hérité à maintenir s'objecteront, cependant, car toutes les solutions de contournement existantes cesseront de fonctionner - voir ci -

Si le nouveau comportement fixe est opt-in, vous avez toujours la gêne de devoir faire quelque chose juste pour obtenir le bon comportement, mais au moins le code existant ne cassera pas.

Mais les mécanismes d'opt-in existants sont eux-mêmes problématiques:
Maintenant que la RFC des fonctionnalités optionnelles de @KirkMunro a été rejetée, cela laisse à peu près une _ variable de préférence_, et le défi est la portée dynamique de PowerShell: un code tiers appelé à partir d'une étendue qui a opté et qui n'a pas été conçue pour utiliser la nouvelle l'implémentation pourrait alors être interrompue (à moins que la variable de préférence ne soit temporairement réinitialisée).

La portée _Lexical_ de l'opt-in est requise ici, ce que nous n'avons pas actuellement. La RFC pour la portée _lexique_ du mode strict propose l'implémentation d'une fonctionnalité à portée lexicale (ou un but différent), via une instruction using (qui, notamment, est généralement à portée _dynamique_). En suivant ce modèle, une instruction using ProperExternalArgumentQuoting portée lexicale (le nom est un WIP :) - si cela est techniquement faisable - vaut la peine d'être considérée.

Nous avons besoin du comité PowerShell pour peser (à nouveau) et pour fournir des conseils clairs sur la voie à suivre, avec des commentaires en temps opportun sur les questions à mesure qu'elles se posent. @ SteveL-MSFT?


Notez qu'une solution de type --% évoquée par l'article de blog 7.1 (voir ci - ci - ce commentaire ), serait une fonctionnalité _separate_ - corrigeant le natif de PowerShell (non-émulation) le comportement est toujours un must.

Personnellement, cela ne me dérangerait pas de corriger le comportement par défaut, même s'il s'agit d'un changement radical; la RFC propose en effet cela et suggère un opt-in si vous voulez l'ancien comportement (cassé).

Si le nouveau comportement fixe est opt-in, vous avez toujours la gêne de devoir faire quelque chose juste pour obtenir le bon comportement, mais au moins le code existant ne cassera pas.

D'accord, en fait, je dirais qu'il est moins logique d'avoir un défaut cassé , puis un défaut correctement implémenté. Étant donné que l'implémentation actuelle est en fait un bogue , le nouveau comportement doit être opt-in, pas opt-out, car cela n'a vraiment pas de sens de continuer à encourager les appels de shell externes cassés qui sont susceptibles de se rompre de manière inattendue. façons. Dans tous les cas, PowerShell 7 devrait s'efforcer de s'améliorer par rapport à l'ancien Windows PowerShell.

@ SteveL-MSFT et moi avons convenu que nous devrions fermer celui-ci en faveur du # 13068. Tout ce que nous touchons ici est tout simplement un changement radical, et nous devrions résoudre le problème avec un nouvel opérateur qui sert de mode opt-in.

Je ne vois absolument pas comment # 13068 résoudrait ceci: si cet opérateur est introduit comme prévu, nous n'avons toujours aucun moyen d'appeler correctement un exécutable natif avec un tableau donné d'arguments ou avec des arguments explicites dont le contenu provient de variables.

L'exemple, que @JustinGrote a donné dans ce thread ne fonctionne actuellement pas de manière fiable (si des guillemets incorporés sont possibles dans la charge utile de l'argument) et l'ajout de cet opérateur ne donnera aucune alternative qui améliore quoi que ce soit.

@joeyaiello Pouvez-vous au moins laisser ce problème ouvert jusqu'à ce que cet opérateur existe réellement et que quelqu'un puisse montrer comment cet opérateur améliorerait quoi que ce soit, qui a été mentionné dans ce fil?

Oh et qu'en est-il aussi de Linux? Ce problème est stupide et inattendu sur Windows, mais sous Linux, cela a encore moins de sens, d'autant plus qu'il n'y a pas d'histoire longue des scripts Linux PowerShell, qui va casser.

Créer un opérateur spécial pour cela n'a aucun sens pour un shell de ligne de commande, puisque son travail principal est de lancer des programmes et de leur transmettre des arguments. Introduire un nouvel opérateur qui fait system() pour ce travail est comme Matlab introduisant un moyen d'appeler calc.exe car il a un bogue dans son arithmétique. Ce qui devrait plutôt être fait est que:

  • L'équipe pwsh se prépare à une nouvelle version majeure qui corrige les éléments de la ligne de commande, déplaçant le comportement actuel derrière une applet de commande intégrée.
  • En tant que solution provisoire, la prochaine version de pwsh obtient une applet de commande intégrée qui utilise le nouveau comportement correct pour le passage de la ligne de commande.

La même chose s'applique à Start-Process . (En fait, c'est un très bon candidat pour la "nouvelle" cmdlet avec des options comme -QuotingBehavior Legacy ...) Voir # 13089.

Pourquoi Powershell se comporte-t-il différemment dans ces deux situations? Plus précisément, il encapsule de manière incohérente les arguments contenant des espaces entre guillemets.

J'obtiens des résultats cohérents dans la v.7. Semble fixe.

PING 'A \"B'

La requête Ping n'a pas pu trouver l'hôte A "B.

PING 'A\" B'

La requête Ping n'a pas pu trouver l'hôte A "B.

Ce n'est pas corrigé, car les noms d'hôtes verbatim que ping devraient voir sont A \"B et A\" B - _avec_ les caractères \ .

PowerShell, en tant que shell, doit analyser les arguments en fonction de _ ses_ règles - uniquement - puis s'assurer de manière transparente que le processus cible voit les mêmes valeurs textuelles que celles résultant de l'analyse propre de PowerShell.

Ces _autres_ shells - et ces pauvres programmes fonctionnant sous Windows qui doivent agir comme leur propre shell, en quelque sorte, en ayant à analyser une _ligne de commande_ juste pour extraire les arguments individuels passés - utilisent \ comme échappement Le caractère ne doit pas entrer dans l'image ici - tenir compte de cela (nécessaire sous Windows uniquement, sous Unix, vous passez simplement les arguments verbatim directement sous forme de tableau) est le travail de PowerShell en tant que shell, _en coulisses_.

En passant, tout comme PowerShell lui-même ne nécessite pas l'échappement de " _inside '...' _ (chaînes entre guillemets simples), les shells compatibles POSIX tels que bash : exécuté à partir de bash , par exemple, /bin/echo 'A \"B' (raisonnablement) imprime A \"B (les \ sont traités comme des littéraux dans les chaînes entre guillemets simples) - qui exécutent le la même commande de PowerShell donne (de manière inattendue)
A "B - le \ est manquant - est une manifestation des problèmes discutés ici.

Je devrais clarifier:

  • Du point de vue de vouloir finalement passer A "B _verbatim_, vous devriez pouvoir utiliser 'A "B' de PowerShell.

  • La ligne de commande que PowerShell construit actuellement dans les coulisses contient "A "B" - que le processus cible voit comme A B - c'est-à-dire l'enceinte aveugle dans "..." , sans échapper au _embedded_ " entraîné la perte effective des " incorporés. Ce que PowerShell devrait utiliser dans la ligne de commande des coulisses dans ce cas est "A \"B" - c'est-à-dire que le " intégré a besoin de \ -escaping.

  • De même, le même boîtier aveugle fait que 'A \"B' est représenté par "A \"B" sur la ligne de commande des coulisses, ce qui se trouve juste à transformer le _embedded_ \" en un _escaped_ " caractère, que le processus cible voit donc comme A "B ; autrement dit, l'absence d'échappée _automatique_ a entraîné la perte effective du \ intégré. Ce que PowerShell _ devrait_ utiliser dans la ligne de commande des coulisses dans ce cas est "A \\\"B" - c'est-à-dire que les \ et les " doivent être échappés.

Ce n'est pas corrigé, car les noms d'hôtes verbatim que ping devraient voir sont A \"B et A\" B - _avec_ les caractères \ .

«Il» renvoie ici à la plainte citée, que je ne peux heureusement pas reproduire.

@ yecril71pl , je vois: mon hypothèse (incorrecte) était que "les arguments contenant des espaces entre guillemets doubles" se réfèrent à l'absence d'échappements automatiques des " et \ caractères_, comme expliqué dans mon commentaire précédent - et c'est le nœud de cette question.

Il y a eu des correctifs mineurs dans PowerShell Core que Windows PowerShell n'a pas; Je ne peux penser qu'à un pour le moment:

  • Windows PowerShell utilise des guillemets doubles aveugles dans le cas d'un \ : 'A B\' se transforme en (cassé) "A B\" - PS Core gère cela correctement ( "A B\\" ) .

Étant donné que ce dépôt est uniquement destiné à PS Core, il suffit de se concentrer sur ce qui est encore cassé dans PS Core (il peut être utile de mentionner les différences _en aparté_, mais il est préférable de rendre cet aspect explicite).


Et même le scénario que vous aviez à l'esprit _est_ toujours cassé dans PS Core - mais seulement _si vous omettez le \ de l'argument_:

Passer 'A" B' entraîne toujours _non_-double-quoted A" B dans les coulisses (alors que 'A\" B' aboutit à "A\" B" - qui est également cassé, comme indiqué - uniquement différemment).

Étant donné que ce dépôt est uniquement destiné à PS Core, il suffit de se concentrer sur ce qui est encore cassé dans PS Core (il peut être utile de mentionner les différences _en aparté_, mais il est préférable de rendre cet aspect explicite).

Meta apart: je trouve utile de savoir que le mauvais comportement mentionné dans le commentaire d'un autre utilisateur ne s'applique pas. Bien sûr, nous aurions pu ignorer le commentaire simplement parce que l'utilisateur ne s'est pas donné la peine de vérifier la version actuelle. Bien. Les journalistes indisciplinés étant indisciplinés, il vaut toujours mieux être sûr. A MON HUMBLE AVIS.

Aucun argument ici, mais fournir un cadrage et un contexte appropriés à de tels _asides_ est important, en particulier dans un fil de discussion looooong où le commentaire original - nécessaire pour le contexte - a été publié il y a longtemps et est, en fait, maintenant _hidden_ ​​par défaut pour réellement _lien_ au commentaire d'origine cité).

Je me demande si ces discussions font une différence. De toute évidence, les décisions du comité sont indépendantes de ce que veut la communauté. Regardez les balises: Resolution- won't fix. Mais comme PowerShell est open source (MIT), la communauté peut résoudre ce problème dans un fork séparé et appeler cette PowerShellCommunity ( pwshc ) pour faire court.

Cela supprime le besoin de compatibilité descendante. Plus tard dans PowerShell 8, le comité pourrait intégrer le fork.

À propos de la rétrocompatibilité: PowerShell n'est préinstallé sur aucun système d'exploitation, et pour moi, la difficulté d'installer PowerShellCommunity est la même que PowerShell . Je préfère installer la version communautaire et l'utiliser tout de suite au lieu d'attendre une future version 8 (ou de rendre le code plus complexe avec un nouvel opérateur).

Puisque le Comité a décidé de garder les choses brisées, il vaut mieux savoir à quel point elles sont brisées. Je pense que la communauté peut vivre avec Invoke-Native qui fait ce qu'il faut. Ce n'est pas le travail de la communauté de sauver la face de Microsoft contre son gré.

il vaut mieux savoir à quel point ils sont brisés

Je suis entièrement d'accord - même si le problème ne peut être résolu sur le moment, savoir comment faire les choses correctement _ en principe, si et quand le moment vient_ est important - même si ce moment ne vient jamais dans le contexte d'une langue donnée.

la communauté peut vivre avec Invoke-Native qui fait ce qu'il faut

Pour être clair:

  • Quelque chose comme Invoke-NativeShell aborde un _ cas d'utilisation différent_ - qui fait l'objet de # 13068 - et pour ce cas d'utilisation, une telle applet de commande n'est _pas_ un palliatif: c'est la bonne solution - voir https://github.com/ PowerShell / PowerShell / issues / 13068 # issuecomment -656781439

  • _Ce_ problème concerne la résolution de _PowerShell lui-même_ (il ne s'agit pas de la fonctionnalité _native_ de la plate-forme_), et la solution _stopgap_ à cela est de fournir une cérémonie de bas niveau _opt-in_ - d'où la proposition d'expédier la fonction iep comme -in, en attendant un correctif _proper_ dans une version future qui est autorisée à interrompre considérablement la compatibilité descendante.

Ce n'est pas le travail de la communauté de sauver la face de Microsoft

Je ne pense pas que le souci de

Cela dit, je ne suis pas sûr que fragmenter l'écosystème PowerShell avec une fourchette soit la bonne voie à suivre.

Je n'ai pas le temps de répondre à tout cela en ce moment, mais je pense que c'est raisonnable, alors je rouvre:

Pouvez-vous au moins laisser ce problème ouvert jusqu'à ce que cet opérateur existe réellement et que quelqu'un puisse montrer comment cet opérateur améliorerait quoi que ce soit, qui a été mentionné dans ce fil?

Comme je l'ai dit dans un problème connexe, l'opérateur d'appel natif ajoute de la complexité à UX et il est dans la mauvaise direction.
Dans le même temps, toute la discussion ici porte sur la simplification de l' interaction avec les applications natives.

La seule chose qui nous arrête, c'est qu'il s'agit d'un changement radical. Nous avons dit à maintes reprises que c'était trop de destruction, mais pesons-le.

Regardons une session interactive. Un utilisateur installera une nouvelle version de PowerShell et découvrira un nouveau comportement lors de l'appel d'applications natives. Que dira-t-il? Je vais spéculer qu'il dira - "Merci - enfin je peux juste taper et ça marche!".

Regardons le scénario d'exécution / d'hébergement de script. Toute nouvelle version d'une application (même avec une modification mineure!) Peut interrompre un processus métier. Nous nous attendons toujours à cela et vérifions nos processus après toute mise à jour. Si nous trouvons un problème, nous avons plusieurs moyens:

  • annuler la mise à jour
  • préparer un correctif rapide pour le script
  • désactiver une fonctionnalité qui rompt notre script jusqu'à ce que ces choses soient corrigées ou qu'elles meurent elles-mêmes avec le temps.

_Depuis que les mises à jour de version cassent toujours quelque chose, la dernière option est la meilleure que nous pourrions avoir et accepter._

(Je tiens à souligner qu'à l'heure actuelle, PowerShell Core n'est pas un composant de Windows et ne peut donc pas gâcher directement les applications d'hébergement, seuls les scripts sont directement affectés.)

Je veux vous rappeler ce que Jason a dit ci-dessus - c'est une correction de bogue.
Laissez-nous le réparer et tout simplifier pour tout le monde. Laisser les utilisateurs travailler powershell-y .

Comme je l'ai dit dans un problème connexe, l'opérateur d'appel natif ajoute de la complicité dans l'UX et c'est une mauvaise direction.

complexité ❓

Regardons une session interactive. Un utilisateur installera une nouvelle version de PowerShell et découvrira un nouveau comportement lors de l'appel d'applications natives. Que dira-t-il? Je vais spéculer qu'il dira - "Merci - enfin je peux juste taper et ça marche!".

J'ai un scénario différent: les nouveaux utilisateurs essaient PowerShell, réalisent qu'il échoue mystérieusement, le déplacent vers la corbeille et ne reviennent jamais. C'est ce que je ferais.

préparer un correctif rapide pour le script

C'est quelque chose que nous devrions faire pour couvrir les cas évidents dans les scripts utilisateur.

se rendre compte que cela échoue mystérieusement

Toute la discussion ici est simplement de se débarrasser de ces «échecs mystérieux». Et il est préférable de le faire en simplifiant mais sans ajouter de nouvelles choses mystérieuses.

Toute la discussion ici est simplement de se débarrasser de cela. Et il est préférable de le faire en simplifiant mais sans ajouter de nouvelles choses mystérieuses.

Nous ne voulons pas nous débarrasser de la possibilité d'appeler directement des programmes exécutables.

L'ancienne invocation n'est pas non plus directe à cmdline avec son estimation de savoir si quelque chose est déjà cité. De plus, ladite capacité serait toujours conservée avec -%.

De plus, ladite capacité serait toujours conservée avec -%.

--% nécessite une ligne de commande prédéfinie, son utilité est donc limitée.

@ yecril71pl

Nous ne voulons pas nous débarrasser de la possibilité d'appeler directement des programmes exécutables.

Je pense que @iSazonov signifie se débarrasser _du comportement du buggy_, c'est-à-dire corriger le bogue correctement (sans opt-in via une syntaxe supplémentaire), même si cela signifie briser les solutions de contournement existantes. Correct, @iSazonov?

@ Artoria2e5 :

L'ancienne invocation n'est pas non plus directe à cmdline avec son estimation de savoir si quelque chose est déjà cité

[_Update_: J'ai mal lu la ligne citée, mais j'espère que l'information est toujours intéressante.]

  • Vous n'avez pas à _ deviner_, et la règle est simple:

    • _Seulement si_ le nom ou le chemin de votre commande est _quoté_ - '/foo bar/someutil '- et / ou contient _variable références (ou expressions) - $HOME/someutil - & est _required_.
  • Bien que vous puissiez considérer ce besoin comme malheureux, il est au cœur même du langage PowerShell et nécessite d'être capable de distinguer syntaxiquement les deux modes d'analyse fondamentaux, le mode argument et le mode d'expression.

    • Notez que le problème n'est pas spécifique à l'appel de _ programmes externes_; Les commandes PowerShell natives nécessitent également & pour l'invocation si elles sont spécifiées via une référence de chaîne / variable entre guillemets.
  • Si vous ne voulez pas mémoriser cette règle simple, la règle la plus simple est: _toujours_ utilisez & , et tout ira bien - c'est un peu moins pratique que _pas_ besoin de quoi que ce soit, mais pas exactement une difficulté (et plus courte que --% , ce qui est absolument la mauvaise solution - voir ci-dessous)

ladite capacité serait toujours conservée avec -%.

[_Update_: J'ai mal lu la ligne citée, mais j'espère que l'information est toujours intéressante.]

Non, malgré la confusion malheureuse de # 13068 avec _ce_ problème, --% - la possibilité d'appeler le _native shell_, en utilisant _its_, invariablement la syntaxe _platform-specific_ - n'est en aucun cas une solution cela en tant que tel ne fait qu'ajouter à la confusion .

--% est un cas d'utilisation très différent et, tel qu'il est actuellement proposé, présente de graves limitations; S'il y a quelque chose qui ne vaut pas la peine d'introduire un _opérateur_ pour (ou de changer le comportement d'un opérateur existant), c'est la possibilité de passer une ligne de commande textuelle au shell natif (ce que, bien sûr, vous pouvez déjà faire vous-même, avec sh -c '...' sous Unix, et cmd /c '...' , _ mais uniquement si ce problème est résolu_; une implémentation binaire Invoke-NativeShell / ins cmdlet, tout en faisant principalement abstraction des détails du shell cible La syntaxe CLI éviterait le problème en question (par utilisation directe de System.Diagnostics.ProcessStartInfo.ArgumentList ), et peut donc être implémentée indépendamment).

@ yecril71pl

J'ai un scénario différent: les nouveaux utilisateurs essaient PowerShell, réalisent qu'il échoue mystérieusement, le déplacent vers la corbeille et ne reviennent jamais. C'est ce que je ferais.

Pourriez-vous clarifier: craignez-vous que le comportement actuel de PowerShell ne conduise à cette expérience utilisateur désagréable, ou craignez-vous que le changement proposé mène à cette expérience utilisateur.

Parce qu'à mon avis, le comportement actuel de PowerShell est beaucoup plus susceptible de générer une telle expérience. Comme dit ci-dessus: Le comportement actuel de PowerShell doit être considéré comme un bogue.

Pourquoi un nouvel utilisateur, sans aucune connaissance de la rupture de PowerShell, émettrait-il des commandes avec des arguments contorsionnés?

@ yecril71pl Juste pour être absolument sûr: vous considérez la forme actuellement requise pour les arguments "contorsionnée", pas la solution suggérée.

Je considère que votre question vient de votre supposition que je suis un nerd incurable. C'est correct - mais j'ai conservé la capacité d'imaginer ce qu'un type aléatoire normal considérerait comme déformé.

@ mklement0
Je pense que @ Artoria2e5 parlait de convertir le tableau d'arguments en une seule chaîne lpCommandLine en disant

L'ancienne invocation n'est pas non plus directe à cmdline avec son estimation de savoir si quelque chose est déjà cité

Parce qu'en appelant

echoargs.exe 'some"complicated_argument'

vous devez en effet plus ou moins deviner si PowerShell ajoute des citations autour de some"complicated_argument .

Exemple 1: dans la plupart des versions PowerShell
echoarg.exe 'a\" b' et echoarg.exe '"a\" b"' seront traduits en
"C:\path\to\echoarg.exe" "a\" b" (versions testées 2.0; 4.0; 6.0.0-alpha.15; 7.0.1)
mais mon powershell par défaut sur Win10 (version 5.1.18362.752) se traduit
echoarg.exe 'a\" b' à "C:\path\to\echoarg.exe" a\" b et
echoarg.exe '"a\" b"' à "C:\path\to\echoarg.exe" ""a\" b"" .

Exemple 2: les anciennes versions de PowerShell traduisent
echoarg.exe 'a"b c"' à "C:\path\to\echoarg.exe" "a"b c"" (versions testées 2.0; 4.0;)
alors que les versions plus récentes traduisent
echoarg.exe 'a"b c"' à "C:\path\to\echoarg.exe" a"b c" (versions testées 5.1.18362.752; 6.0.0-alpha.15; 7.0.1).


Comme le comportement a été clairement déjà changé plusieurs fois, je ne comprends pas pourquoi il ne peut pas être changé une fois de plus pour obtenir le comportement attendu.

Je vois, @TSlivede , merci pour la clarification, et désolé pour la mauvaise interprétation, @ Artoria2e5.

Quant au vrai problème: nous sommes à 100% d'accord - en fait, vous ne devriez même jamais avoir à penser à ce que lpCommandLine finit par être utilisé dans les coulisses sous Windows; si PowerShell faisait ce qu'il fallait, personne n'aurait à le faire (sauf pour les cas extrêmes, mais ils ne sont pas la faute de PowerShell, et c'est à ce moment-là que --% (tel qu'actuellement mis en œuvre) peut aider; avec un correctif approprié, il n'y aura jamais être des cas extrêmes sur les plates-formes de type Unix).

Quant à simplement résoudre correctement le problème: vous avez certainement mon vote (mais les solutions de contournement existantes vont casser).

TL; DR: L'hypothèse selon laquelle nous pouvons transmettre de manière fiable n'importe quelle valeur comme argument à n'importe quel programme du sous-système Microsoft Windows NT est fausse , nous devrions donc arrêter de prétendre que c'est notre objectif. Cependant, il reste encore beaucoup à sauver si l'on considère l' étendue de l'argument.

Lors de l'appel d'exécutables Windows natifs, nous devons conserver les guillemets d'origine. Exemple:

CMD /CSTART="WINDOW TITLE"

Le système ne trouve pas le fichier WINDOW.

 { CMD /CSTART="WINDOW TITLE" }. Ast. EndBlock. Statements. PipelineElements. CommandElements[1]

StringConstantType
BareWord
Valeur
/ CSTART = TITRE DE WINDOW
StaticType
System.String
Le degré
/ CSTART = "WINDOW TITLE"
Parent
CMD / CSTART = "WINDOW TITLE"

Si nous prenions l' étendue comme modèle, nous ne perdrions rien et nous pourrions appeler l'exécutable natif comme prévu. La solution de contournement de l'utilisation d'un argument de chaîne fonctionne ici, mais je ne pense pas qu'il soit strictement techniquement nécessaire de le faire, à condition qu'une prise en charge appropriée soit implémentée dans PowerShell. Cette approche fonctionnerait pour tous les cas.

Les guillemets entre guillemets présentent un problème insurmontable car il existe des outils qui interprètent l'échappement de la barre oblique inverse ( TASKLIST "\\\"PROGRAM FILES" ) et des outils qui ne le font pas ( DIR "\""PROGRAM FILES" /B ) et des outils qui ne dérangent pas ( TITLE A " B ). Cependant, si nous devions nous échapper, l'échappement standard avec des barres obliques inverses empoisonne tous les outils de gestion de fichiers courants car ils ne prennent tout simplement pas en charge les guillemets du tout et les doubles barres obliques inverses \\ signifient quelque chose de complètement différent pour eux (essayez DIR "\\\"PROGRAM FILES" /B ), donc envoyer un argument avec un guillemet à l'intérieur devrait être une erreur d'exécution. Mais nous ne pouvons pas lancer d'erreur car nous ne savons pas laquelle est laquelle. Alors que l'utilisation du mécanisme d'échappement normal ne devrait pas nuire aux arguments qui ne contiennent pas de guillemets, nous ne pouvons pas être sûrs que, lorsqu'il est appliqué à des arguments qui les contiennent et alimenté à un outil qui ne prend pas en charge les guillemets comme valeurs, il serait provoquer nécessairement une erreur d'abandon plutôt qu'un comportement inattendu, et un comportement inattendu serait en effet très mauvais. C'est un lourd fardeau que nous imposons à l'utilisateur. De plus, nous ne serons jamais en mesure de fournir des outils «indifférents» ( CMD /CECHO='A " B' ).

Notez que les variables d'environnement ne représentent pas des valeurs dans CMD , elles représentent des fragments de code qui sont analysés au fur et à mesure que les variables d'environnement sont développées, et il n'y a aucune disposition pour les traiter de manière fiable comme des arguments pour d'autres commandes. CMD n'opère tout simplement pas sur les objets de quelque nature que ce soit, pas même sur les chaînes, ce qui semble être la cause première de l'énigme actuelle.

TL; DR: L'hypothèse selon laquelle nous pouvons transmettre de manière fiable n'importe quelle valeur en tant qu'argument à n'importe quel programme du sous-système Microsoft Windows NT est incorrecte, nous devrions donc arrêter de prétendre que c'est notre objectif.

Cela devrait être le but, n'est-ce pas? Ce n'est pas le problème de PowerShell si un programme ne peut pas interpréter les arguments qu'il reçoit.

Lors de l'appel d'exécutables Windows natifs, nous devons conserver les guillemets d'origine. Exemple:

CMD /CSTART="WINDOW TITLE"

Suggérez-vous que l'appel d'un programme devrait changer dynamiquement le langage de PowerShell à tout ce que le programme appelé utilise? Vous avez écrit cet exemple dans PowerShell, ce qui signifie qu'il devrait être équivalent à l'un des éléments suivants

CMD "/CSTART=WINDOW TITLE"
CMD '/CSTART=WINDOW TITLE'
CMD /CSTART=WINDOW` TITLE

TL; DR: L'hypothèse selon laquelle nous pouvons transmettre de manière fiable n'importe quelle valeur en tant qu'argument à n'importe quel programme du sous-système Microsoft Windows NT est incorrecte, nous devrions donc arrêter de prétendre que c'est notre objectif.

Cela devrait être le but, n'est-ce pas? Ce n'est pas le problème de PowerShell si un programme ne peut pas interpréter les arguments qu'il reçoit.

Le programme CMD peut interpréter l'argument /CECHO=A " B mais PowerShell ne peut pas le transmettre sans le déformer.

Lors de l'appel d'exécutables Windows natifs, nous devons conserver les guillemets d'origine. Exemple:

CMD /CSTART="WINDOW TITLE"

Suggérez-vous que l'appel d'un programme devrait changer dynamiquement le langage de PowerShell à tout ce que le programme appelé utilise? Vous avez écrit cet exemple dans PowerShell, ce qui signifie qu'il devrait être équivalent à l'un des éléments suivants

CMD "/CSTART=WINDOW TITLE"
CMD '/CSTART=WINDOW TITLE'
CMD /CSTART=WINDOW` TITLE

J'ai essayé de suggérer que, lors de l'interfaçage avec des programmes externes sous le sous-système Microsoft Windows NT, PowerShell dispose d'une myriade de façons d'encoder les arguments qui sont tous équivalents à PowerShell mais pas équivalents au programme récepteur. Être brutal et forcer le One True Way ™ des arguments d'encodage, sans prêter attention à la disposition des citations utilisée par l'utilisateur, n'est pas utile, pour le dire légèrement.

@ yecril71pl Je suis vraiment confus par vos commentaires. Que proposez-vous exactement ici? Vos cas d'utilisation sont tous couverts par --% . Vous l'avez rejeté plus tôt en disant

--% nécessite une ligne de commande prédéfinie, son utilité est donc limitée.

Mais en fait, vous pouvez utiliser des variables d'environnement avec --% . Essaye ça:

PS > $env:mytitle='WINDOW TITLE'
PS > cmd --% /CSTART="%mytitle%"

Alors qu'est-ce que je manque?

Il nous manque la syntaxe CMD /CSTART="$mytitle" , sans rien divulguer à ENV: .

Comme une idée terrible, nous avons la possibilité de remplacer Environment.ExpandEnvironmentVariables par autre chose. De toute façon, il n'y a pas d'implémentation native sous Unix, et je ne pense pas que les choses qu'il traite deviendraient critiques pour les performances une fois réécrites en C #.

Puisque les signes égaux ne sont de toute façon pas autorisés dans les noms de var env, nous pouvons avoir %=$a% mean $a . Cela ne casserait rien d'existant tout en permettant des extensions très flexibles (et peut-être mauvaises) comme le faire fonctionner comme les chaînes de modèle de JS. Bon sang, nous pouvons également définir %VARNAME=$var% comme une sorte de syntaxe de secours.

Quant à l'enfer de la documentation que cela causerait ... Je m'excuse.

  • Nous n'avons _pas_ de problème d'analyse.

  • Ce que nous avons, c'est un problème avec _comment PowerShell transmet les arguments textuels et stringifiés qui ont résulté de l'analyse _its_ aux exécutables externes (natifs )_:

    • Sous _Windows_, le problème est que la ligne de commande pour appeler l'exécutable externe avec qui est construit en arrière-plan ne respecte _pas_ la convention la plus largement utilisée pour citer des arguments , comme détaillé dans la commande Parsing C ++ de la documentation du compilateur Microsoft C / C ++- Section des

    • Ce qui se passe actuellement n'est même pas qu'une convention _différente_ soit utilisée: probablement en raison d'un oubli, les lignes de commande qui sont construites sont situationnellement _syntactiquement fondamentalement cassées_, en fonction des spécificités des arguments, relatives à une combinaison de guillemets et d'espaces intégrés ainsi que des arguments de chaîne vide.

    • En fin de compte, le problème est l'architecture fondamentale de la création de processus sous Windows: vous êtes obligé de coder les arguments à passer à un processus _ en tant que ligne de commande_ - une seule chaîne représentant _ tous_ les arguments - plutôt que de les passer sous la forme d'un _ tableau_ d'arguments (qui c'est ainsi que les plates-formes de type Unix le font). La nécessité de passer une ligne de commande nécessite l'implémentation de _règles de guillemet et d'échappement_, et c'est finalement _à chaque programme_ comment interpréter la ligne de commande qui lui est donnée. En effet, cela revient à forcer inutilement les programmes à être une sorte de mini-shell: ils sont obligés de _re-exécuter_ la tâche que le shell a déjà effectuée, tâche qui devrait être du ressort d'un _shell uniquement_ (comme c'est le cas sous Unix), à savoir l'analyse d'une ligne de commande en arguments individuels. En un mot, c'est l' anarchie qui fait passer des arguments sous Windows .

    • Dans la pratique, l'anarchie est atténuée par le fait que la plupart des programmes adhèrent à la convention susmentionnée, et les nouveaux programmes en cours de développement sont très susceptibles d'adhérer à cette convention, principalement parce que les environnements d'exécution largement utilisés sous-tendant les applications de console implémentent ces conventions (telles que Microsoft C / C ++ / .NET). La solution sensée est donc:

      • Faites en sorte que PowerShell adhère à cette convention lors de la création de la ligne de commande en arrière-plan.
      • Pour les programmes "escrocs" qui ne respectent _pas_ cette convention - qui inclut notamment cmd.exe , les fichiers batch et les utilitaires Microsoft tels que msiexec.exe et msdeploy.exe - fournissez un mécanisme pour explicitement contrôler la ligne de commande passée à l'exécutable cible; c'est ce que fournit --% , le symbole d'arrêt de l'analyse - quoique assez maladroit .
    • Sur _Unix_, le problème est que _une ligne de commande est en cours de construction_ - à la place, le tableau d'arguments verbatim doit être passé _as-is_ , que .NET Core prend désormais en charge (depuis la v2.1, via ProcessStartInfo.ArgumentList ; il devrait toujours avoir pris en charge cela, étant donné que - raisonnablement - _il n'y a pas de lignes de commande_, seulement des tableaux d'arguments, lorsqu'un processus est créé sur des plates-formes de type Unix).

    • Une fois que nous utilisons ProcessStartInfo.ArgumentList , tous les problèmes sous Unix disparaissent.

La résolution de ces problèmes est le but du https://github.com/PowerShell/PowerShell-RFC/pull/90 de @TSlivede .

Dans https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -650242411, j'ai proposé en outre de compenser automatiquement la "roguishness" des fichiers batch, étant donné leur utilisation encore très répandue comme points d'entrée CLI pour les -profile logiciel tel qu'Azure (CLI az est implémenté sous forme de fichier batch, az.cmd ).
De même, nous devrions envisager de faire la même chose pour msiexec.exe et msdeploy.exe et peut-être d'autres CLI Microsoft «non fiables» de haut niveau.


Je viens de publier un module, Native , ( Install-Module Native -Scope CurrentUser ) qui traite de tout ce qui précède via sa fonction ie (abréviation de i nvoke (external) e xecutable; it est une implémentation plus complète de la fonction iep présentée ci-dessus ).

Il inclut également ins ( Invoke-NativeShell ) , qui adresse # 13068, et dbea ( Debug-ExecutableArguments ) pour diagnostiquer le passage d'arguments - voir https: // github. com / PowerShell / PowerShell / issues / 13068 # issuecomment -671572939 pour plus de détails.

En d'autres termes: ie peut servir d'étape discrète pendant que nous attendons que ce problème soit résolu, simplement en préfixant les invocations avec ie comme commande:

Au lieu de:

# This command is currently broken, because the '{ "name": "foo" }' argument isn't properly passed.
curl.exe -u jdoe  'https://api.github.com/user/repos' -d '{ "name": "foo" }'

vous utiliseriez ce qui suit:

# OK, thanks to `ie`
ie curl.exe -u jdoe  'https://api.github.com/user/repos' -d '{ "name": "foo" }'

Comme pour l'exemple CMD /CSTART="WINDOW TITLE" (dont la forme la plus idiomatique est cmd /c start "WINDOW TITLE" , qui fonctionne déjà):

C'est essentiellement le même problème qu'avec les arguments prop="<value with spaces>" pour msiexec / msdeploy : PowerShell - à juste titre - transforme /CSTART="WINDOW TITLE" en "/CSTART=WINDOW TITLE" , ce qui, cependant, rompt l'appel cmd.exe .

Il existe deux façons de résoudre ce problème:

  • Déléguez à ins / Invoke-NativeShell (notez que l'utilisation de cmd.exe /c est effectivement implicite):

    • ins 'START="WINDOW TITLE"'
    • Si vous utilisez une chaîne _expandable_, vous pouvez incorporer des valeurs PowerShell dans la chaîne de commande.

      • $title = 'window title'; ins "START=`"$title`""

  • Vous pouvez également utiliser l'implémentation actuelle de --% , mais attention à ses limitations :

    • cmd --% /CSTART="WINDOW TITLE"
    • Comme discuté, une limitation problématique de --% est que la seule façon d'incorporer des valeurs _PowerShell_ est d'utiliser un _aux. variable d'environnement_ et référencez-la avec la syntaxe %...% :

      • $env:_title = 'window title'; cmd --% /CSTART="%_title%"

      • Pour éviter cette limitation, --% devrait toujours avoir été implémenté avec un argument de chaîne _single_ - par exemple,

        cmd --% '/CSTART="WINDOW TITLE"' ou cmd --% "/CSTART=`"$title`"" - mais cela ne peut pas être changé sans rompre la compatibilité descendante, donc un symbole _nouveau_ devrait être introduit - personnellement, je n'en vois pas la nécessité.

  • ils sont obligés de _re-exécuter_ la tâche que le shell a déjà effectuée

Je ne pense pas que CMD.EXE divise les lignes de commande en arguments, la seule chose qui est nécessaire est de savoir quel exécutable appeler et le reste est juste la ligne de commande écrite par l'utilisateur (après les substitutions de variables d'environnement, qui sont effectués sans aucun égard pour les limites des arguments). Bien sûr, les commandes internes du shell sont une exception ici.

C'est essentiellement le même problème qu'avec les arguments prop="<value with spaces>" pour msiexec / msdeploy

Je ne suis pas un utilisateur confiant de l'un ou de l'autre, j'ai donc préféré évoquer quelque chose que je connais mieux.

Pour être clair: ce qui suit n'a aucun impact sur les points soulevés dans mon commentaire précédent.

Je ne pense pas que CMD.EXE divise les lignes de commande en arguments

  • Il peut s'en tirer sans _explicit_ fractionnement lors de l'appel _external executables_ (commandes exécutées par un autre exécutable dans un processus enfant), mais il doit le faire pour les _batch files_.

  • Même lors de l'appel d'exécutables externes, il doit être conscient des limites des arguments, afin de déterminer si un métacaractère donné (par exemple & ) a une fonction _syntactic_ ou s'il fait partie d'un argument entre guillemets doubles et donc à traiter comme un littéral:

:: OK - the "..." around & tells cmd.exe to use it verbatim
C:\>echoArgs.exe one "two & three"
Arg 0 is <one>
Arg 1 is <two & three>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" one "two & three"

De plus, cmd.exe reconnaît les caractères _embedded_ " . in "..." chaînes sont reconnues si elles sont échappées comme "" :

:: OK - the "" is recognized as an escaped "
C:\>echoArgs.exe "3"" of rain & such."
Arg 0 is <3" of rain & such.>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "3"" of rain & such."

Malheureusement, cmd.exe _only_ prend en charge (uniquement Windows) "" et pas aussi le \" plus largement utilisé (ce que les _shells_ de type POSIX sur Unix utilisent _exclusivement_ - remarque: _shells_, pas _programs_, car les programmes ne voient que le tableau des arguments verbatim qui résultent de l'analyse du shell).

Alors que la plupart des CLI sur Windows prennent en charge _both_ "" et \" , certains _seulement_ comprennent \" (notamment Perl et Ruby), et alors vous avez des problèmes:

:: !! BROKEN: cmd.exe misinterprets the & as *unquoted*, thinks it's the statement-sequencing operator, 
:: !! and tries to execute `such`:
C:\>echoArgs.exe "3\" of rain & such."
Arg 0 is <3" of rain >

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "3\" of rain

'such."' is not recognized as an internal or external command,
operable program or batch file.

Par conséquent:

  • Évitez d'appeler directement cmd.exe , si possible.

    • Appelez les exécutables externes directement (une fois ce problème résolu) ou via ie (pour le moment), en utilisant la syntaxe _PowerShell's_.
  • Si vous devez appeler cmd.exe , utilisez ins / Invoke-NativeShell pour la simplicité générale et, en particulier, pour la facilité d'intégration des valeurs de variable et d'expression PowerShell dans la ligne de commande .

    • Une raison légitime de toujours appeler cmd.exe directement est de compenser le manque de prise en charge de PowerShell pour les données d'octets bruts dans le pipeline - voir cette réponse SO pour un exemple.

Je sais que je vais attraper beaucoup de critiques ici, et j'apprécie vraiment la profondeur de la discussion qui se déroule, mais ... canards ... est-ce que quelqu'un a un exemple de tout cela qui compte réellement dans un scénario réel ?

Je pense que nous ne sommes pas habilités dans PowerShell à résoudre «l'anarchie» qui existe actuellement avec l'analyse des arguments Windows. Et pour plusieurs des mêmes raisons que nous ne pouvons pas résoudre le problème, il y a une bonne raison pour laquelle Windows et les compilateurs VC ++ ont choisi de ne pas briser ce comportement. C'est endémique, et nous n'allons créer une très longue queue de nouveaux problèmes (et largement indéchiffrables) que si nous changeons les choses.

Pour les utilitaires déjà multiplateformes et très utilisés entre Windows et Linux (par exemple Docker, k8s, Git, etc.), je ne vois pas ce problème se manifester dans le monde réel.

Et pour ces applications «voyous» qui font un mauvais travail: ce sont en grande partie des utilitaires hérités de Windows uniquement.

Je conviens que ce que vous avez décrit @ mklement0 est en grande partie une solution "correcte". Je ne sais tout simplement pas comment y arriver sans vraiment foirer les choses.

Les usages assez basiques se cassent:

❯ git commit --allow-empty -m 'this is what we call a "commit message" which contains arbitrary text, often with punctuation'
error: pathspec 'message which contains arbitrary text, often with punctuation' did not match any file(s) known to git
❯ $a = 'this is what we call a "commit message" which contains arbitrary text, often with punctuation'
❯ git commit --allow-empty -m "$a"
error: pathspec 'message which contains arbitrary text, often with punctuation' did not match any file(s) known to git
❯ $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.0.3
PSEdition                      Core
GitCommitId                    7.0.3
OS                             Microsoft Windows 10.0.19042
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

git.exe est une application qui se comporte bien, donc le correctif dans PowerShell sera simple, bien que tous les scripteurs soient obligés de revenir sur leurs solutions intelligentes. cmd.exe est plus difficile à adapter et nécessite une approche beaucoup plus prévenante qui peut résoudre quelques problèmes mais probablement pas tous. Ce qui est en fait épouvantable, étant donné que PowerShell a commencé comme un outil Windows NT. Je comprends cette question comme _ s'il existe un scénario réel où un utilitaire hérité mal comporté comme cmd.exe sera appelé à partir de PowerShell d'une manière qui pose des problèmes dans l'interface_. PowerShell a tenté d'aborder ce problème en dupliquant la plupart des fonctionnalités dans cmd.exe , de manière à rendre cmd.exe redondant. Ceci est également possible pour d'autres outils, par exemple MSI peut être exploité via ActiveX, bien que cela nécessite des connaissances considérables. Alors, y a-t-il quelque chose d'essentiel qui n'est pas couvert?

@ PowerShell / PowerShell-Committee en a discuté. Nous apprécions l'exemple de git qui montre clairement un exemple convaincant dans le monde réel. Nous avons convenu que nous devrions avoir une fonctionnalité expérimentale au début de la version 7.2 pour valider l'impact d'une telle modification de rupture. Un exemple de test supplémentaire montre que même --% a un problème même s'il aurait dû être non analysé:

PS> testexe --% -echoargs 'a b c "d e f " g h'
Arg 0 is <'a>
Arg 1 is <b>
Arg 2 is <c>
Arg 3 is <d e f >
Arg 4 is <g>
Arg 5 is <h'>

Cela semble être un problème dans le classeur de paramètres de commande native.

Ouais, merci @cspotcode. Cet exemple a été définitivement un moment aha pour moi (d'autant plus que j'ai vraiment frappé celui-là dans le monde réel).

Je suis toujours préoccupé par l'aspect du changement radical, et je pense que c'est un candidat potentiel pour une fonctionnalité expérimentale qui peut rester expérimentale sur plusieurs versions de PowerShell, et ce n'est absolument pas quelque chose dont nous sommes sûrs que cela finira par réussir.

J'ai également besoin de creuser davantage pour comprendre l'aspect liste d'autorisation / "application rouge" de votre RFC, @ mklement0 , car je ne sais pas à quel point nous voulons nous inscrire pour maintenir une liste comme celle-là.

@joeyaiello et @ SteveL-MSFT, laissez-moi d'abord faire une méta-observation:

Alors qu'il est bon de voir cet exemple de @cspotcode vous a donné un _glimpse_ du problème, vos réponses trahissent encore un manque fondamental de la compréhension et l' appréciation de la (ampleur du) problème sous - jacent (je soutiendrai ce point dans un commentaire plus tard) .

Ce n'est pas un jugement _personnel_: je reconnais pleinement combien il doit être difficile d'être étiré très mince et d'avoir à prendre des décisions sur un très large éventail de sujets dans un court laps de temps.

Cependant, cela indique un problème de structure: il me semble que les décisions sont régulièrement prises par le comité @ PowerShell / powershell sur la base d'une compréhension superficielle des problèmes discutés, au détriment de la communauté dans son ensemble.

Pour moi, la réponse du comité à la question dont il est question ici est l'exemple le plus significatif de ce problème structurel à ce jour.

Par conséquent, je vous demande de considérer ceci:

Que diriez-vous de nommer des sous-comités spécifiques à un sujet que le comité consulte et qui ont la compréhension requise des questions impliquées?

pouvez-vous partager le contenu de testexe SteveL-MSFT, je veux juste être sûr!

@TSlivede a bien résumé le problème dans https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -665125375:

PowerShell, d'un autre côté, prétend être un shell (jusqu'à ce que # 1995 soit résolu, je ne dirai pas que c'est un shell)

Comme indiqué à maintes reprises, l'un des principaux mandats d'un shell est d'appeler des exécutables externes avec des arguments.

PowerShell ne parvient actuellement pas à remplir ce mandat, étant donné que les arguments avec des guillemets doubles et des arguments de chaîne vide ne sont pas transmis correctement.

Comme indiqué précédemment, cela a peut-être été moins problématique à l'époque de Windows uniquement, où le manque de CLI externes capables a rarement fait surface, mais ces jours sont révolus, et si PowerShell veut s'établir comme une plateforme multiplateforme crédible shell, il doit résoudre ce problème.

L'exemple git @cspotcode est un bon exemple; tout exécutable auquel vous voulez passer une chaîne JSON - par exemple, curl - en est un autre:

# On Unix; on Windows, 
#   echoArgs.exe '{ "foo": "bar" }' 
# would show the same problem.
PS> /bin/echo '{ "foo": "bar" }'
{ foo: bar }  # !! Argument was incorrectly passed.

Laissant de côté la rétrocompatibilité:

  • Sous Unix, le problème est trivialement et _complètement_ résolu en utilisant ProcessStartInfo.ArgumentList dans les coulisses.

  • Sous Windows, le problème est trivial et _ presque_ résolu en utilisant ProcessStartInfo.ArgumentList dans les coulisses.

    • Pour les cas marginaux (CLI "escrocs"), il y a le ( mal implémenté ) --%
    • En guise de _courtoisie_, nous pouvons compenser certains cas extrêmes bien connus pour réduire le besoin de --% - voir ci-dessous.

Par conséquent, _dès que possible_, l'un des choix suivants doit être effectué:

  • Réalisez l'importance de faire fonctionner correctement le passage d'arguments et de le _fixer au détriment de la compatibilité descendante_.

  • Si la rétrocompatibilité est vraiment primordiale, fournissez un nouvel opérateur ou une fonction telle que la fonction ie du module Native qui résout le problème, et publiez-le largement comme le seul moyen fiable d'appeler externe exécutables.

Proposer une fonction _expérimentale_ pour traiter une caractéristique fondamentale mal rompue est totalement inadéquate.


@ SteveL-MSFT

Même envisager l'utilisation de --% comme solution à ce problème est fondamentalement erroné:

C'est une fonctionnalité _Windows uniquement_ qui ne connaît que les références de variables d'environnement "..." et %...% -style.

Sous Unix, le concept "d'arrêt de l'analyse" ne s'applique fondamentalement pas: _il n'y a pas de ligne de commande_ à passer aux processus enfants, seulement des tableaux d'arguments.

Ainsi, _someone_ doit analyser la ligne de commande en arguments _before_ invocation, qui est implicitement déléguée à la classe ProcessStartInfo , via sa propriété .Arguments , qui sous Unix utilise les conventions _Windows_ pour analyser une ligne de commande - et reconnaît donc les guillemets "..." (avec échappement de " comme "" ou \" ) uniquement.

--% est une fonctionnalité Windows uniquement dont le seul but légitime est d'appeler des CLI «non fiables».


@joeyaiello

que Windows et les compilateurs VC ++ ont choisi de ne pas briser ce comportement.

Le compilateur VC ++ _impose une convention sensée et largement observée_ , pour mettre de l'ordre dans l'anarchie.

C'est précisément l'adhésion à cette convention qui est préconisée ici , ce que l'utilisation de ProcessStartInfo.ArgumentList nous donnerait automatiquement.

_Cela couvrira à lui seul la grande majorité des appels. Couvrir TOUS les appels est impossible et ne relève pas de la responsabilité de PowerShell._

Comme indiqué, pour les CLI "escrocs" qui nécessitent des formes non conventionnelles de cotation, --% doit être utilisé (ou ins / Invoke-NativeShell du module Native ).

_A titre de courtoisie_, nous pouvons automatiquement compenser les scénarios «non fiables» bien connus, à savoir l'appel de fichiers batch et certaines CLI Microsoft de haut niveau:

  • Le cas du fichier batch est générique, et facilement expliqué et conceptualisé (par exemple, passez a&b comme "a&b" , même s'il ne devrait pas nécessiter de guillemets) - cela évitera le besoin d'utilisation de --% avec toutes les CLI qui utilisent des fichiers batch comme point d'entrée (ce qui est assez courant), tels que az.cmd Azure

  • L'alternative aux exceptions de codage en dur pour des CLI spécifiques - qui, certes, peut prêter à confusion - est de détecter le _pattern_ suivant dans les arguments qui résultent de l'analyse de PowerShell - <word>=<value with spaces> - et, au lieu de passer "<word>=<value with spaces>" , comme cela se produit actuellement, pour passer <word>="<value with spaces>" ; ce dernier satisfait les CLI «non fiables», tout en étant également accepté par les CLI adhérant à la convention; par exemple, echoArgs "foo=bar baz" voit finalement le même premier argument que echoArgs --% foo="bar baz"

@musm Vous pouvez trouver le code source de TestExe sur https://github.com/PowerShell/PowerShell/blob/master/test/tools/TestExe/TestExe.cs.

GitHub
PowerShell pour chaque système! Contribuez au développement PowerShell / PowerShell en créant un compte sur GitHub.

Je pense que la prise en charge des exceptions par défaut va simplement conduire à une situation similaire à la situation actuelle, où les gens doivent revenir sur "l'utilité" de PowerShell. S'il y a des exceptions, il devrait être évident qu'elles sont appliquées.

Peut-être quelque chose comme:

# Arguments passed correctly, without regard for the program's ability to handle them
& $program a "" 'c "d e" f'
# Try to pass the arguments intelligently based on the program being called
&[] $program a "" 'c "d e" f'
# Escape the arguments for a batch file, eg) " -> ""
&[bat] $program a "" 'c "d e" f'

J'ai vraiment du mal à trouver une syntaxe pour cela qui n'est pas cassée. Au moins, cela a du sens si vous pensez qu'il s'agit d'un transtypage du programme, mais le transtypage de la variable réelle contenant le programme nécessiterait d'inclure des parenthèses.

Cela, en plus de permettre aux gens d'ajouter des exceptions pour tout comportement cassé qu'ils souhaitent, devrait, espérons-le, éliminer le besoin de --% . La classe d'exception qu'ils enregistrent aurait alors une méthode pour déterminer si elle est applicable au programme (pour un appel intelligent), et une méthode d'échappement où vous lui lancez simplement l'arborescence de syntaxe abstraite de la commande et elle renvoie le tableau / la chaîne d'arguments.

  • au lieu de passer "<word>=<value with spaces>" , comme c'est le cas actuellement, pour passer <word>="<value with spaces>"

Les guillemets utilisés pour construire la ligne de commande doivent suivre la façon dont l'appel est cité dans PowerShell. Par conséquent:

  1. Un appel qui ne contient pas de guillemets doubles dans son texte doit placer des guillemets doubles autour de la valeur entière.
  2. Un appel qui contient des guillemets doit les conserver tels qu'ils sont écrits dans le script _ si possible_.

En particulier:
| argument de script | ligne de commande |
| ----------------- | ---------------- |
| p=l of v | "p = l de v" |
| p=l` of` v | "p = l de v" |
| p="l of v" | p = "l de v" |
| p="l of v"'a s m' | p = "l de v" un "sm" |
| p="l of v"' s m' | p = "l de vsm" |

La dernière ligne montre un exemple où il ne sera pas possible de conserver les guillemets doubles d'origine.

Je sais que je vais attraper beaucoup de critiques ici, et j'apprécie vraiment la profondeur de la discussion qui se déroule, mais ..._ canards _... est-ce que quelqu'un a un exemple de tout cela qui compte réellement dans un scénario réel ?

Je l'ai déjà mentionné dans ce fil il y a un an (c'est caché maintenant ...) que j'avais l'habitude d'obtenir beaucoup de moments WFT en utilisant

rg '"quoted"'

et dans git bash, ce n'est pas le cas.

Maintenant, je reçois moins ces moments WTF parce que malheureusement, j'ai trouvé ce long problème de github et j'ai trouvé que passer " à Powershel est totalement cassé. L'exemple récent de "git.exe" est également excellent.

Pour être honnête, maintenant je n'ose même plus utiliser Powershell pour appeler la commande native alors que je sais que je pourrais passer " en chaîne comme paramètre. Je sais que je pourrais avoir un mauvais résultat ou une erreur.

Vraiment, @ mklement0 l'a très bien résumé (cela devrait être gravé dans la pierre quelque part)

Comme indiqué à maintes reprises, l'un des principaux mandats d'un shell est d'appeler des exécutables externes avec des arguments .
PowerShell ne parvient actuellement pas à remplir ce mandat, étant donné que les arguments avec des guillemets doubles et des arguments de chaîne vide ne sont pas transmis correctement.
Comme indiqué précédemment, cela a peut-être été moins problématique à l'époque de Windows uniquement, où le manque de CLI externes capables a rarement fait surface, mais ces jours sont révolus, et si PowerShell veut s'établir comme une plateforme multiplateforme crédible shell, il doit résoudre ce problème .

Et sur les changements de rupture.
Récemment, un collègue m'a écrit que mon script ne fonctionnait pas sur sa machine. Je ne l'exécutais que sur Powershel Core et il l'exécutait sur Windows Powershell. Il s'avère que Out-File -Encoding utf8 fichier encodé avec "BOM" sur Windows Powershell et sans BOM sur Powershel Core. Cet exemple est en quelque sorte non supprimé, mais montre qu'il y a déjà des changements de rupture subtils dans Powershel et c'est bien parce que nous éliminons les bizarreries et le comportement intuitif d'un langage qui est célèbre pour cela. Ce serait formidable si l'équipe de Powershel était un peu plus indulgente en ce qui concerne les modifications apportées maintenant que nous avons Powershell multiplateforme qui est expédié en dehors de Windows et que nous savons que Windows Powershell est en mode "maintenance" et sera utilisable pour toujours si vous voulez vraiment qu'il exécute un ancien script qui a éclaté dans la nouvelle version de Powershell.

RE: ce dernier point de rupture des changements - je suis entièrement d'accord. Il y a de nombreux changements de rupture que nous avons tolérés pour diverses raisons. Cependant, il semble de plus en plus souvent que certains changements de rupture sont simplement désapprouvés pour des raisons de préférence et ne sont pas suffisamment pris en considération pour leur valeur réelle.

Il y a quelques changements comme celui-ci qui amélioreraient _ massivement_ l'expérience globale du shell pour tous ceux qui ont besoin d'accéder à l'extérieur de PowerShell pour faire avancer les choses, ce qui se produit tout le temps. Il a été convenu à maintes reprises que le comportement actuel est intenable et déjà largement rompu pour autre chose que les usages les plus simples. Et pourtant, nous sommes toujours confrontés à cette réticence à rompre les changements, même s'il existe de nombreux changements de rupture déjà acceptés, dont certains ont un impact tout aussi important.

Pour ceux qui demandent des exemples, prenez une minute pour visiter Stack Overflow pour une fois. Je suis sûr que @ mklement0 a une litanie d'exemples où l'aide de la communauté est nécessaire pour expliquer un changement radical dans les nouvelles versions. Cela arrive tout le temps_. Nous n'avons aucune excuse pour ne pas apporter de modifications décisives utiles.

Chaque fois que l'équipe MSFT répète sans cesse la même chose, nous pouvons être sûrs qu'elle en sait plus qu'elle ne peut en dire publiquement. Nous devons respecter leur discipline intérieure et ne pas faire pression sur eux. _Peut-être pouvons-nous trouver un compromis._ J'espère avoir le temps aujourd'hui de décrire une voie alternative avec la migration paresseuse.

Je reconnais cela, et c'est pourquoi je mets rarement un point d'honneur à le remettre en question.

Cependant, il s'agit d'un projet open source; s'il n'y a aucune visibilité possible sur ces décisions, les gens finiront inévitablement frustrés. Aucun blâme à jeter de chaque côté de cette médaille, c'est juste la réalité de la situation ici, OMI. Donc oui, avoir un chemin de migration peut soulager quelque peu cette douleur, mais nous avons besoin de politiques claires définies sur la façon dont cela doit fonctionner qui feront que les choses fonctionnent pour le plus de gens possible. Le compromis est cependant difficile à atteindre en cas de manque d'informations.

J'ai hâte de voir ce que vous avez dans votre manche. 😉

@ mklement0 vous avez absolument raison, et à tel point que je ne peux répondre qu'à votre méta-point pour le moment. Malheureusement, dans les cas où nous ne sommes pas en mesure d'atteindre le niveau de profondeur requis pour répondre à une question comme celle-ci, l'approche la plus sûre consiste souvent à reporter ou à rejeter le changement de rupture jusqu'à ce que nous ayons plus de temps pour

Cependant, je veux faire un autre méta-point sur les changements de rupture: notre télémétrie implique que la plupart des utilisateurs de PowerShell 7 ne gèrent pas leurs propres versions. Ils exécutent des scripts automatisés dans un environnement géré qui est confortable, par exemple la mise à niveau de leurs utilisateurs de la 6.2 à la 7.0 (voir le saut de 2 jours dans les utilisateurs de 6.2 devenant des utilisateurs de 7.0 à partir du 8/3; ce n'est pas notre seul point de données ici, mais c'est pratique en ce moment qui fait le point). Pour ces utilisateurs, un changement de rupture qui transforme un script parfaitement fonctionnel en un script non fonctionnel est inacceptable.

Je dois également à la communauté un blog sur ma façon de penser l'impact des changements de rupture: à savoir troquer la prévalence de l'usage existant et la gravité de la rupture contre la facilité d'identification et de correction de la rupture. Celui-ci est extrêmement répandu dans les scripts existants, il est difficile à identifier et à corriger, et le comportement de rupture va du succès total à l'échec total, d'où ma réticence extrême à faire quoi que ce soit ici.

Je pense qu'il est juste de dire que nous n'allons rien faire ici dans la version 7.1, mais je suis tout à fait disposé à en faire une priorité d'enquête pour la version 7.2 (c'est-à-dire que nous passons plus que le temps de notre comité à en discuter.)

Que diriez-vous de nommer des sous-comités spécifiques à un sujet que le comité consulte et qui ont la compréhension requise des questions en jeu?

Nous y travaillons. Je sais que je l'ai déjà dit, mais nous sommes extrêmement proches (comme dans, je suis en train de créer le blog et vous allez probablement voir apparaître bientôt de nouveaux labels avec lesquels nous jouerons).

J'apprécie la patience de chacun et je reconnais qu'il est ennuyeux d'obtenir une réponse concise du Comité toutes les deux semaines alors que les gens consacrent énormément de réflexion et de considération à la discussion. Je sais qu'il semble que cela signifie que nous ne réfléchissons pas profondément aux choses, mais je pense que nous n'exprimons tout simplement pas la profondeur de nos discussions avec autant de détails que les gens ici. Dans mon propre arriéré, j'ai tout un ensemble de sujets de blog comme celui du changement radical sur la façon dont je pense prendre des décisions au sein du comité, mais je n'ai tout simplement jamais eu la chance de m'asseoir et de les exprimer. Mais je peux voir ici que les gens y trouveraient peut-être beaucoup de valeur.

J'espère que je ne suis pas allé trop loin dans cette discussion. Je ne veux pas que ce problème devienne totalement un méta-problème de la gestion du projet, mais je voulais aborder une partie de la frustration compréhensible que je vois ici. J'implore tous ceux qui souhaitent en parler plus en détail avec nous de se joindre à l' appel de la ici et je ne manquerai pas d'y répondre lors de l'appel).

Juste une petite note sur le méta-point: j'apprécie la réponse réfléchie, @joeyaiello.

Quant à la gravité du changement de rupture: les affirmations suivantes semblent contradictoires:

Quelqu'un a-t-il un exemple de tout cela qui compte réellement dans un scénario réel

contre.

Celui-ci est extrêmement répandu dans les scripts existants, il est difficile d'identifier et de corriger

S'il est déjà répandu, la maladresse et l'obscurité des solutions de contournement nécessaires sont une raison de plus pour résoudre enfin ce problème, d'autant plus qu'il faut s'attendre à ce que le nombre de cas augmente.

Je me rends compte que toutes les solutions de contournement existantes seront interrompues.

S'il est primordial d'éviter cela, cette approche suggérée précédemment est la voie à suivre:

fournissez un _nouveau opérateur ou une fonction_ telle que la fonction ie du module Native qui résout le problème, et publiez-le largement comme le seul moyen fiable d'appeler des exécutables externes.

Une _fonction_ telle que ie permettrait aux gens d'accepter le comportement correct avec _minimal fuss_, _ comme un palliatif_, sans alourdir le _language_ avec un nouvel élément syntaxique (un opérateur), dont la seule raison d'être serait pour contourner un bug hérité jugé trop grave pour être corrigé:

  • Le «stopgap» fournirait un accès _officiellement sanctionné_ au comportement correct (sans dépendre de fonctionnalités _experimental_).
  • Aussi longtemps que le palliatif sera nécessaire, il devra être largement diffusé et correctement documenté.

Si / quand le comportement par défaut est corrigé:

  • la fonction peut être modifiée pour s'y reporter, afin de ne pas casser le code qui l'utilise.
  • le nouveau code peut être écrit sans avoir besoin de la fonction plus.

Une fonction telle que ie permettrait aux gens d'accepter le comportement correct avec un minimum de tracas, comme un palliatif

Nous pouvons simplifier l'adoption avec # 13428. Nous pouvons injecter ceci avec les investigations de @ mklement0 dans Engine de manière transparente.

@Dabombber

Je pense que la prise en charge des exceptions par défaut va simplement conduire à une situation similaire à la situation actuelle, où les gens doivent revenir sur "l'utilité" de PowerShell. S'il y a des exceptions, il devrait être évident qu'elles sont appliquées.

Les accommodements que je propose font que la grande majorité des appels "fonctionnent simplement" - ce sont des abstractions utiles de l'anarchie de la ligne de commande Windows dont nous devrions faire de notre mieux pour protéger les utilisateurs.

L'hypothèse justifiable est que ce blindage est un effort ponctuel pour les exécutables _legacy_, et que ceux qui viennent d'être créés adhéreront aux conventions Microsoft C / C ++.

Il est impossible de faire cela dans _tous_ les cas, cependant; pour ceux qui ne peuvent pas être accueillis automatiquement, il y a --% .

Personnellement, je ne veux pas avoir à me demander si un utilitaire donné foo est implémenté en tant que foo.bat ou foo.cmd , ou s'il nécessite foo="bar none" arguments spécifiquement, sans accepter également "foo=bar none" , qui pour les exécutables conformes aux conventions sont équivalents.

Et je ne veux certainement pas d'un formulaire de syntaxe distinct pour diverses exceptions, telles que &[bat]
Au lieu de cela, --% est l'outil fourre-tout (Windows uniquement) pour formuler la ligne de commande exactement comme vous le souhaitez, quelles que soient les exigences spécifiques et non conventionnelles du programme cible.

Plus précisément, les aménagements proposés sont les suivants:

Remarque:

  • Comme indiqué, ils sont requis _ sous Windows_ uniquement; sous Unix, déléguer à ProcessStartInfo.ArgumentList est suffisant pour résoudre tous les problèmes.

  • Au moins à un niveau élevé, ces aménagements sont faciles à conceptualiser et à documenter.

  • Notez qu'ils seront appliqués après l'analyse habituelle de PowerShell, à l'étape de traduction (Windows uniquement) en ligne de commande du processus. Autrement dit, l'analyse des paramètres de PowerShell ne sera pas impliquée - et _ne devrait pas_ l'être , @ yecril71pl.

  • Tous les cas vraiment exotiques non couverts par ces hébergements devraient être traités par les utilisateurs eux-mêmes, avec
    --% - ou, avec le module Native installé, avec ins / Invoke-NativeShell , ce qui facilite l'intégration des valeurs de variable et d'expression PowerShell dans l'appel.

    • La commande dbea ( Debug-ExecutableArguments ) du module Native peut aider à diagnostiquer et à comprendre quelle ligne de commande de processus est finalement utilisée - voir l'exemple ci-dessous.

Liste des hébergements:

  • Pour les fichiers de commandes (comme indiqué, l'importance de prendre en compte automatiquement ce cas est la prévalence des CLI de haut niveau qui utilisent des fichiers de commandes _ comme point d'entrée_, comme az.cmd pour Azure).

    • Les guillemets doubles incorporés, le cas échéant, sont échappés sous la forme "" (plutôt que \" ) dans tous les arguments.
    • Tout argument qui ne contient cmd.exe tels que & est placé entre guillemets (alors que PowerShell par défaut n'inclut que les arguments avec des espaces entre guillemets doubles); Par exemple, un argument textuel vu par PowerShell comme a&b est placé comme "a&b" sur la ligne de commande passée à un fichier de commandes.
  • Pour les CLI de haut niveau telles que msiexec.exe / msdeploy.exe et cmdkey.exe (sans exceptions de codage en dur pour eux):

    • Tout appel qui contient au moins un argument des formes suivantes déclenche le comportement décrit ci-dessous; <word> peut être composé de lettres, de chiffres et de traits de soulignement:

      • <word>=<value with spaces>
      • /<word>:<value with spaces>
      • -<word>:<value with spaces>
    • Si un tel argument est présent:

      • Les guillemets doubles incorporés, le cas échéant, sont échappés sous la forme "" (plutôt que \" ) dans tous les arguments. - voir https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 pour savoir pourquoi nous ne devrions _pas_ faire cela; cela signifie que dans le cas rare où <value with spaces> a _embedded_ " caractères., --% doit être utilisé; par exemple,
        msiexec ... --% PROP="Nat ""King"" Cole"
      • Seule la partie <value with spaces> est placée entre guillemets, pas l'argument dans son ensemble (ce dernier étant ce que PowerShell - à juste titre - fait par défaut); Par exemple, un argument textuel vu par PowerShell comme foo=bar none est placé comme foo="bar none" sur la ligne de commande du processus (plutôt que comme "foo=bar none" ).
    • Remarque:

      • Si l'exécutable cible se trouve _pas_ être une CLI de style msiexec , aucun mal n'est fait, car les CLI respectant les conventions considèrent raisonnablement <word>="<value with spaces>" et "<word>=<value with spaces>" _equivalent_, tous deux représentant verbatim <word>=<value with spaces> .

      • De même, la grande majorité des exécutables acceptent "" manière interchangeable avec \" pour échapper les " chars incorporés., À l'exception notamment de la propre CLI, Ruby et Perl de PowerShell (_pas_ l'accommodation vaut au moins la peine si la CLI PowerShell est appelée, mais je pense que le codage en dur Ruby et Perl aurait également du sens). https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 montre que toutes les applications qui utilisent la fonction CommandLineToArgvW WinAPI ne prennent pas en charge "" -escaping.

Tous les autres cas sous Windows peuvent également être traités avec ProcessStartInfo.ArgumentList , qui applique implicitement la convention Microsoft C / C ++ (ce qui signifie notamment \" pour " -escaping).


La fonction ie de la version actuelle ( 1.0.7 ) du module Native implémente ces accommodements (en plus de corriger l'analyse des arguments cassés) , pour les versions 3 et supérieures de Install-Module Native ).

Je vous invite, vous et tout le monde ici, à le mettre à l'épreuve pour tester l'affirmation selon laquelle il "fonctionne juste" pour la grande majorité des appels exécutables externes

Limitations actuellement inévitables:

  • Remarque: Ces limitations techniques viennent du fait que ie est implémenté en tant que _fonction_ (une correction appropriée dans le moteur lui-même n'aurait _pas__ ces problèmes):

    • Alors que $LASTEXITCODE est correctement défini sur le code de sortie du processus, $? finit toujours par $true - le code utilisateur ne peut actuellement pas définir $? explicitement, bien que l'ajout de cette capacité a été allumé en vert - voir https://github.com/PowerShell/PowerShell/issues/10917#issuecomment -550550490. Malheureusement, cela signifie que vous ne pouvez actuellement pas utiliser ie significative && et || , les && , les opérateurs de la chaîne de pipeline .
      Cependant, si _abandonner_ un script lors de la détection d'un code de sortie différent de zéro est souhaité, la fonction wrapper iee peut être utilisée.

    • -- en tant qu'argument est invariablement «mangé» par le classeur de paramètres PowerShell; passez-le simplement _twice_ afin de passer -- à l'exécutable cible ( foo -- -- au lieu de foo -- ).

    • Un jeton sans guillemets avec , doit être entre guillemets afin de ne pas être interprété comme un tableau et passé comme plusieurs arguments; par exemple, passez 'a,b' plutôt que a,b ; de même, passez -foo:bar (quelque chose qui ressemble à un argument PowerShell nommé) comme '-foo:bar' (cela ne devrait pas être nécessaire, mais est dû à un bogue: # 6360); de même '-foo.bar must be passed as ' -foo.bar'` (un autre bogue, qui affecte également les appels directs aux exécutables externes: # 6291)

  • Je m'attends à ce que la fonction fonctionne de manière robuste dans PowerShell _Core_. En raison des changements dans _Windows PowerShell_ au fil du temps, il peut y avoir des cas marginaux qui ne sont pas gérés correctement, même si je n'en connais que deux:

    • L'adaptation des guillemets partiels pour les CLI de style msiexec ne peut pas être appliquée dans les versions 3 et 4, car ces versions enveloppent l'argument entier dans un ensemble supplémentaire de guillemets doubles; cela fonctionne cependant dans la v5.1.

    • "" -escaping est utilisé par défaut pour contourner les problèmes, mais dans les cas où \" est nécessaire (PowerShell CLI, Perl, Ruby), un jeton tel que 3" of snow est passé par erreur comme arguments _3_, car toutes les versions de Windows PowerShell négligent de mettre un tel argument entre guillemets; cela semble se produire pour les arguments avec des caractères " non initiaux qui ne sont pas précédés d'un caractère espace.


Exemples, avec la sortie de PowerShell Core 7.1.0-preview.5 sur Windows 10:

Remarque: La fonction dbea ( Debug-ExecutableArguments ) est utilisée pour illustrer la manière dont les arguments seraient reçus par des exécutables / fichiers batch externes.

Argument actuel, cassé passant:

  • Appel d'une application conforme aux conventions (une application console .NET utilisée par dbea dans les coulisses par défaut):
# Note the missing 2nd argument and the effective loss of embedded double quotes,
# due to the embedded " chars. not having been escaped.
PS> dbea -- 'a&b' '' '{ "foo": "bar" }'

2 argument(s) received (enclosed in <...> for delineation):

  <a&b>
  <{ foo: bar }>

Command line (helper executable omitted):

  a&b  "{ "foo": "bar" }"
  • Appel d'un fichier batch:

Notez l'utilisation de -UseBatchFile pour que dbea transmette les arguments à un assistant _batch file_ à la place.

# Note that only *part of the first argument* is passed and that the `&` is interpreted as cmd.exe's
# statement separator, causing `b` to be run as a command (which fails).
PS> dbea -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

1 argument(s) received (enclosed in <...> for delineation):

  <a>

'b' is not recognized as an internal or external command,
operable program or batch file.
  • Appel d'une CLI msiexec -style, cmdkey.exe :
# The call fails, because `cmdkey.exe` requires the password argument to 
# to be quoted exactly as `/password:"bar none"` (double-quoting of the option value only), 
# whereas PowerShell - justifiably - passes `"/password:bar none"` (double-quoting of the whole argument).
PS> cmdkey.exe /generic:foo /user:foo /password:'bar none'

The command line parameters are incorrect.

Résoudre le problème avec ie :

Notez l'utilisation de -ie dans les appels dbea , ce qui provoque l'utilisation de ie pour les appels.

  • Appel d'une application conforme aux conventions (une application console .NET utilisée par dbea en coulisses par défaut):
# OK
# Note that the empty 2nd argument is correctly passed, and that \" is used for embedded "-escaping.
PS> dbea -ie -- 'a&b' '' '{ "foo": "bar" }'

3 argument(s) received (enclosed in <...> for delineation):

  <a&b>
  <>
  <{ "foo": "bar" }>

Command line (helper executable omitted):

  a&b "" "{ \"foo\": \"bar\" }"
  • Appel d'un fichier batch:
# OK
# - `a&b` was enclosed in "...", due to the presence of metacharacter `&`
# - "" is used for escaping of embedded " chars.
# Note that `echo %1`, for instance, prints the argument exactly as passed on the command line, including quoting.
# `echo %~1` strips the surrounding double quotes, but embedded escaped ones still print as "".
# However, if you pass these arguments (`%*`) through to convention-compliant CLIs, they are parsed correctly.
PS> dbea -ie -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

3 argument(s) received (enclosed in <...> for delineation):

  <"a&b">
  <"">
  <"{ ""foo"": ""bar"" }">
  • Appel d'une CLI msiexec -style, cmdkey.exe :
# The call now succeeds, because `ie` ensure the value-only double-quoting that cmdkey.exe requires.
# (Use `cmdkey /del:foo` to remove the credentials again.)
PS> ie cmdkey.exe /generic:foo /user:foo /password:'bar none'

CMDKEY: Credential added successfully.

Pour montrer que les guillemets doubles de valeur uniquement ont été appliqués dans la ligne de commande réelle, via dbea :

PS> dbea -ie -- cmdkey.exe /generic:foo /user:foo /password:'bar none'

  <cmdkey.exe>
  </generic:foo>
  </user:foo>
  </password:bar none>

Command line (helper executable omitted):

  cmdkey.exe /generic:foo /user:foo /password:"bar none"

Le code suivant provoque une perte de données:

{ PARAM($A) $A } | OUT-FILE A.PS1
PWSH A.PS1 -A:(1,2)

1

@JamesWTruher a proposé un correctif et valide s'il répond aux préoccupations soulevées dans ce problème

Y a-t-il une demande d'extraction de ce correctif proposé? Ce serait bien si nous pouvions commenter le PR. Parce que réparer cela n'a jamais été la partie compliquée de l'OMI. La partie compliquée était de savoir comment gérer la compatibilité descendante. Et ce serait bien, si nous pouvions voir comment le correctif proposé gère cela ...

C'est bon à entendre, @ SteveL-MSFT - un correctif pour _tous_ les problèmes discutés ici? Pour la v7.1, après tout? Et j'appuie la demande de

@ yecril71pl , c'est une bonne trouvaille, bien que celle-ci soit vraiment liée à l'analyse de PowerShell (qui a _quelque_ casse spéciale pour les exécutables externes), pas à la façon dont la ligne de commande native est construite _after_ parsing (où les problèmes précédemment discutés viennent de).

Un repro plus succinct du problème, sous Unix:

PS> printf '<%s>\n' -a:(1,2,3)
<-a:1>
<2>
<3>

Autrement dit, seul l'élément du tableau _first_ était directement attaché à -a: , les autres étaient passés en tant qu'arguments séparés.

Il existe des problèmes liés avec des arguments qui ressemblent à des paramètres PowerShell, mais qui ne le sont pas:

Il existe un problème lié qui affecte uniquement les appels aux commandes _PowerShell_ qui utilisent $args / @args : # 6360

  • & { $args.Count; $args } -foo:bar rapporte 2, '-foo:', 'bar'

Il y a aussi # 6291, qui affecte à la fois les commandes PowerShell et les exécutables externes (notez le . ).

  • & { $args.Count; $args } -foo.bar rapporte 2, '-foo', '.bar'

Une chose à noter est que (...) dans le cadre d'un bareword normalement des résultats dans (...) la sortie « dans son ensemble devient un argument de _separate_, donc le fait que le premier élément _is_ fixé dans le printf ci-dessus est la preuve d'une casse spéciale pour les appels exécutables externes; par exemple,
& { $args.Count; $args.ForEach({ "$_" }) } foo('bar', 'baz') donne 2, 'foo', 'bar baz' , le deuxième argument étant la stringification du tableau 'bar', 'baz' .

Lorsque PowerShell doit passer -A:(1,2) pour un exécutable externe, il comprend que -A: est une chaîne et (1,2) est un tableau qui doit être rassemblé en '1 2'. PowerShell essaie de conserver la syntaxe d'origine de l'appel, donc, en mettant tout cela ensemble, nous obtenons '-A: 1 2', alors que le résultat correct serait '-A: "1 2"'. Cela ressemble à une omission triviale dans le code de rassemblement pour moi.

Je ne dirais pas que le problème spécifique de @ yecril71pl est lié à l'analyse (bien que je sois d'accord, qu'il n'a rien à voir avec le problème de "conversion de tableau en ligne de commande", qui est discuté dans ce numéro).

Lorsque PowerShell doit passer -A: (1,2) pour un exécutable externe, il comprend que -A: est une chaîne et (1,2) est un tableau

Presque: -A: est un paramètre nommé et le tableau est la valeur de ce paramètre (pour tester cela: supprimez le - devant et vous voyez, qu'il est cité différemment). Mais le problème n'est pas que le tableau est incorrectement converti en chaîne - le problème est que pour les exécutables natifs, les arguments sont (presque) toujours splattés, même en utilisant $ et non @ et même si le tableau provient d'une expression telle que (1,2) .

Test par exemple printf '<%s>\n' -a:('a b',2) : Comme la chaîne a b contient un espace, elle est correctement citée, mais comme 2 est dans l'élément suivant du tableau et le tableau est splatté, 2 ne fait pas partie du premier argument.


La magie se produit dans NativeCommandParameterBinder.cs

À la ligne 170, PowerShell essaie d'obtenir un énumérateur pour la valeur d'argument actuelle.

IEnumerator list = LanguagePrimitives.GetEnumerator(obj);

Si list n'est pas null , powershell ajoute chaque élément de la liste (éventuellement entre guillemets s'il contient des espaces) à la lpCommandLine.

Les éléments sont séparés par des espaces ( ligne 449 ) par défaut. La seule exception est si le tableau était un littéral
(comme dans printf '<%s>\n' -a:1,2 ).
Ensuite, powershell essaie d'utiliser le même séparateur dans la lpCommandLine, qui a été utilisé dans la ligne de script.

J'attends un PR quand vous êtes prêt. Si cela devient 7.1, nous le prendrons, sinon ce sera 7.2. La rétrocompatibilité est quelque chose qu'il aborde. Peut-être que ce qui aiderait à écrire des tests Pester (en utilisant testexe -echoargs qui peut être construit en utilisant publish-pstesttools de build.psm1).

J'attends un PR quand vous êtes prêt

C'est exactement ce que je voulais éviter - veuillez montrer le code qui n'est pas prêt (marquer PR comme travail en cours).

Ou du moins commenter, ce qu'il veut faire.

Ce serait bien si nous pouvions discuter de la manière dont il souhaite gérer la compatibilité ascendante.

@TSlivede , notez que puisque le PowerShell _CLI_ est appelé - un exécutable externe - -A:(1,2) est analysé _avant_ sachant que ce jeton finira par se lier au paramètre _named_ -A - qu'un tel paramètre entrera éventuellement en jeu est accessoire au problème.

@ yecril71pl :

Il comprend que -A: est une chaîne

Non, il fait l'objet d'une casse spéciale lors de l'analyse, car il se produit _ ressembler à_ un paramètre PowerShell.

Cette casse spéciale se produit pour les appels aux commandes PowerShell qui utilisent également $args (par opposition aux paramètres déclarés réels qui sont liés), mais cela se produit _différemment_ pour les exécutables externes (ce qui est normalement un argument séparé reste attaché, mais dans le cas d'une collection uniquement son élément _first_).

Vous pouvez en fait désactiver cette casse spéciale si vous passez -- au préalable, mais, bien sûr, cela passera également -- , qui n'est supprimé que pour les appels aux commandes _PowerShell_:

PS> printf '<%s>\n' -- -a:(1,2,3)
<-->   # !! not removed
<-a:>
<1>    # array elements are *all* now passed as indiv. arguments, because (...) output is separate (splatted) argument
<2>
<3>

Si l'argument _n'est pas_ ressemble à un paramètre PowerShell, le comportement habituel (la sortie de (...) devient un argument séparé) intervient même pour les exécutables externes (avec le comportement habituel d'un _array_ étant splatté, c'est-à-dire transformé en arguments individuels dans le cas de l'exécutable externe).

# Note: No "-" before "a:" -> output from `(...)` becomes separate argument(s)
PS> printf '<%s>\n' a:(1,2,3)
<a:>
<1>
<2>
<3>

Appliquer ce comportement de manière cohérente aurait du sens - une expression (...) faisant partie d'un mot nu devrait toujours devenir un argument séparé - voir # 13488.

Pour passer un seul argument '-A:1 2 3' , avec le tableau _stringified_, utilisez une (n implicite) _expandable string_, auquel cas vous avez besoin de $(...) plutôt que (...) _and_ - étonnamment - actuellement aussi "..." :

PS> printf '<%s>\n' "-a:$(1,2,3)"  # quotes shouldn't be needed; `-a:"$(1,2,3)"` would work too.
<a:1 2 3> # SINGLE argument with stringified array.

Vous ne devriez pas aussi avoir besoin de "..." dans ce cas - c'est encore une fois nécessaire en raison des anomalies relatives aux jetons qui ressemblent à des paramètres (qui s'appliquent en général aux appels _both_ PowerShell et exécutables externes - voir # 13489); s'ils ne le font pas, vous n'avez pas besoin de citer:

# Anomaly due to looking like a parameter: $(...) output becomes separate argument
PS> Write-Output -- -a:$(1,2,3)
-a:
1
2
3

# Otherwise (note the absence of "-"): no quoting needed; treated implicitly like 
# "a:$(1,2,3)"
PS> Write-Output -- a:$(1,2,3)
a:1 2 3  # SINGLE argument with stringified array.

Le monde des jetons composés en mode argument est complexe, avec plusieurs incohérences - voir # 6467.

@ SteveL-MSFT

Dans sa forme actuelle, testexe -echoArgs imprime uniquement les arguments individuels analysés par l'exécutable .NET Core à partir de la ligne de commande brute (sous Windows), et non de la ligne de commande brute elle-même.

Il ne peut donc pas tester les accommodements avec des citations sélectives pour les fichiers batch et les CLI de style msiexec - en supposant que de tels accommodements seront implémentés, ce que je recommande fortement; par exemple, vous ne pourrez pas vérifier que PROP='foo bar' été passé comme PROP="foo bar" , avec des guillemets doubles juste autour de la partie valeur.

Cependant, pour imprimer la ligne de commande brute, testexe ne doit pas être un exécutable .NET _Core_, car .NET Core _ crée une ligne de commande hypothétique_ qui utilise toujours \" -escaping pour " intégré "" été utilisé, et ne reflète généralement pas fidèlement les arguments entre guillemets doubles et ceux qui ne l'étaient pas - pour le fond, voir https://github.com/ dotnet / runtime / issues / 11305 # issuecomment -674554010.

Seul un exécutable compilé .NET _Framework_ montre la vraie ligne de commande dans Environment.CommandLine , donc testexe devrait être compilé de cette façon (et modifié pour (éventuellement) imprimer la ligne de commande brute).

Pour tester les accommodements pour les fichiers batch, un fichier _batch_ de test séparé est nécessaire, pour vérifier que 'a&b' est passé comme "a&b" et 'a"b' comme "a""b" , pour exemple.

La compilation

Sinon, pouvons-nous simplement nous fier à un simple script bash pour Linux / macOS pour émettre les arguments?

#!/bin/bash
for i; do
   echo $i
done

Et sur Windows quelque chose de similaire avec un fichier batch.

Que diriez-vous d'utiliser node avec un script .js?

console.log(process.execArgv.join('\n') ou toute autre chaîne qui vous gère
voulez-vous faire pour que la sortie soit belle?

@cspotcode , pour obtenir la ligne de commande brute, nous avons besoin d'un appel WinAPI.

@ SteveL-MSFT:

Sous Windows , vous pouvez déléguer la compilation à _Windows PowerShell_ via sa CLI, ce que je fais dans dbea ; voici un exemple simple qui produit un exécutable .NET _Framework_ qui fait écho à la ligne de commande brute (uniquement), ./rawcmdline.exe :

powershell.exe -noprofile -args ./rawcmdline.exe -c {

  param([string] $exePath)

  Add-Type -ErrorAction Stop -OutputType ConsoleApplication -OutputAssembly $exePath -TypeDefinition @'
using System;
static class ConsoleApp {
  static void Main(string[] args) {
    Console.WriteLine(Environment.CommandLine);
  }
}
'@

}

Exemple d'appel:

PS> ./rawcmdline.exe --% "a&b" PROP="foo bar"
"C:\Users\jdoe\rawcmdline.exe"  "a&b" PROP="foo bar"

Quant à un _batch file_ qui fait écho à ses arguments, dbea crée également un à la demande .

Sous Unix , un simple script shell, comme indiqué dans votre commentaire, est en effet suffisant, et vous pouvez même utiliser un script ad hoc que vous passez à /bin/sh comme un _argument_ .

@ PowerShell / powershell-Committee en a discuté aujourd'hui, nous demandons à @JamesWTruher de mettre à jour son PR pour inclure également dans le cadre de sa fonctionnalité expérimentale pour sauter l'étape du processeur de commande natif qui reconstruit le tableau d'arguments en une chaîne et simplement passer cela au nouveau tableau args dans ProcessStartInfo (il y a un peu de code pour s'assurer que les noms et les valeurs des paramètres correspondent correctement). En outre, nous acceptons que nous puissions avoir besoin d'une liste d'autorisation pour les commandes connues de cas spéciaux qui échouent toujours avec le changement proposé et que c'est quelque chose qui peut être ajouté plus tard.

Pour ceux qui ne l'ont peut-être pas remarqué: le PR a été publié (en tant que WIP) et est déjà en discussion: https://github.com/PowerShell/PowerShell/pull/13482

PS, @ SteveL-MSFT, concernant l'obtention de la ligne de commande brute sur Windows: bien sûr, une alternative à la délégation de la compilation à Windows PowerShell / .NET _Framework_ consiste à améliorer l'application console .NET _Core_ existante pour en faire une P / Invoque l'appel à la fonction GetCommandLine() WinAPI, comme illustré ci-dessous.

using System;
using System.Runtime.InteropServices;

namespace demo
{
  static class ConsoleApp
  {
    [DllImport("kernel32.dll")]
    private static extern System.IntPtr GetCommandLineW();

    static void Main(string[] args)
    {
      Console.WriteLine("\n{0} argument(s) received (enclosed in <...> for delineation):\n", args.Length);
      for (int i = 0; i < args.Length; ++i)
      {
        Console.WriteLine("  <{0}>", args[i]);
      }

      // Windows only: print the raw command line.
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
      {
        Console.WriteLine("\nCommand line:\n\n  {0}\n", Marshal.PtrToStringUni(GetCommandLineW()));
      }

    }

  }

}

@ SteveL-MSFT

La rétrocompatibilité est quelque chose qu'il aborde.

Je pense que cela est implicite dans votre clarification ultérieure que ProcessStartInfo.ArgumentList (une _collection_ d'arguments _verbatim_ à utiliser tel quel sur Unix et traduit en une ligne de commande conforme à la convention MS C / C ++ sous Windows par .NET Core lui-même ) doit être utilisé, mais permettez-moi de le déclarer explicitement:

  • Résoudre correctement ce problème, une fois pour toutes, _précie toute concession à la compatibilité descendante_.

  • Le PR de @JamesWTruher est sur la bonne voie à ce jour, le seul problème semble être que les arguments vides ne sont toujours pas passés .

    • Une fois que cela est résolu, le correctif est terminé sur _Unix_ - mais il manque les aménagements importants pour les CLI sur _Windows_ (voir ci-dessous).

nous pouvons avoir besoin d'une liste d'autorisation pour les commandes connues de cas spéciaux qui échouent toujours avec la modification proposée et qui peut être ajoutée plus tard.

Je vous exhorte à ne pas remettre cela à plus tard .

Au lieu d'une _allowlist_ (casse spéciale pour des exécutables spécifiques), des règles générales simples peuvent régir les accommodements , affinées à partir de celles ci-dessus après quelques discussions supplémentaires avec @TSlivede .

Ces hébergements, qui sont _nécessaires sous Windows uniquement_:

Plus précisément, ce sont:

  • Tout comme sous Unix, ProcessStartInfo.ArgumentList est utilisé par défaut - sauf si _une ou les deux_ des conditions suivantes sont remplies, _dans ce cas, la ligne de commande du processus doit être construite manuellement_ (et assignée à ProcessStartInfo.Arguments comme actuellement):

    • Un fichier batch ( .cmd , .bat ) ou cmd.exe directement est appelé:
    • Dans ce cas, _embedded_ " sont échappés comme "" (plutôt que comme \" ), et les arguments sans espace contenant l'un des métacaractères cmd.exe sont également entre guillemets (normalement, seuls les arguments _avec des espaces_ sont entre guillemets): " & | < > ^ , ; - cela permettra aux appels aux fichiers batch de fonctionner de manière robuste, ce qui est important, car de nombreuses CLI de haut niveau utilisent des fichiers batch comme _entry points_.
    • Indépendamment (et éventuellement en plus), si au moins un argument qui correspond à l'expression régulière
      '^([/-]\w+[=:]|\w+=)(.*? .*)$' est présent, tous ces arguments doivent appliquer des guillemets doubles _partiaux_ autour de la partie _value uniquement_ (ce qui suit le : ou = )

      • Par exemple, msiexec.exe / msdeploy.exe et cmdkey.exe -style arguments vus textuellement par PowerShell comme
        FOO=bar baz et /foo:bar baz / -foo:bar baz seraient placés sur la ligne de commande du processus comme
        foo="bar baz" ou /foo:"bar baz" / -foo:"bar baz" rendant _toutes_ les CLI qui nécessitent ce style de citation heureux.
    • Les caractères verbatim \ dans les arguments doivent être traités conformément aux conventions MS C / C ++.

Ce qui n'est pas couvert par ces hébergements:

  • msiexec.exe (et probablement msdeploye.exe aussi) prend en charge _uniquement_ "" -échappement de _embedded_ " caractères. , que les règles ci-dessus ne couvriraient _pas_ - sauf si vous appelez via un fichier batch ou cmd /c .

    • Cela devrait être assez rare pour commencer (par exemple,
      msiexec.exe /i example.msi PROPERTY="Nat ""King"" Cole" ), mais il est probablement rendu encore plus rare du fait que les invocations misexec sont généralement _ rendues synchrones_ pour attendre la fin d'une installation, auquel cas vous pouvez éviter le problème dans l'un des deux voies:
    • cmd /c start /wait msiexec /i example.msi PROPERTY='Nat "King' Cole' - s'appuie sur des appels à cmd.exe (puis) ​​déclenchant "" -escaping
    • Start-Process -Wait msiexec '/i example.msi PROPERTY="Nat ""King"" Cole"' - s'appuyer sur le paramètre -ArgumentList ( -Args ) en passant un seul argument de chaîne textuellement comme ligne de commande de processus (même si ce n'est pas ainsi que ce paramètre est censé fonctionner - voir # 5576).
  • Toute autre CLI non conventionnelle pour laquelle les aménagements ci-dessus ne sont pas suffisants - je n'en ai personnellement connaissance.

À la fin de la journée, il y a toujours une solution de contournement: appelez via cmd /c , ou, pour les applications non-console, via Start-Process , ou utilisez --% ; si et quand nous fournissons une cmdlet ins ( Invoke-NativeShell ), c'est une autre option; une cmdlet dbea ( Debug-ExecutableArguments avec des capacités similaires à echoArgs.exe , mais à la demande également pour les fichiers batch, aiderait également à _diagnostiquer_ les problèmes.


En ce qui concerne le chemin vers un changement radical par rapport à l'opt-in:

  • Est-ce que l'implémentation de cela comme une fonctionnalité expérimentale signifie que si un intérêt suffisant est montré, cela deviendra le comportement _default_ et représentera donc un changement de rupture (non trivial)?

  • Pouvez-vous vous assurer que cette fonctionnalité expérimentale est largement diffusée, compte tenu de son importance?

    • Une préoccupation générale que j'ai à propos des fonctionnalités expérimentales est que leur utilisation peut souvent être _unwitting_ dans les versions d'aperçu, étant donné que _toutes_ les fonctionnalités expérimentales sont activées par défaut. Nous voulons absolument que les gens connaissent et exercent délibérément cette fonctionnalité.
Cette page vous a été utile?
0 / 5 - 0 notes