如何将此sqlcmd脚本转换为Invoke-Sqlcmd脚本?

时间:2012-09-26 15:12:07

标签: sql-server tsql powershell cmd sqlcmd

我有一个旨在由sqlcmd执行的SQL脚本,以及一个使用正确参数执行sqlcmd的Command脚本。

我想将Command脚本转换为使用Invoke-Sqlcmd而不是sqlcmd的PowerShell脚本。

SQL脚本,Command脚本和新的PowerShell脚本都位于C:\Users\iain.CORP\SqlcmdQuestion目录中。

SQL脚本

SQL脚本称为ExampleQuery.sql。它选择一个字符串文字。字符串文字的值由sqlcmd在运行时设置为ComputerName sqlcmd脚本变量的值。代码如下所示:

SELECT '$(ComputerName)';

命令脚本

命令脚本名为ExecQuery.cmd。它调用sqlcmd来执行ExampleQuery.sql并将脚本变量ComputerName的值设置为环境变量COMPUTERNAME的值。代码如下所示:

sqlcmd -i ExampleQuery.sql -v ComputerName = %COMPUTERNAME%

当我打开命令提示符时,默认工作目录为C:\Users\iain.CORP。我将更改为包含文件的目录,然后运行命令脚本:

cd C:\Users\iain.CORP\SqlcmdQuestion
ExecQuery.cmd

我看到了这个输出:

---------
SKYPC0083

(1 rows affected)

该脚本成功选择由sqlcmd设置的字符串文字。

PowerShell脚本

PowerShell脚本名为ExecQuery.ps1。它应该与命令脚本一样,使用Invoke-Sqlcmd而不是sqlcmd。代码如下所示:

Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100

Invoke-Sqlcmd -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"

当我打开PowerShell提示时,默认工作目录为Z:\。我切换到包含文件的目录,并运行PowerShell脚本:

cd C:\Users\iain.CORP\SqlcmdQuestion
.\ExecQuery.ps1

我看到了这个输出:

Invoke-Sqlcmd : Could not find file 'Z:\ExampleQuery.sql'.
At C:\Users\iain.CORP\SqlcmdQuestion\ExecQuery.ps1:4 char:14
+ Invoke-Sqlcmd <<<<  -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
    + CategoryInfo          : InvalidResult: (:) [Invoke-Sqlcmd], FileNotFoundException
    + FullyQualifiedErrorId : ExecutionFailed,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand

PowerShell脚本引发错误,因为Invoke-Sqlcmd无法在Z:\目录中找到输入文件,该目录恰好是默认的工作目录。

Command脚本在当前工作目录中找到了脚本。

如何使Invoke-Sqlcmd使用当前工作目录而不是默认工作目录?

2 个答案:

答案 0 :(得分:13)

对于这个答案,假设目录C:\Users\iain.CORP\SqlcmdQuestion存在,并且在该位置执行dir会产生以下输出,如问题所示:

    Directory: C:\Users\iain.Corp\SqlcmdQuestion


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        26/09/2012     15:30         27 ExampleQuery.sql
-a---        26/09/2012     15:30         61 ExecQuery.cmd
-a---        26/09/2012     15:34        172 ExecQuery.ps1

PowerShell按设计忽略工作目录

我的问题有一个错误的前提:

  

如何使Invoke-Sqlcmd使用当前工作目录而不是默认工作目录?

cmdlet确实使用当前工作目录。问题是我在PowerShell会话中根本没有更改工作目录。

在PowerShell中,cdSet-Location cmdlet的别名。您可以使用Get-Alias cmdlet

来证明这一点
Get-Alias cd

输出:

CommandType     Name                                                Definition
-----------     ----                                                ----------
Alias           cd                                                  Set-Location

Alex Angelopoulos解释说:

  

[A]尽管PowerShell的位置类似于工作目录,但该位置与工作目录不同。实际上,PowerShell不会触及工作目录。

