powershell invoke-commandからSQL-Serverエージェントへの戻り値

多くのMS-SQLサーバーで独自のバックアップ管理方法を作成するとき、リモート呼び出しのためにpowershellに値を転送するメカニズムの研究に多くの時間を費やしたので、私は自分自身にメモを書いています。

それでは、簡単なスクリプトから始めて、ローカルで実行してみましょう。

$exitcode = $args[0]
Write-Host 'Out to host.'
Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

スクリプトを実行するには、次のCMDファイルを使用します。毎回指定するわけではありません。

@Echo OFF
PowerShell .\TestOutput1.ps1 1
ECHO ERRORLEVEL=%ERRORLEVEL%

画面に次のように表示されます。

Out to host.
Out to output.
ExitCode: 1
1
ERRORLEVEL=1

次に、WSMANを介して(リモートで)同じスクリプトを実行します。

Invoke-Command -ComputerName . -ScriptBlock { &'D:\sqlagent\TestOutput1.ps1' $args[0] } -ArgumentList $args[0]

そしてここに結果があります:

Out to host.
Out to output.
ExitCode: 2
2
ERRORLEVEL=0

奇妙なことに、Errorlevelはどこかで消えましたが、スクリプトから値を取得する必要があります。次の構成を試します。

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:\sqlagent\TestOutput1.ps1' $args[0] } -ArgumentList $args[0]

もっと面白いです。出力で出力されたメッセージはどこかで消えました:

Out to host.
ExitCode: 2
ERRORLEVEL=0

さて、叙情的な余談として、Powershell関数内の変数に割り当てずにWrite-Outputまたは式のみを記述した場合(これは暗黙的に出力チャネルへの出力を意味します)、ローカルで実行しても何も表示されないことに注意してください!これは、Powershellパイプラインアーキテクチャの結果です。各関数には独自の出力パイプラインがあり、そのために配列が作成されます。それに入るすべてのものが関数の結果と見なされます。Returnステートメントは、戻り値を最後の要素と同じパイプラインに追加し、呼び出し元の関数に制御を渡します。説明のために、次のスクリプトをローカルで実行します。

Function Write-Log {
  Param( [Parameter(Mandatory=$false, ValueFromPipeline=$true)] [String[]] $OutString = "`r`n" )
  Write-Output ("Function: "+$OutString)
  Return "ReturnValue"
}
Write-Output ("Main: "+"ParameterValue")
$res = Write-Log "ParameterValue"
$res.GetType()
$res.Length
$res | Foreach-Object { Write-Host ("Main: "+$_) }

そして、これが彼の結果です:

Main: ParameterValue

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
2
Main: Function: ParameterValue
Main: ReturnValue

メイン関数(スクリプト本体)にも独自の出力パイプラインがあり、CMDから最初のスクリプトを実行すると、出力がファイルにリダイレクトされ、

PowerShell .\TestOutput1.ps1 1 > TestOutput1.txt

次に、画面に表示されます

ERRORLEVEL=1

そしてファイル内
Out to host.
Out to output.
ExitCode: 1
1

PowerShellから同様の呼び出しを行うと

PS D:\sqlagent> .\TestOutput1.ps1 1 > TestOutput1.txt


その後、画面になります

Out to host.
ExitCode: 1

そしてファイル内

Out to output.
1

これは、CMDがpowershellを開始し、他の指示がない場合、2つのストリーム(ホストと出力)を混合してCMDに渡し、受け取ったすべてをファイルに送信します。また、Powershellから起動した場合、これら2つのストリームは別々に存在し、シンボルリダイレクトは出力にのみ影響します。

メイントピックに戻ると、Powershell内の.NETオブジェクトモデルは1台のコンピューター(単一OS)のフレームワーク内に完全に存在することを思い出します。リモートコードがWSMANを介して実行されると、オブジェクトはXMLシリアル化を介して転送されます。次のコードを実行して、実験を続けましょう。

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:\sqlagent\TestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$res.GetType()
$host.SetShouldExit($res)

画面には次のものがあります。

Out to host.

ExitCode: 3

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
    "exitCode",  : "System.Object[]",  "SetShouldExit"   "System.Int32": "    "System.Object[]"  "System.Object[]"   "System
.Int32"."
D:\sqlagent\TestOutput3.ps1:3 :1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

ERRORLEVEL=0

素晴らしい結果です!これは、Invoke-Commandが呼び出されると、パイプラインが2つのストリーム(ホストと出力)に分割されることを意味します。出力ストリームに値を1つだけ残してみましょう。リモートで実行する最初のスクリプトを変更します。

$exitcode = $args[0]
Write-Host 'Out to host.'
#Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

次のように実行します。

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:\sqlagent\TestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$host.SetShouldExit($res)

そして...はい、それは勝利のようです!

Out to host.
ExitCode: 4

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


ERRORLEVEL=4

私たちに何が起こったのかを理解してみましょう。ローカルでpowershellを呼び出し、次にリモートコンピューターでpowershellを呼び出し、そこでスクリプトを実行しました。リモートマシンからの2つのストリーム(ホストと出力)がシリアル化されて転送され、出力ストリームがデジタル値が1つある場合は、Int32タイプに変換され、この形式で受信側に送信され、受信側はそれを呼び出し終了コードとして使用しましたパワーシェル。

そして最後のチェックとして、SQLサーバー上に「オペレーティングシステム(cmdexec)」というタイプのワンステップタスクを次のテキストで作成します。

PowerShell -NonInteractive -NoProfile "$res=Invoke-Command -ComputerName BACKUPSERVER -ConfigurationName SQLAgent -ScriptBlock {&'D:\sqlagent\TestOutput1.ps1' 6}; $host.SetShouldExit($res)"

フーレイ!タスクが失敗しました。ログのテキスト:

   : DOMAIN\agentuser. Out to host. ExitCode: 6.     6.     .

調査結果:

  • Write-Outputを使用したり、割り当てなしで式を指定したりしないでください。このコードをスクリプト内の別の場所に移動すると、予期しない結果が生じる可能性があることに注意してください。
  • 手動での起動ではなく、自動化メカニズム、特にWINRMを介したリモート呼び出しでの使用を目的としたスクリプトで、Try / Catchを介して手動でエラー処理を行い、このスクリプトがイベントの開発のために出力ストリームに正確に1つを送信するようにします。プリミティブ型の値。従来のErrorlevelを取得したい場合-この値は数値でなければなりません。

All Articles