web-dev-qa-db-ja.com

ネストされた引用符を使用したPowershell呼び出しmsbuild

PowershellとPsakeを使用して、VisualStudioソリューションのパッケージと展開を作成します。 msbuildを使用してデータベースプロジェクトをデプロイしようとしています-msdosvisualstudioコマンドラインを使用して正しく機能しています

   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;

25
JohnF

短縮版

引用符を含む引数を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


完全なコマンドラインの例(2番目の選択肢を使用):

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
 Arg0は</ target:Deploy> 
 Arg1は</ p:UseSandboxSettings = False> 
 Arg2は</ p:TargetDatabase = UpdatedTargetDatabase> [.____です。 ]引数3は</ p:TargetConnectionString = "Data Source =(local)\ SQL; Integrate Security = True"> 
引数4は<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)環境パスの外、特定のディレクトリ(現在のディレクトリを含む)内のコマンドの場合、コマンドへのフルパスまたは相対パスを使用できます =。アイデアは、同じ名前の任意のファイルをその場所で実行させるのではなく、オペレーターに「thisファイルを呼び出したい」と明示的に宣言させることです(- 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)パスに特殊文字が含まれている場合は、呼び出し演算子またはエスケープ文字を使用できます。たとえば、番号で始まる実行可能ファイル、またはスペースを含むディレクトリにある実行可能ファイル。

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)配列形式は、ネイティブコマンドの引数の動的リストを作成する場合に特に便利です。各引数が個別のパラメーターとして認識されるためには、引数が重要です。配列変数に格納され、1つの文字列にまとめられるだけではありません。 (一般的な省略形$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は、「スプラッティング」と呼ばれる手法を使用して、ハッシュテーブルに含まれる名前付き引数を心配することなく送信できます。パラメータの順序。これは、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)複雑な引数を使用したネイティブコマンドの呼び出し

.1)単純な引数の場合、ネイティブコマンドに使用される自動エスケープで通常は十分です。ただし、括弧、ドル記号、スペースなどの場合、PowerShellで使用される文字は、パーサーによって解釈されることなく、そのままネイティブコマンドに送信するためにエスケープする必要があります。これは、バックティックエスケープ文字`を使用するか、引数を一重引用符で囲むことによって実行できます。

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)>


.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) PowerShellv3は新しい停止解析記号--%を追加しました( 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>

これは、文字列内の解析停止記号を渡すことにより、複数の引数を表す単一の文字列を削除するためにも使用できます。 (ただし、ベストプラクティスは、そもそも議論を混乱させないことです)。

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がネイティブコマンドに渡す引数をデバッグするための2つの主要なツールがあります。

4.1)最初はEchoArgs.exePowerShell Community Extensions からのコンソールアプリケーションであり、間に渡された引数を単純に書き戻します。山かっこ(上記の例に示されているように)。

4.2) 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"


その他のリソース

記事

質問

61
Emperor XLII

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;
11
Trevor Sullivan

パラメータ全体を一重引用符で囲みます。

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

引用の余分なレベルは、PSHがPSHのルールでコンテンツを処理しないことを意味します。 (文字列内の単一引用符は2倍にする必要があります。これは、PSH単一引用符で囲まれた文字列でエスケープする唯一のタイプです)。

4
Richard

この回答 の記事で言及されていますが、 PowerShell を使用すると、-%を使用してPowerShellの通常の解析を停止できます。

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

問題は、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
JasonMArcher

@ Richard-これをテストすると、有効なプロジェクトファイルが提供されていないという別のエラーが生成されます。これをechoargspscxヘルパーで実行して、より詳細な例を示しました。

  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"
    _
1
JohnF

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
moswald