Вызов Powershell msbuild с вложенными кавычками - PullRequest
25 голосов
/ 03 июня 2011

Использование Powershell и Psake для создания пакета и развертывания для решения Visual Studio. Попытка развернуть проект базы данных с помощью msbuild - который работает правильно с помощью командной строки msdos visual studio

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

тот же вызов метода приводит к ошибке при вызове из powershell

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

относительно пробелов - не могу понять, как повторить этот вызов в powershell - пример строки подключения к базе данных Источник данных =. \ SQL2008; Начальный каталог = DocumentExecution; Интегрированная безопасность = True;

Ответы [ 7 ]

59 голосов
/ 12 декабря 2011

Короткая версия

Как передать аргумент, содержащий кавычки, в собственную команду из PowerShell?

  • Использовать одинарные кавычки вместо двойныхкавычки в строке аргумента:
    <b>"</b>/p:Target=<b>'</b>Data Source=(local)\SQL;Integrated Security=True<b>'"</b>
    /p:Target=<b>'</b>Data Source=(local)\SQL;Integrated Security=True<b>'</b>

  • Используйте обратную косую черту для двойных кавычек в строке аргумента * :
    <b>'</b>/p:Target=<b>\"</b>Data Source=(local)\SQL;Integrated Security=True<b>\"'</b>
    /p:Target=<b>"</b>Data Source=(local)\SQL;Integrated Security=True<b>"</b>

Если встроенные кавычки используются только для обработки аргумента как одной строки, а неБудучи обязательной частью параметра, можно использовать следующее:

  • Заключать в кавычки всю строку аргумента вместо вложения кавычек в аргумент:
    <b>'</b>/p:Target=Data Source=(local)\SQL;Integrated Security=True<b>'</b>
    /p:Target=Data Source=(local)\SQL;Integrated Security=True

  • Сбросить все специальные символы PowerShell с помощью обратных галочек (это можно сделать только как строковый аргумент):
    /p:Target=<b>&#x0060;"</b>Data Source=<b>&#x0060;(</b>local<b>&#x0060;)</b>\SQL<b>&#x0060;;</b>Integrated Security=True<b>&#x0060;"</b>
    или /p:Target=Data<b>&#x0060; </b>Source=<b>&#x0060;(</b>local<b>&#x0060;)</b>\SQL<b>&#x0060;;</b>Integrated<b>&#x0060; </b>Security=True
    /p:Target=Data<b> </b>Source=<b>(</b>local<b>)</b>\SQL<b>;</b>Integrated<b> </b>Security=True

Пример полной командной строки (с использованием второй альтернативы):

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>


Длинная версия

Вызов собственных команд - это нечто, возникающее при переходе между устаревшей системой cmd и PowerShell (почти так же, как «разделение параметров запятыми»).Готча;).

Я попытался обобщить все, что я знаю по поводу вызова команд в PowerShell (v2 и v3) здесь, вместе со всеми примерами и ссылками, которые я могу собрать.

1) Непосредственный вызов собственных команд

1.1) В простейшем случае для исполняемого файла, расположенного в пути среды, команду можно вызвать напрямую , как вы бы назвали командлет PowerShell.

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

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

1.2) Вне пути среды, для команд в определенном каталоге (включая текущий), можно использовать полный или относительный путь к команде .Идея состоит в том, чтобы оператор явно объявил «Я хочу вызвать этот файл», вместо того, чтобы произвольный файл с таким же именем выполнялся вместо него ( см. Этот вопрос длябольше информации о безопасности PowerShell ).Неспособность использовать путь, когда он требуется, приведет к ошибке «термин не распознан».

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) Если путь содержит специальные символы, можно использовать оператор вызова или escape-символ .Например, исполняемый файл, начинающийся с цифры, или расположенный в каталоге с пробелом.

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

PS> Copy-Item EchoArgs.exe C:\tools\5pecialCharacter.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) Косвенный вызов собственных команд

2.1) Если вы не вводите команду в интерактивном режиме, а вместо этого храните путь в переменной, Оператор вызова также можно использовать для вызова команды, названной в переменной .

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) Аргументы, передаваемые команде, также могут храниться в переменных. Аргументы в переменных могут передаваться по отдельности или в массиве. Для переменных, содержащих пробелы, PowerShell автоматически экранирует пробелы, так что собственная команда видит его как один аргумент.(Обратите внимание, что оператор вызова обрабатывает первое значение как команду, а остальные значения как аргументы; аргументы не должны объединяться с переменной команды.)

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) Формат массива особенно полезен для построения динамического списка аргументов для собственной команды. Чтобы каждый аргумент распознавался как отдельный параметр, важно, чтобы аргументы сохранялись в переменной массива.и не просто слиты воедино в одну строку.(Обратите внимание, что общая аббревиатура $args - это автоматическая переменная в PowerShell, которая может привести к тому, что значения, сохраненные в ней, будут перезаписаны; вместо этого лучше использовать описательное имя, например $msbuildArgs, чтобы избежать конфликта имен.)

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) Кроме того, для сценариев, функций, командлетов и т. П. PowerShell v2 может отправлять именованные аргументы, содержащиеся в хеш-таблице, используя технику «splatting», не беспокоясь о порядке параметров. Это не работает с собственными командами, которые не участвуют в объектной модели PowerShell и могут обрабатывать только строковые значения.

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) Вызов собственных команд со сложными аргументами

