Appel Powershell msbuild avec guillemets imbriqués

utilisant Powershell et Psake pour créer le paquet et le déploiement d'une solution visual studio. Essayer de déployer un projet de base de données en utilisant msbuild - qui fonctionne correctement en utilisant msdos visual studio ligne de commande

   msbuild /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"

le même appel de méthode entraîne une erreur lorsqu'il est appelé de powershell

& msbuild /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"

concernant les espaces - ne peut pas comprendre comment répliquer cet appel dans powershell - Echantillon connexion de la base de données Data Source=.SQL2008; catalogue Initial = DocumentExecution;Integrated Security= True;

25
demandé sur JohnF 2011-06-03 12:17:48

7 réponses

Version Courte

Comment passer un argument contenant des citations dans une commande native de PowerShell?

  • utilisez des guillemets simples au lieu de guillemets doubles dans la chaîne d'arguments:

       "/p:Target='Data Source=(local)\SQL;Integrated Security=True'"

    /p:Target='Data Source=(local)\SQL;Integrated Security=True'

  • utilisez backslash-escapade pour les guillemets doubles dans l'argument ficelle :

       '/p:Target=\"Data Source=(local)\SQL;Integrated Security=True\"'

    /p:Target="Data Source=(local)\SQL;Integrated Security=True"

si les guillemets inclus ne sont utilisés que pour traiter l'argument comme une chaîne simple, plutôt que d'être une partie requise du paramètre, alors la suivante peut être utilisée:

  • citer toute la chaîne d'arguments, au lieu de l'intégrer citations dans l'argument:

       '/p:Target=Data Source=(local)\SQL;Integrated Security=True'

    /p:Target=Data Source=(local)\SQL;Integrated Security=True

  • Escape all PowerShell special characters with backticks (cela ne peut être fait que comme un argument en ligne):

       /p:Target=`"Data Source=`(local`)\SQL`;Integrated Security=True`"

    ou /p:Target=Data` Source=`(local`)\SQL`;Integrated` Security=True

    /p:Target=Data Source=(local)\SQL;Integrated Security=True



Exemple de ligne de commande complète (utilisant la deuxième alternative):

PS> [string[]]$arguments = @(
  '/target:Deploy',
  '/p:UseSandboxSettings=False',
  '/p:TargetDatabase=UpdatedTargetDatabase',
  '/p:TargetConnectionString=\"Data Source=(local)\SQL;Integrate Security=True\"',
  'C:\program files\MyProjectName.dbproj'
)
PS> ./echoargs $arguments
Arg 0 is </target:Deploy>
Arg 1 is </p:UseSandboxSettings=False>
Arg 2 is </p:TargetDatabase=UpdatedTargetDatabase>
Arg 3 is </p:TargetConnectionString="Data Source=(local)\SQL;Integrate Security=True">
Arg 4 is <C:\program files\MyProjectName.dbproj>




Version Longue

appeler les commandes natives est quelque chose qui se produit un peu quand les gens se déplacent entre le système cmd legacy et PowerShell (presque autant que les "paramètres de séparation avec des virgules" gotcha ;).

j'ai essayé et j'ai résumé tout ce que je sais Sur le sujet de l'invocation de commande dans PowerShell (v2 et v3) ici, avec tous les exemples et les références que je peux rassembler.



1) Appeler Les Commandes Natives Directement

1.1) dans son plus simple, pour un exécutable situé dans le chemin d'environnement, la commande peut être appelée directement , comme vous appelleriez un cmdlet PowerShell.

PS> Get-ItemProperty echoargs.exe -Name IsReadOnly
...
IsReadOnly   : True    

PS> attrib echoargs.exe
A    R       C:\Users\Emperor XLII\EchoArgs.exe



