【问题标题】:Passing path scripting variable to sqlcmd when called using Invoke-Expression使用 Invoke-Expression 调用时将路径脚本变量传递给 sqlcmd
【发布时间】:2017-06-23 22:43:52
【问题描述】:

我正在尝试创建一个用于创建数据库的 powershell 脚本。 createDb.ps1 的相关部分是:

param([string] $server,
      [string] $dbName)
$scriptpath = "C:\script\path" 
$cDb = "master"
$line = "script.sql"
$outfile = "\log.txt"
$dbDir = "C:\database path\"
$command = @"
sqlcmd -b -S $server -d $cDb -i '$scriptpath\$line' -o '.$outfile' -v dbLocation='$dbDir' dbName=$dbName
"@

Invoke-Expression $command

我使用以下参数调用脚本:

createDb.ps1 -server localhost -dbName TestDb

但是,当我运行它时,我收到以下错误:

sqlcmd: 'dbDir=C:\database path\" dbName=TestDb': Invalid argument. Enter '-?' for help.

当我从命令行执行以下命令时,一切都按预期工作:

sqlcmd -b -S localhost -d master -i "C:\script\path\script.sql" -o ".\log.txt" -v dbLocation="C:\database path\" dbName=TestDb

【问题讨论】:

  • 不知道你为什么要使用 PowerShell,但是当你可以使用 MSSQL PowerShell 扩展 (SQLPS) 时,却“向 SQLCMD 发起攻击”...?
  • 如果我没记错的话,您需要在默认 SQL Server 安装之外进行额外安装才能使用它。而 sqlcmd 附带默认的 SQL Server 安装。由于该脚本可以在许多服务器上运行,我想尽量减少所需的设置。如果不是这种情况,那么这可能是一个不错的选择。

标签: powershell sqlcmd


【解决方案1】:

不要先将命令行构建为字符串,然后将其传递给Invoke-Expression - 这不仅没有必要,还会导致参数分区出现问题。

直接调用命令

sqlcmd -b -S $server -d $cDb -i $scriptpath\$line -o ".$outfile" -v dbLocation=`"$dbDir`" `
  dbName=$dbName

注意".$outfile"dbLocation=`"$dbDir`" 需要特殊处理:

  • 由于PowerShell parsing quirk(从 PSv5.1 开始),.$ 在未引用标记的开头导致它被分解为 2 个参数。将.$outfile 包含在"..." 中可以防止该问题。

  • 在 PowerShell 解析您的命令后,它实质上是重建命令行,并在将其传递给系统执行之前使用选择性双引号参数。虽然这通常可以按需要工作,但也有一些极端情况,例如 dbLocation=$dbDir:

      1234563 单个参数,它产生"dbLocation=C:\database path\" - 和sqlcmd 可能对此犹豫不决。
  • 通过在标记中显式嵌入双引号 - 使用`",它们被转义 " 字符。 - PowerShell 单独保留 dbLocation=`"$dbDir`" 的扩展结果,生成:dbLocation="C:\database path\"

  • 警告

    • 大多数目标程序将 \" 解释为不具有 句法 功能(在这种情况下:not 作为结束 @987654343 @ 之前恰好是 \),但 作为 转义、嵌入 " 导致参数解析失败 - 取决于在目标程序上,您可能必须转义最后的 \ - 或者可能所有 \ 实例 - 作为 \\

    • 理解在 Windows 上最终总是由目标程序来解释命令行是很重要的;更多信息请参见this answer

一般情况:

  • 变量引用在作为参数传递给外部实用程序时不需要双引号,即使被引用变量的值包含空格(或其他元字符) .

    • 也就是说,将变量引用与文字/其他变量引用结合的常规双引号标记是形成的一个好习惯(例如,"$scriptpath\$line"".$outfile"),因为精确的规则当这些标记被认为是多个参数时不容易记住 - 请参阅底部的链接。
  • 但是,您需要注意 PowerShell 自己的元字符的未引用实例 - 例如一般的 , 和标记开头的 @ - 和 `-转义它们以将它们用作文字(在手头的情况下不是问题)。

  • 引用样式只对 PowerShell 重要 - 一旦 PowerShell 执行了自己的解析并可能扩展了变量引用,当时的文字标记将重新组合成一个命令行,在幕后使用双引号来保留参数边界,即使原始命令行包含单引号标记。

    • 例如,PowerShell 命令行 foo.exe 'bar baz' $env:ProgramFiles $env:OS 将在调用 foo.exe 时转换为 foo.exe "bar baz" "C:\Program Files" Windows_NT
      请注意如何根据需要对带有嵌入空格的值使用双引号,而与原始命令行中使用的引号无关。

有关 PowerShell 如何解析参数的全面讨论,请参阅我的 this answer

【讨论】:

  • 感谢您提供的信息,这让我明白了很多。但是,这仍然没有解决我的问题。我认为这与我的 -v 选项有关。我在某处读到通过powershell调用带有多个'='的sqlcmd会导致问题。你知道这是否属实,是否有办法绕过它并且仍然有多个脚本变量?
  • 原来是错误的 SQL 脚本和我的调用的组合。由于您的回答解决了我的 powershell 脚本问题,我将其标记为答案。谢谢!
  • @CSCoder:谢谢 - 我刚刚在你接受后不久对我的答案添加了重要更新 - 我希望我没有让事情变得更糟;如果有,请告诉我。
【解决方案2】:

在运行 sqlcmd 时使用双引号将属性值括起来:

$command = @" 
    sqlcmd -b -S $server -d $cDb -i "$scriptpath\$line" -o ".$outfile" -v dbLocation="$dbDir" dbName=$dbName 
"@

【讨论】:

  • 这也不起作用..我收到相同的错误消息。
  • 也许也给这两个变量自己的 -v 开关(例如-v dbLocation="$dbDir" -v dbName=$dbName)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-26
  • 2019-03-16
  • 2016-12-14
相关资源
最近更新 更多