Vertės grąžinimas iš powershell invoke-command į SQL serverio agentą

Kurdamas savo atsarginių kopijų tvarkymo keliuose MS-SQL serveriuose metodiką, daug laiko praleidau tyrinėdamas reikšmių perdavimo mechanizmą Powershell nuotolinių skambučių metu, todėl rašau sau priminimą, jei tai būtų naudinga. kam nors kitam.

Taigi, pradėkime nuo paprasto scenarijaus ir paleiskite jį vietoje:

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

Norėdami paleisti scenarijus, naudosiu šį CMD failą, jo neįtrauksiu kiekvieną kartą:

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

Ekrane pamatysime:

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


Dabar paleiskite tą patį scenarijų per WSMAN (nuotoliniu būdu):

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

Ir štai rezultatas:

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

Puiku, klaidų lygis kažkur dingo, bet mes turime gauti vertę iš scenarijaus! Pabandykime tokią konstrukciją:

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

Tai dar įdomiau. Išvesties pranešimas kažkur dingo:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Dabar, kaip lyrinį nukrypimą, pažymėsiu, kad jei Powershell funkcijoje rašote Write-Output arba tiesiog išraišką, nepriskirdami jos jokiam kintamajam (ir tai netiesiogiai reiškia išvestį į išvesties kanalą), tada net ir veikiant lokaliai, nieko nebus rodoma ekrane! Tai yra powershell konvejerinės architektūros pasekmė – kiekviena funkcija turi savo Output konvejerį, jai sukuriamas masyvas ir viskas, kas į jį patenka, laikoma funkcijos vykdymo rezultatu, operatorius Return prideda grąžinamąją reikšmę prie to paties. konvejerį kaip paskutinį elementą ir perduoda valdymą iškvietimo funkcijai. Norėdami iliustruoti, paleiskite šį scenarijų vietoje:

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

Ir štai rezultatas:

Main: ParameterValue

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

Pagrindinė funkcija (scenarijaus korpusas) taip pat turi savo išvesties konvejerį, o jei paleidžiame pirmąjį scenarijų iš CMD, nukreipiame išvestį į failą,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

tada pamatysime ekrane

ERRORLEVEL=1

ir byloje

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

jei panašiai skambintume iš powershell

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

tada jis bus ekrane

Out to host.
ExitCode: 1

ir byloje

Out to output.
1

Taip atsitinka todėl, kad CMD paleidžia powershell, kuri, nesant kitų instrukcijų, sumaišo dvi gijas (Host ir Output) ir perduoda jas CMD, kuri siunčia viską, ką gavo į failą, o paleidus iš powershell, šios dvi gijos egzistuoja atskirai, o simbolių peradresavimai veikia tik išvestį.

Grįžtant prie pagrindinės temos, prisiminkime, kad .NET objekto modelis powershell viduje pilnai egzistuoja viename kompiuteryje (vienoje OS), paleidus kodą nuotoliniu būdu per WSMAN, objektų perkėlimas vyksta per XML serializavimą, o tai suteikia daug papildomo susidomėjimo. mūsų tyrimams. Tęskime eksperimentus paleisdami šį kodą:

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

Ir štai ką mes matome ekrane:

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

Puikus rezultatas! Tai reiškia, kad iškviečiant Invoke-Command išlaikomas konvejerių padalijimas į dvi gijas (Host ir Output), o tai suteikia vilčių sulaukti sėkmės. Pabandykime išvesties sraute palikti tik vieną reikšmę, kuriai pakeisime patį pirmąjį scenarijų, kurį paleisime nuotoliniu būdu:

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

Paleiskite jį taip:

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

ir... TAIP, atrodo kaip pergalė!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Pabandykime išsiaiškinti, kas atsitiko. Mes iškvietėme powershell vietinį, kuris savo ruožtu iškvietė powershell nuotoliniame kompiuteryje ir ten įvykdė mūsų scenarijų. Du srautai (Host ir Output) iš nuotolinio įrenginio buvo serijiniai ir perduodami atgal, o išvesties srautas, turintis vieną skaitmeninę reikšmę, buvo konvertuotas į Int32 tipą ir perduotas priimančiajai pusei, o priimančioji jį naudojo. kaip skambinančiojo powershell išėjimo kodas.

Ir kaip galutinį patikrinimą, sukurkime vieno žingsnio užduotį SQL serveryje su tipu „Operacinė sistema (cmdexec)“ su tokiu tekstu:

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

HOORAY! Užduotis atlikta su klaida, tekstas žurnale:

Выполняется от имени пользователя: DOMAINagentuser. Out to host. ExitCode: 6.  Код завершения процесса 6.  Шаг завершился с ошибкой.

Išvados:

  • Nenaudokite Write-Output ir nenurodykite išraiškų be priskyrimo. Atminkite, kad šio kodo perkėlimas į kitą scenarijaus vietą gali sukelti netikėtų rezultatų.
  • Skriptuose, skirtuose ne rankiniam paleidimui, o naudoti automatizavimo mechanizmuose, ypač nuotoliniams skambučiams per WINRM, atlikite rankinį klaidų tvarkymą naudodami „Try/Catch“ ir užtikrinkite, kad bet kokio įvykių plėtojimo metu šis scenarijus siųs tiksliai vieną primityviojo tipo reikšmę. . Jei norite gauti klasikinį klaidų lygį, ši reikšmė turi būti skaitinė.

Šaltinis: www.habr.com

Добавить комментарий