1.2) en dehors du chemin d'environnement, pour les commandes dans un répertoire spécifique (y compris le répertoire courant), le chemin complet ou relatif à la commande peut être utilisé . L'idée est que l'opérateur déclare explicitement "je veux invoquer ce "fichier" 1519370920, plutôt que de laisser un arbitraire fichier qui s'est avéré avoir le même nom exécuter à sa place ( voir cette question pour plus d'informations sur la sécurité PowerShell ). À défaut d'utiliser un chemin d'accès lorsqu'il est nécessaire d'entraîner un "terme n'est pas reconnu" une erreur.

PS> echoargs arg
The term 'echoargs' is not recognized as the name of a cmdlet, function, script file,
 or operable program...

PS> ./echoargs arg
Arg 0 is <arg>

PS> C:\Windows\system32\attrib.exe echoargs.exe
A    R       C:\Users\Emperor XLII\EchoArgs.exe



1.3) si un chemin contient des caractères spéciaux, le caractère Call operator ou escape peut être utilisé . Par exemple, un exécutable commençant par un nombre, ou localisé dans un répertoire contenant un espace.

PS> $env:Path
...;C:\tools\;...

PS> Copy-Item EchoArgs.exe C:\toolspecialCharacter.exe
PS> 5pecialCharacter.exe special character
Bad numeric constant: 5.

PS> & 5pecialCharacter.exe special character
Arg 0 is <special>
Arg 1 is <character>

PS> `5pecialCharacter.exe escaped` character
Arg 0 is <escaped character>


PS> C:\Users\Emperor XLII\EchoArgs.exe path with spaces
The term 'C:\Users\Emperor' is not recognized as the name of a cmdlet, function,
 script file, or operable program...

PS> & 'C:\Users\Emperor XLII\EchoArgs.exe' path with spaces
Arg 0 is <path>
Arg 1 is <with>
Arg 2 is <spaces>

PS> C:\Users\Emperor` XLII\EchoArgs.exe escaped` path with` spaces
Arg 0 is <escaped path>
Arg 1 is <with spaces>



2) Appeler Les Commandes Natives Indirectement

2.1) lorsque vous n'tapez pas une commande de façon interactive, mais que vous avez le chemin stocké dans une variable, , l'opérateur d'appel peut également être utilisé pour invoquer la commande nommée dans une variable .

PS> $command = 'C:\Users\Emperor XLII\EchoArgs.exe'
PS> $command arg
Unexpected token 'arg' in expression or statement.

PS> & $command arg
Arg 0 is <arg>



2.2) les arguments passés à une commande peuvent aussi être stockés dans des variables. les Arguments dans les variables peuvent être passés individuellement, ou dans un tableau. pour les variables contenant des espaces, PowerShell s'échappera automatiquement des espaces de sorte que la commande native le voit comme un seul argument. (Notez que l'opérateur d'appel traite la première valeur comme la commande et les valeurs restantes comme des arguments; les arguments ne devraient pas être combiné avec la variable de commande.)

PS> $singleArg = 'single arg'
PS> $mushedCommand = "$command $singleArg"
PS> $mushedCommand
C:\Users\Emperor XLII\EchoArgs.exe single arg

PS> & $mushedCommand
The term 'C:\Users\Emperor XLII\EchoArgs.exe single arg' is not recognized as the
 name of a cmdlet, function, script file, or operable program...

PS> & $command $singleArg
Arg 0 is <single arg>

PS> $multipleArgs = 'multiple','args'
PS> & $command $multipleArgs
Arg 0 is <multiple>
Arg 1 is <args>



2.3) le format de tableau est particulièrement utile pour construire une liste dynamique d'arguments pour une commande native. pour que chaque argument soit reconnu comme un paramètre distinct, il est important que les arguments soient stockés dans une variable de tableau, et pas simplement mungés ensemble dans une chaîne. (Il est à noter que l'abréviation courante $args est une variable PowerShell, qui peut faire en sorte que les valeurs qu'il contient soient écrasées; à la place, il est préférable d'utiliser un nom descriptif comme $msbuildArgs pour éviter le conflit de noms.)

PS> $mungedArguments = 'initial argument'
PS> $mungedArguments += 'second argument'
PS> $mungedArguments += $(if( $someVariable ) { 'dynamic A' } else { 'dynamic B' })
PS> ./echoargs $mungedArguments
Arg 0 is <initial argumentsecond argumentdynamic B>

PS> $arrayArguments = @('initial argument')
PS> $arrayArguments += 'second argument'
PS> $arrayArguments += $(if( $someVariable ) { 'dynamic A' } else { 'dynamic B' })
PS> ./echoargs $arrayArguments
Arg 0 is <initial argument>
Arg 1 is <second argument>
Arg 2 is <dynamic B>



2.4) en outre, pour les scripts, les fonctions, les cmdlets, etc., PowerShell v2 peut envoyer des arguments nommés contenus dans un hashtable en utilisant une technique appelée "splatting", sans avoir à se soucier de l'ordre des paramètres. Cela ne fonctionne pas avec les commandes natives, qui ne participent pas au Modèle D'objet PowerShell et ne peuvent traiter que des valeurs de chaîne.

PS> $cmdletArgs = @{ Path = 'EchoArgs.exe'; Name = 'IsReadOnly' }
PS> $cmdlet = 'Get-ItemProperty'
PS> & $cmdlet $cmdletArgs     # hashtable object passed to cmdlet
Cannot find path 'C:\Users\Emperor XLII\System.Collections.Hashtable'...

PS> & $cmdlet @cmdletArgs     # hashtable values passed to cmdlet
...
IsReadOnly   : True

PS> ./echoargs @cmdletArgs
Arg 0 is <Name>
Arg 1 is <IsReadOnly>
Arg 2 is <Path>
Arg 3 is <EchoArgs.exe>



3) Appeler Des Commandes Natives Avec Des Arguments Compliqués

3.1) pour les arguments simples, l'échappement automatique utilisé pour les commandes natives est généralement suffisant. Toutefois, pour les parenthèses, signes dollar, espaces, et tel, les caractères utilisés par PowerShell doivent être échappés pour être envoyés tels quels aux commandes natives , sans qu'ils soient interprétés par l'analyseur. Cela peut être fait avec le caractère d'échappement backtick, ` , ou en mettant l'argument dans une chaîne de citation simple.

