我有一个旨在由sqlcmd执行的SQL脚本,以及一个使用正确参数执行sqlcmd的Command脚本。
我想将Command脚本转换为使用Invoke-Sqlcmd而不是sqlcmd的PowerShell脚本。
SQL脚本,Command脚本和新的PowerShell脚本都位于C:\Users\iain.CORP\SqlcmdQuestion
目录中。
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脚本名为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使用当前工作目录而不是默认工作目录?
答案 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
我的问题有一个错误的前提:
如何使Invoke-Sqlcmd使用当前工作目录而不是默认工作目录?
cmdlet确实使用当前工作目录。问题是我在PowerShell会话中根本没有更改工作目录。
在PowerShell中,cd
是Set-Location
cmdlet的别名。您可以使用Get-Alias
cmdlet
Get-Alias cd
输出:
CommandType Name Definition
----------- ---- ----------
Alias cd Set-Location
[A]尽管PowerShell的位置类似于工作目录,但该位置与工作目录不同。实际上,PowerShell不会触及工作目录。
Set-Location不设置工作目录。它设置了工作位置,这在PowerShell中是一个类似但不同的概念。
您可以在使用cd
设置工作位置之后使用.NET属性Environment.CurrentDirectory
检查工作目录来证明这一点,如下所示:
cd C:\Users\iain.CORP\SqlcmdQuestion
Environment::CurrentDirectory
输出:
Z:\
我猜这个设计决定是一致的。例如,当工作位置设置为注册表配置单元时,工作目录将是未定义的。
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有两种解决方法,使用工作目录而不是工作位置来解析相对路径:
-InputFile
参数的值。 CandiedCode's answer显示了如何执行此操作。我通过修改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\'