Nagbabalik ng value mula sa powershell invoke-command sa ahente ng SQL Server

Kapag gumagawa ng sarili kong pamamaraan para sa pamamahala ng mga backup sa maraming MS-SQL server, gumugol ako ng maraming oras sa pag-aaral ng mekanismo para sa pagpasa ng mga halaga sa Powershell sa panahon ng mga malalayong tawag, kaya sumusulat ako ng isang paalala sa aking sarili kung sakaling ito ay kapaki-pakinabang sa ibang tao.

Kaya, magsimula tayo sa isang simpleng script at patakbuhin ito nang lokal:

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

Upang magpatakbo ng mga script, gagamitin ko ang sumusunod na CMD file, hindi ko ito isasama sa bawat oras:

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

Sa screen makikita natin ang sumusunod:

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


Ngayon, patakbuhin natin ang parehong script sa pamamagitan ng WSMAN (malayuan):

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

At narito ang resulta:

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

Mahusay, ang Errorlevel ay nawala sa isang lugar, ngunit kailangan nating makuha ang halaga mula sa script! Subukan natin ang sumusunod na disenyo:

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

Ito ay mas kawili-wili. Ang mensahe sa Output ay nawala sa isang lugar:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Ngayon, bilang isang lyrical digression, mapapansin ko na kung sa loob ng isang Powershell function ay isusulat mo ang Write-Output o isang expression lamang nang hindi ito itinatalaga sa anumang variable (at ito ay nagpapahiwatig ng output sa Output channel), at kahit na tumatakbo nang lokal, walang ipapakita sa screen! Ito ay isang kinahinatnan ng arkitektura ng pipeline ng powershell - ang bawat function ay may sariling Output pipeline, isang array ang nilikha para dito, at lahat ng pumapasok dito ay itinuturing na resulta ng pagpapatupad ng function, ang Return operator ay nagdaragdag ng return value sa parehong pipeline bilang huling elemento at naglilipat ng kontrol sa function ng pagtawag. Upang ilarawan, patakbuhin natin ang sumusunod na script nang lokal:

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: "+$_) }

At narito ang resulta:

Main: ParameterValue

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

Ang pangunahing function (script body) ay mayroon ding sariling Output pipeline, at kung patakbuhin natin ang unang script mula sa CMD, ire-redirect ang output sa isang file,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

tapos makikita natin sa screen

ERRORLEVEL=1

at sa file

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

kung gumawa tayo ng katulad na tawag mula sa powershell

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

pagkatapos ay makikita ito sa screen

Out to host.
ExitCode: 1

at sa file

Out to output.
1

Nangyayari ito dahil naglulunsad ang CMD ng powershell, na, sa kawalan ng iba pang mga tagubilin, pinaghahalo ang dalawang thread (Host at Output) at ibinibigay ang mga ito sa CMD, na nagpapadala ng lahat ng natanggap nito sa isang file, at sa kaso ng pagtakbo mula sa powershell, ang dalawang thread na ito ay umiiral nang hiwalay, at ang mga pag-redirect ng simbolo ay nakakaapekto lamang sa Output.

Pagbabalik sa pangunahing paksa, tandaan natin na ang .NET object model sa loob ng powershell ay ganap na umiiral sa loob ng isang computer (isang OS), kapag tumatakbo ang code nang malayuan sa pamamagitan ng WSMAN, ang paglilipat ng mga bagay ay nangyayari sa pamamagitan ng XML serialization, na nagdudulot ng maraming karagdagang interes. sa aming pananaliksik. Ipagpatuloy natin ang ating mga eksperimento sa pamamagitan ng pagpapatakbo ng sumusunod na code:

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

At ito ang mayroon kami sa screen:

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:sqlagentTestOutput3.ps1:3 Π·Π½Π°ΠΊ:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

ERRORLEVEL=0

Mahusay na resulta! Nangangahulugan ito na kapag tumatawag sa Invoke-Command, ang paghahati ng mga pipeline sa dalawang thread (Host at Output) ay pinananatili, na nagbibigay sa amin ng pag-asa para sa tagumpay. Subukan nating mag-iwan lamang ng isang value sa Output stream, kung saan babaguhin natin ang pinakaunang script na pinapatakbo natin nang malayuan:

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

Patakbuhin natin ito ng ganito:

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

at... OO, mukhang panalo na!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Subukan nating alamin kung ano ang nangyari. Tinawag namin ang powershell nang lokal, na tinatawag namang powershell sa remote na computer at pinaandar ang aming script doon. Dalawang stream (Host at Output) mula sa remote na makina ay na-serialize at ipinasa pabalik, habang ang Output stream, na mayroong isang digital na halaga sa loob nito, ay na-convert sa uri ng Int32 at dahil dito ay ipinasa sa receiving side, at ginamit ito ng receiving side. bilang exit code ng caller powershell.

At bilang panghuling pagsusuri, gumawa tayo ng isang hakbang na trabaho sa SQL server na may uri na "Operating system (cmdexec)" na may sumusunod na text:

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

HOORAY! Nakumpleto ang gawain nang may error, text sa log:

ВыполняСтся ΠΎΡ‚ ΠΈΠΌΠ΅Π½ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ: DOMAINagentuser. Out to host. ExitCode: 6.  Код Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ процСсса 6.  Π¨Π°Π³ Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»ΡΡ с ошибкой.

Konklusyon:

  • Iwasan ang paggamit ng Write-Output at pagtukoy ng mga expression nang walang pagtatalaga. Magkaroon ng kamalayan na ang paglipat ng code na ito sa ibang lugar sa script ay maaaring magdulot ng mga hindi inaasahang resulta.
  • Sa mga script na inilaan hindi para sa manu-manong paglunsad, ngunit para sa paggamit sa iyong mga mekanismo ng automation, lalo na para sa mga malayuang tawag sa pamamagitan ng WINRM, gawin ang manu-manong paghawak ng error sa pamamagitan ng Try/Catch, at tiyaking, sa anumang pag-unlad ng mga kaganapan, ang script na ito ay nagpapadala ng eksaktong isang primitive na uri ng halaga . Kung gusto mong makuha ang classic na Errorlevel, dapat na numero ang value na ito.

Pinagmulan: www.habr.com

Magdagdag ng komento