PS> ./echoargs money=.00
Arg 0 is <money=.00>

PS> ./echoargs money=`.00
Arg 0 is <money=.00>


PS> ./echoargs value=(spaces and parenthesis)
The term 'spaces' is not recognized as the name of a cmdlet, function, script file,
 or operable program...

PS> ./echoargs 'value=(spaces and parenthesis)'
Arg 0 is <value=(spaces and parenthesis)>



3.2) malheureusement, ce n'est pas aussi simple lorsqu'il s'agit de guillemets. Dans le cadre du traitement des arguments pour natif commandes, le processeur PowerShell tente de normaliser toutes les guillemets dans un argument de sorte que le contenu de l'argument, sans guillemets, soit passé comme une valeur unique à la commande native. Le traitement des paramètres de commande natifs se produit comme une étape séparée après l'analyse, donc l'échappement normal ne fonctionnera pas pour les guillemets doubles; seules les guillemets simples échappés, ou les guillemets doubles échappés peuvent être utilisés .

PS> ./echoargs value="double quotes"
Arg 0 is <value=double quotes>

PS> ./echoargs 'value="string double quotes"'
Arg 0 is <value=string>
Arg 1 is <double>
Arg 2 is <quotes>

PS> ./echoargs value=`"escaped double quotes`"
Arg 0 is <value=escaped double quotes>

PS> ./echoargs 'value=\"backslash escaped double quotes\"'
Arg 0 is <value="backslash escaped double quotes">


PS> ./echoargs value='single quotes'
Arg 0 is <value=single quotes>

PS> ./echoargs "value='string single quotes'"
Arg 0 is <value='string single quotes'>

PS> ./echoargs value=`'escaped` single` quotes`'
Arg 0 is <value='escaped single quotes'>



3.3) PowerShell v3 a ajouté un nouveau symbole de stop-parsing --% (voir about_Parsing ). Lorsqu'il est utilisé avant les arguments compliqués, --% passera les arguments comme-est sans parsing ou expansion variable, à l'exception de cmd-like %ENVIRONMENT_VARIABLE% valeurs .

PS> ./echoargs User:"$env:UserName" "Hash"#555
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash>

PS> ./echoargs User: "$env:UserName" --% "Hash"#555
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash#555>