3.1) Для простых аргументов обычно достаточно автоматического экранирования, используемого для собственных команд. Однако для круглых скобок, знаков доллара, пробелов и т. Д. символов, используемых PowerShell, необходимо экранировать, чтобы они отправлялись как есть исходным командам без их интерпретации синтаксическим анализатором. Это можно сделать с помощью escape-символа обратного тика &#x0060; или путем помещения аргумента в строку в одинарных кавычках.

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

PS> ./echoargs money=`$10.00
Arg 0 is <money=$10.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) К сожалению, это не так просто, когда используются двойные кавычки. В качестве части обработки аргументов для собственных команд процессор PowerShell пытается нормализовать все двойные кавычки в аргументе, чтобы содержимое аргумента, без кавычек, передавалось как отдельное значение собственной команде. Собственная обработка параметров команды происходит как отдельный шаг после синтаксического анализа, поэтому обычное экранирование не будет работать для двойных кавычек; можно использовать только одинарные кавычки или двойные кавычки с обратной косой чертой .

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 добавлен новый символ остановки разбора --% (см. about_Parsing). При использовании перед сложными аргументами --% будет передавать аргументы как есть, без разбора или расширения переменных, за исключением cmd-подобных %ENVIRONMENT_VARIABLE% значений .

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>

Это также можно использовать для разборки единственной строки, представляющей несколько аргументов, путем передачи символа остановки синтаксического анализа в строке & lowast; (хотя лучше всего не мунг аргументы в первую очередь).

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) Отладка собственных команд

Существует два ключевых инструмента для отладки аргументов, которые PowerShell передает собственным командам.

4.1) Первое - это EchoArgs.exe, консольное приложение из PowerShell Community Extensions , которое просто записывает аргументы, переданные ему в угловых скобках (как показано в приведенных выше примерах).

4.2) Второй - это Trace-Command, командлет, который может показать много деталей о том, как PowerShell обрабатывает конвейер. В частности, источник трассировки NativeCommandParameterBinder покажет, что PowerShell получает и передает собственной команде.

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"


Другие ресурсы

Статьи

Вопросы

11 голосов
/ 03 марта 2012

Это можно было бы сделать намного проще, если бы вы использовали командлет Start-Process с параметром -ArgumentList.Я удивлен, что это еще не было упомянуто.

Пример:

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

Вот метод, который мне нравится использовать немного лучше, который позволяет замену переменных:

$ConnectionString = 'aConnectionWithSpacesAndSemiColons';
$DatabaseProjectPath = 'aDatabaseProjectPathWithSpaces';
$MsbuildArguments = '/target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="{0}" "{1}"' -f $ConnectionString, $DatabaseProjectPath;
Start-Process -FilePath msbuild.exe -ArgumentList $MsbuildArguments;
4 голосов
/ 03 июня 2011

Поместите весь параметр в одинарные кавычки:

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

Дополнительный уровень цитирования будет означать, что PSH не обрабатывает контент с правилами PSH. (Любые одинарные кавычки внутри строки должны быть удвоены - это единственный тип экранирования в строке с одинарными кавычками PSH).

1 голос
/ 04 мая 2013

Это упоминается в статьях от этого ответа , но с PowerShell 3 вы можете использовать -%, чтобы остановить обычный синтаксический анализ, который делает PowerShell.

msbuild --% /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"
1 голос
/ 09 декабря 2011

Благодаря ответу JohnF, я наконец смог разобраться в этом.

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>

Короче говоря, но с обратной кавычки перед двойными кавычками И точкой с запятой.Все, что меньше (или больше!), Облажается.

1 голос
/ 24 июня 2011

Ваша проблема в том, что PowerShell не экранирует кавычки при передаче их приложениям командной строки. Я сам столкнулся с этим и подумал, что PowerShell съедает цитаты. Просто сделай это.

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 голос
/ 04 июня 2011

@ Ричард - Тестирование приводит к другой ошибке, сообщающей, что не предоставлен действительный файл проекта.Я провел это через echoargs pscx helper, чтобы показать более подробные примеры.

  1. С одиночными кавычками, обертывающими TargetConnectionString - Powershell оценивает каждый пробел в строке соединения как новую строку:

    & 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. Разделение каждого параметрас обратными чертами воссоздает исходную проблему = без кавычек вокруг строки подключения:

    & 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. Добавление обратных кавычек к кавычкам ведет себя так же, как в примере 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. Использование оператора @ для разделения параметров по-прежнему игнорирует кавычки:

    $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. Обратные галочки для экранирования строки соединения с использованием разделителей строк - те же результаты, что и в примере 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"
    
...