Вызов Powershell msbuild с вложенными кавычками

Использование 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 ответов

Укороченная версия

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

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

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

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

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

  • Удалите все специальные символы PowerShell с помощью обратных символов * (это можно сделать только в виде встроенного аргумента):
    /p:Target=`"Data Source=`(local`)\SQL`;Integrated Security=True`"
    или же /p:Target=Data` Source=`(local`)\SQL`;Integrated` Security=True
    /p:Target=Data Source=(local)\SQL;Integrated 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
Аргумент 0 равен 
Аргумент 1 - это 
Аргумент 2 - это 
Аргумент 3: 
Arg 4 - это 



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

Вызов собственных команд - это нечто, возникающее при переходе между устаревшей системой 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-символа backtick, ` или путем помещения аргумента в строку в одинарных кавычках.

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-like %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>

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

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

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"


Другие источники

статьи

Вопросы

Все это может быть сделано намного проще, если вы используете 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;

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

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

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

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

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

Благодаря ответу 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>

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

@Richard - при тестировании выдается другая ошибка о том, что не предоставлен действительный файл проекта. Я запустил это через 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 `
    

    с /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"
    

Ваша проблема в том, что 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"
Другие вопросы по тегам