PS> ./echoargs --% User: "%USERNAME%" "Hash"#555
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash#555>

cela peut également être utilisé pour supprimer une chaîne de caractères représentant des arguments multiples, en passant le symbole stop-parsing dans une corde (bien que la meilleure pratique soit de ne pas étouffer les arguments en premier lieu).

PS> $user = 'User:"%USERNAME%"'
PS> $hash = 'Hash#' + $hashNumber
PS> $mungedArguments = $user,$hash -join ' '
PS> ./echoargs $mungedArguments
Arg 0 is <User:%USERNAME% Hash#555>

PS> ./echoargs --% $mungedArguments
Arg 0 is <$mungedArguments>

PS> ./echoargs '--%' $mungedArguments
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash#555>



4) Débogage Des Commandes Natives

il y a deux outils clés pour déboguer les arguments que PowerShell passe aux commandes natives.

4.1) le premier est EchoArgs.exe , une application de console du PowerShell Community Extensions qui réécrit simplement les arguments qui lui sont passés entre les crochets d'angle (comme indiqué dans les exemples ci-dessus).

4.2) le second est Trace-Command , un cmdlet qui peut montrer de nombreux détails sur la façon dont PowerShell traite un pipeline. En particulier, l' NativeCommandParameterBinder trace source montrera ce que PowerShell reçoit et transmet à une commande native.

PS> Trace-Command *NativeCommand* { ./echoargs value="double quotes" } -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  "value=double quotes"
DEBUG: NativeCommandParameterBinder : Argument 0: value=double quotes

PS> Trace-Command *NativeCommand* { ./echoargs 'value="double quotes"' } -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  "value="double quotes""
DEBUG: NativeCommandParameterBinder : Argument 0: value=double
DEBUG: NativeCommandParameterBinder : Argument 1: quotes

