web-dev-qa-db-ja.com

SQLエージェント-PowerShellステップ「構文エラー」

実行されていないSQLエージェントジョブステップで次のコードを設定しています。受け取ったメッセージは単純です:

ステップ1の実行を開始できません(理由:line(46):構文エラー)。ステップは失敗しました。

ジョブの期間:00:00:00

このスクリプトの46行目はhere-stringです。


    ,@DriveLetter = '$($c.DriveLetter)'

このスクリプトは、SQL Serverエージェントの外部で完全に実行されます。

ServerNamesテーブル:


CREATE TABLE ServerTable (
ServerName varchar(50)
)

ディスクスペーステーブルは次のようになります。


CREATE TABLE DiskSpace (
ID int IDENTITY(1,1),
ServerName varchar(50),
DriveLetter varchar(2),
DiskSpaceCapacityGB decimal(12,5),
DiskSpaceFreeGB decimal(12,5)
)

PowerShellスクリプト:

This command is to allow SQL Agent job to show failure in event PowerShell script errors
Default behavior in SQL Agent for PowerShell steps it 'Continue'
$erroractionpreference = "Stop"


$sqlInstance = 'server1' $sqlDatabase = 'database1'


$qServerList = @" Select ServerName From ServerTable "@


$srvList = Invoke-Sqlcmd -ServerInstance $sqlInstance -Database $sqlDatabase -Query $qServerList | Where-Object {$_ -ne '' -or $_ -ne $null} | Select-Object -ExpandProperty ServerName


foreach ($s in $srvList) { if (Test-Connection -ComputerName $s -Count 1 -ErrorAction 'SilentlyContinue') { try { $cServer = Get-WmiObject Win32_Volume -ComputerName $s -ErrorAction 'Stop' | where {$.DriveType -eq 3 -and $.DriveLetter } | Select-Object @{Label="ServerName";Expression={$s}}, @{Label="DriveLetter";Expression={$.DriveLetter}}, @{Label="DiskSpaceCapacityGB";Expression={"{0:N0}" -f($.Capacity/1GB)}}, @{Label="DiskFreeSpaceGB";Expression={"{0:N2}" -f($_.FreeSpace/1GB)}}

        foreach ($c in $cServer) 
        {
            $qAddServerDiskSpace = @"
 </ code>

EXEC [dbo]。[StoredProcInsert] @ServerName = '$($ c.ServerName)' 、@ DriveLetter = '$($ c.DriveLetter)' 、@ DiskSpaceCapacityGB = $($ c.DiskSpaceCapacityGB) 、@ DiskFreeSpaceGB = $($ c.DiskFreeSpaceGB) "@
            try
            {
                Invoke-Sqlcmd -ServerInstance $sqlInstance -Database $sqlDatabase -Query $qAddServerDiskSpace -ErrorAction 'Stop'
            }
            catch
            {
                $ErrorMsg = $_.Exception.Message
                $fullMsg = "Error writing executing dbo.StoredProcInsert for $s - $ErrorMsg"
                Return $fullMsg
            }           
        }
    }
    catch
    {
        $ErrorMsg = $_.Exception.Message
        $qAddPSErrorLog = @"

EXEC [dbo]。[StoredProcInsert_Error] @ServerName = '$($ cServer.ServerName)' 、@ErrorText = '$($ ErrorMsg)' "@ 試行 { Invoke-Sqlcmd -ServerInstance $ sqlInstance -Database $ sqlDatabase -Query $ qAddPSErrorLog -ErrorAction 'Stop' } catch { $ ErrorMsg = $ _。Exception.Message $ fullMsg = "$ sの実行dbo.StoredProcInsert_Errorの書き込みエラー-$ ErrorMsg" } } } else { $ qAddPSErrorLog = @ " EXEC [dbo]。[StoredProcInsert_Error] @ServerName = '$($ cServer.ServerName)' 、@ErrorText = 'Unable to ping server' "@
    try 
    {
        Invoke-Sqlcmd -ServerInstance $sqlInstance -Database $sqlDatabase -Query $qAddPSErrorLog  -ErrorAction 'Stop'
    }
    catch 
    {
        $ErrorMsg = $_.Exception.Message
        $fullMsg = "Error writing executing dbo.StoredProcInsert_Error for $s - $ErrorMsg"
        Return $fullMsg
    }
}

}

[〜#〜]編集[〜#〜]

CmdExecタイプをPowerShell -Command "& { }"として使用するための追加の手順を設定します。この実行により、スクリプトが実行され、適切なキャッチブロックのエラーログが入力されますが、ディスク領域のデータはテーブルに書き込まれません。

この実行のエージェント履歴に警告メッセージが表示されます。

ユーザーとして実行されたメッセージ:NT Service\SQLAgent $ myInstance。警告:一部のインポートされたコマンド名には未承認の動詞が含まれているため、検出されにくくなる可能性があります。 Verboseパラメーターを使用して詳細を確認するか、Get-Verbと入力して、承認された動詞の一覧を表示します。プロセス終了コード0。ステップは成功しました。

同じタイプのステップを使用してファイルを呼び出した場合:PowerShell -File MyScript.ps1上記のPowerShellステップタイプと同じエラーメッセージが表示されます。

10
user507

これはあまり直感的ではなく、説明で具体的なものを見つけることはできませんでした[例:正確なBOLまたはホワイトペーパーは見つかりませんでした]。

SQLエージェントジョブの構文エラーは、 ユーザートークン に基づくT-SQL構文エラーです。つまり、基本的には、PowerShellのサブ式演算子がSQL Serverエージェントのトークンとして扱われるということです。したがって、PowerShellでは、この$( )はSQL Serverエージェントの予約文字シーケンスとして扱われるようです。したがって、SQLエージェントは、参照されているBOLの記事からPRINT N'Current database name is $(A-DBN)' ;のようなT-SQLの例を探します。

そのため、スクリプトで,@DriveLetter = '$($c.DriveLetter)'に達したとき、「$ c.DriveLetter」は許可されているトークンの1つではありません。

これに対する回避策は、PowerShellで部分式ステートメントを使用しないことです。次のようにスクリプトで調整を行います。


$qAddServerDiskSpace = @"
EXEC [dbo].[StoredProcInsert]
    @ServerName = '$($c.ServerName)'
    ,@DriveLetter = '$($c.DriveLetter)'
    ,@DiskSpaceCapacityGB = $($c.DiskSpaceCapacityGB)
    ,@DiskFreeSpaceGB = $($c.DiskFreeSpaceGB)
"@

このようなものに変更する必要があります:


$severName = $c.ServerName
$driveLetter = $c.DriveLetter
$capacityGB = $c.DiskSpaceCapacityGB
$freeGB = $c.DiskFreeSpaceGB

$qAddServerDiskSpace = @"
EXEC [dbo].[StoredProcInsert]
   @ServerName = '$serverName'
   ,@DriveLetter = '$driveLetter'
   ,@DiskSpaceCapacityGB = $CapacityGB
   ,@DiskFreeSpaceGB = $freeGB
"@

上記の調整をスクリプトに加えたところ、完全に実行されました。

11
user507

単一引用符( ')は、Powershellの特殊文字であり、文字列を区切ります。二重引用符と一重引用符の違いは、それが 文字列を解釈する の問題です。これは特殊文字なので、 Grave-accent( `) を使用してエスケープする必要があります。この構文はあなたのために働くはずです:

 @ServerName = `'$($c.ServerName)`'
,@DriveLetter = `'$($c.DriveLetter)`'
1
Mike Fal