0

我有一个设计为由sqlcmd执行的 SQL 脚本,以及一个使用正确参数执行 sqlcmd 的命令脚本。

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

SQL 脚本、命令脚本和新的 PowerShell 脚本都位于目录中C:\Users\iain.CORP\SqlcmdQuestion

SQL 脚本

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

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:\目录恰好是默认工作目录。

命令脚本在当前工作目录中找到了该脚本。

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

4

2 回答 2

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-Locationcmdlet 的别名。Get-Alias您可以使用cmdlet来证明这一点:

Get-Alias cd

输出:

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

亚历克斯·安杰洛普洛斯解释说:

[A] 虽然 PowerShell 的位置类似于工作目录,但位置与工作目录不同。事实上,PowerShell 不会触及工作目录。

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

Environment.CurrentDirectory您可以通过在设置工作位置后使用问题中的 .NET 属性检查工作目录来证明这一点cd

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

输出:

Z:\

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

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

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

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

我相信cmdlet的参数-InputFile是由method实现的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检查指定的输入文件是否存在。该方法的文档说明相对路径是使用工作目录解析的:

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

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

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

输出:

False

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

您可以解决异常行为

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

  1. 始终使用绝对路径作为-InputFile参数的值。CandiedCode 的回答显示了如何做到这一点。
  2. 设置工作目录并使用相对路径。

我通过这样的修改解决了这个问题而没有副作用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 之前设置工作目录以匹配工作位置。为了避免更改工作目录的意外副作用,脚本在完成之前恢复工作目录值。

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

于 2012-09-28T13:36:05.550 回答
2

您可以完全限定 Inputfile 位置:

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

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

$FileLocation = 'C:\Users\iain.CORP\SqlcmdQuestion\'
于 2012-09-26T18:42:35.833 回答