PS> Trace-Command *NativeCommand* { ./echoargs value=`"double quotes`" } -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  value="double quotes"
DEBUG: NativeCommandParameterBinder : Argument 0: value=double quotes

PS> Trace-Command *NativeCommand* { ./echoargs 'value=\"double quotes\"' } -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  "value=\"double quotes\""
DEBUG: NativeCommandParameterBinder : Argument 0: value="double quotes"



Autres Ressources

Articles

Questions

56
répondu Emperor XLII 2017-05-23 12:25:13

tout cela pourrait être rendu beaucoup plus facile si vous utilisiez le cmdlet Start-Process avec le paramètre-ArgumentList. Je suis surpris que cela n'ait pas encore été mentionné.

exemple:

Start-Process -FilePath msbuild.exe -ArgumentList '/target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"';

Voici une méthode que j'aime utiliser un peu mieux, qui permet la substitution de variables:

$ConnectionString = 'aConnectionWithSpacesAndSemiColons';
$DatabaseProjectPath = 'aDatabaseProjectPathWithSpaces';
$MsbuildArguments = '/target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="{0}" "{1}"' -f $ConnectionString, $DatabaseProjectPath;
Start-Process -FilePath msbuild.exe -ArgumentList $MsbuildArguments;
11
répondu Trevor Sullivan 2012-03-03 05:07:07

mettez le paramètre entier en guillemets simples:

& msbuild /target:Deploy /p:UseSandboxSettings=false '/p:TargetConnectionString="aConnectionWithSpacesAndSemiColons"' "aDatabaseProjectPathWithSpaces"

le niveau supplémentaire de citation signifie que PSH ne traite pas le contenu avec les règles de PSH. (Les guillemets à l'intérieur de la chaîne de caractères doivent être doublés-c'est le seul type d'échappatoire dans une chaîne de caractères simple PSH).

4
répondu Richard 2011-06-03 08:26:42

@Richard - Tests cela génère une erreur en disant qu'aucun fichier du projet est fourni. J'ai passé ceci dans echoargs pscx helper pour montrer quelques exemples plus détaillés.

  1. avec des marques de quaatation simples enveloppant la chaîne de connexion target-Powershell évalue chaque espace dans la chaîne de connexion comme une nouvelle ligne:

    & echoargs /target:Deploy /p:UseSandboxSettings=false    /p:TargetDatabase=UpdatedTargetDatabase /p:TargetConnectionString='"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"' "C:\program files\MyProjectName.dbproj"
    
    Arg 0 is </target:Deploy>
    Arg 1 is </p:UseSandboxSettings=false>
    Arg 2 is </p:TargetDatabase=UpdatedTargetDatabase>
    Arg 3 is </p:TargetConnectionString=Data>
    Arg 4 is <Source=(local)\SQLEXPRESS;Integrated>
    Arg 5 is <Security=True;Pooling=False>
    Arg 6 is <C:\program files\MyProjectName.dbproj>
    
  2. séparation de chaque paramètre avec des bâtons arrière recréer le problème initial = pas de guillemets autour de la chaîne de connexion:

    & echoargs /target:Deploy `
    /p:UseSandboxSettings=false `
    

    c /p:TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False" "C:\program files\MyProjectName.dbproj "

    Arg 0 is </target:Deploy>
    Arg 1 is </p:UseSandboxSettings=false>
    Arg 2 is </p:TargetDatabase=UpdatedTargetDatabase>
    Arg 3 is </p:TargetConnectionString=Data Source=(local)\SQLEXPRESS;Integrated Se
    curity=True;Pooling=False>
    Arg 4 is <C:\program files\MyProjectName.dbproj>
    
  3. ajouter des tirets à des guillemets se comporte comme dans l'exemple 1:

    & echoargs /target:Deploy `
    /p:UseSandboxSettings=false `
    /p:TargetDatabase=UpdatedTargetDatabase `
    "/p:TargetConnectionString=`"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"`"  `
    "C:\program files\MyProjectName.dbproj"
    
  4. en utilisant l'opérateur @ pour essayer de diviser les paramètres ignore toujours les guillemets:

    $args = @('/target:Deploy','/p:UseSandboxSettings=false','     /p:TargetDatabase=UpdatedTargetDatabase','/p:TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"','C:\program files\MyProjectName.dbproj'); $args 
    
    /target:Deploy
    /p:UseSandboxSettings=false
    /p:TargetDatabase=UpdatedTargetDatabase
    /p:TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated           Security=True;Pooling=False"
    C:\program files\MyProjectName.dbproj
    
    & echoargs $args
    
  5. Backticks pour échapper à la connectionstring à l'aide de la ligne de séparateurs - même résultat que l'exemple 1:

    & echoargs /target:Deploy `
    /p:UseSandboxSettings=false `
    /p:TargetDatabase=UpdatedTargetDatabase `
    "/p:TargetConnectionString=`"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"`" `
    "C:\program files\MyProjectName.dbproj"
    
1
répondu JohnF 2011-06-04 01:00:23

votre problème est que PowerShell n'échappe pas aux citations quand il les passe aux applications en ligne de commande. J'ai vu ça moi-même et j'ai pensé que PowerShell mangeait les citations. Viens de le faire.

msbuild /target:Deploy /p:UseSandboxSettings=false /p:TargetDatabase=UpdatedTargetDatabase '/p:TargetConnectionString=\"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False\"' "C:\program files\MyProjectName.dbproj"
1
répondu JasonMArcher 2011-06-24 15:58:09

grâce à la réponse de JohnF, j'ai finalement pu comprendre celle-ci.

echoargs /target:clean`;build`;deploy /p:UseSandboxSettings=false /p:TargetConnectionString=`"Data
Source=.`;Integrated Security=True`;Pooling=False`" .\MyProj.dbproj
Arg 0 is </target:clean;build;deploy>
Arg 1 is </p:UseSandboxSettings=false>
Arg 2 is </p:TargetConnectionString=Data Source=.;Integrated Security=True;Pooling=False>
Arg 3 is <.\MyProj.dbproj>

, mais backticks devant les guillemets ET les points-virgules. Rien de moins (ou plus!) le visser en place.

1
répondu moswald 2011-12-08 22:34:21

il est mentionné dans les Articles de cette réponse , mais avec PowerShell 3 vous pouvez utiliser --% pour arrêter la parsing normal PowerShell does.

msbuild --% /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"
1
répondu Lars Truijens 2017-05-23 11:53:59