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

Использование Powershell и Psake для создания пакета и развертывания для визуального студийного решения. Попытка развернуть проект базы данных с помощью 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; Начальный каталог = Выполнение документа; Интегрированная безопасность = Истина;

Ответ 1

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

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

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

  • Использование обратной косой черты для двойных кавычек в строке аргумента & lowast;:
      '/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 с backticks & lowast; (это можно сделать только как встроенный аргумент):
      /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
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. p >

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

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

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; (хотя наилучшей практикой является не аргумент munge в первую очередь).

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"


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

Статьи

Вопросы

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

    $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
    
  • Backticks для выхода из строки подключения с использованием разделителей строк - те же результаты, что и в примере 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"
    

Ответ 5

Ваша проблема в том, что 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"

Ответ 6

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

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

Ответ 7

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

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