0

sqlcmdによって実行されるように設計されたSQLスクリプトと、正しいパラメーターを使用してsqlcmdを実行するコマンドスクリプトがあります。

コマンドスクリプトを、 sqlcmdの代わりにInvoke-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と呼ばれます。sqlcmdの代わりにInvoke-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

Invoke-SqlcmdがZ:\、デフォルトの作業ディレクトリであるディレクトリ内の入力ファイルを見つけることができないため、PowerShellスクリプトでエラーが発生します。

コマンドスクリプトは、現在の作業ディレクトリでスクリプトを見つけました。

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にデフォルトの作業ディレクトリの代わりに現在の作業ディレクトリを使用させるにはどうすればよいですか?

コマンドレットは現在の作業ディレクトリを使用します。問題は、PowerShellセッションで作業ディレクトリをまったく変更しなかったことです。

PowerShellでは、はコマンドレットcdのエイリアスです。これは、コマンドレットSet-Locationを使用して証明できます。Get-Alias

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の一般的な設計原則に違反しています。ほとんどのコマンドレットは作業場所を使用して相対パスを解決しますが、Invoke-Sqlcmdは例外です。

ILSpy逆アセンブラと少しの直感を使用して、含まれているアセンブリを検査するとMicrosoft.SqlServer.Management.PSSnapins、エラーの理由がわかったと思います。

コマンドレットのパラメータ-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を使用して、指定された入力ファイルが存在するかどうかを確認します。メソッドのドキュメントには、相対パスは作業ディレクトリを使用して解決されると記載されています。

pathパラメーターは、相対パス情報または絶対パス情報を指定できます。相対パス情報は、現在の作業ディレクトリからの相対パスとして解釈されます。現在の作業ディレクトリを取得するには、GetCurrentDirectoryを参照してください。

File.Existsこれは、この場合はfalseを返し、質問に表示されるエラーメッセージを引き起こすことを示唆しています。プロンプトから直接メソッドを実行することで、これを証明できます。

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

出力:

False

メソッドはfalseを返すため、コマンドレットは「ファイルが見つかりません」エラーで終了します。

異常な動作を回避できます

Invoke-Sqlcmdには、相対パスを解決するために作業場所の代わりに作業ディレクトリを使用する2つの回避策があります。

  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を実行する前に、作業ディレクトリを作業場所と一致するように設定します。作業ディレクトリを変更することによる意図しない副作用を回避するために、scrtiptは完了する前に作業ディレクトリの値を復元します。

現在のディレクトリの設定については、このチャネル9スレッドで説明されています。そこの例ではDirectory.SetCurrentDirectoryメソッドを使用していますが、プロパティを直接設定する方が簡単です。

于 2012-09-28T13:36:05.550 に答える
2

入力ファイルの場所を完全に修飾できます。

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 に答える