Set-Location不设置工作目录。它设置了工作位置,这在PowerShell中是一个类似但不同的概念。

您可以在使用cd设置工作位置之后使用.NET属性Environment.CurrentDirectory检查工作目录来证明这一点,如下所示:

cd C:\Users\iain.CORP\SqlcmdQuestion
Environment::CurrentDirectory

输出:

Z:\

我猜这个设计决定是一致的。例如,当工作位置设置为注册表配置单元时,工作目录将是未定义的。

Invoke-Sqlcmd违反了这一设计原则

Invoke-Sqlcmd违反PowerShell的一般设计原则,使用工作位置而不是工作目录。大多数cmdlet使用工作位置来解析相对路径,但Invoke-Sqlcmd是一个例外。

使用ILSpy disassembler和一点直觉来检查包含的程序集Microsoft.SqlServer.Management.PSSnapins,我相信我找到了错误的原因。

我相信cmdlet的参数-InputFile是由方法IncludeFileName实现的。 ILSpy对方法的反汇编如下所示:

// Microsoft.SqlServer.Management.PowerShell.ExecutionProcessor
public ParserAction IncludeFileName(string fileName, ref IBatchSource pIBatchSource)
{
    if (!File.Exists(fileName))
    {
        ExecutionProcessor.sqlCmdCmdLet.TerminateCmdLet(new FileNotFoundException(PowerShellStrings.CannotFindPath(fileName), fileName), "ExecutionFailureException", ErrorCategory.ParserError);
        return ParserAction.Abort;
    }
    BatchSourceFile batchSourceFile = new BatchSourceFile(fileName);
    pIBatchSource = batchSourceFile;
    return ParserAction.Continue;
}

Invoke-Sqlcmd使用.NET方法File.Exists来检查指定的输入文件是否存在。方法的documentation注释使用工作目录解析相对路径:

  

允许path参数指定相对路径或绝对路径   信息。相对路径信息被解释为相对于   当前的工作目录。获得当前的工作   目录,请参阅GetCurrentDirectory。

这表明File.Exists在这种情况下会返回false,这会导致问题中出现错误消息。您可以通过直接从提示符执行该方法来证明这一点:

cd C:\Users\iain.CORP\SqlcmdQuestion
[IO.File]::Exists('ExecQuery.sql')

输出:

False

该方法返回false,因此cmdlet以“找不到文件”错误终止。

您可以解决这种不寻常的行为

Invoke-Sqlcmd有两种解决方法,使用工作目录而不是工作位置来解析相对路径:

  1. 始终使用绝对路径作为-InputFile参数的值。 CandiedCode's answer显示了如何执行此操作。
  2. 设置工作目录并使用相对路径。
  3. 我通过修改ExecQuery.ps1这样解决了没有副作用的问题:

    Add-PSSnapin SqlServerCmdletSnapin100
    Add-PSSnapin SqlServerProviderSnapin100
    
    $RestoreValue = [Environment]::CurrentDirectory
    [Environment]::CurrentDirectory = Get-Location
    Invoke-Sqlcmd -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
    [Environment]::CurrentDirectory = $RestoreValue
    

    我看到了这个输出:

    Column1
    -------
    SKYPC0083
    

    成功!

    新脚本在执行Invoke-Sqlcmd之前将工作目录设置为与工作位置匹配。为了避免更改工作目录的意外副作用,scrtipt会在完成之前恢复工作目录值。

    Channel 9 thread中描述了设置当前目录。这里的示例使用Directory.SetCurrentDirectory方法,但我发现直接设置属性更简单。

答案 1 :(得分:2)

您可以完全限定Inputfile位置:

Invoke-Sqlcmd -InputFile 'C:\Users\iain.CORP\SqlcmdQuestion\ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"

使用变量来驱动脚本位置:

$FileLocation = 'C:\Users\iain.CORP\SqlcmdQuestion\'