Vrnitev vrednosti iz powershell invoke-command agentu SQL Server

Ko sem ustvarjal lastno metodologijo za upravljanje varnostnih kopij na več strežnikih MS-SQL, sem porabil veliko časa za preučevanje mehanizma za posredovanje vrednosti ​​​​​v Powershell med oddaljenimi klici, zato si pišem opomnik, če je to koristno nekomu drugemu.

Torej, začnimo s preprostim skriptom in ga zaženimo lokalno:

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

Za izvajanje skriptov bom uporabil naslednjo datoteko CMD, ne bom je vključil vsakič:

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

Na zaslonu bomo videli naslednje:

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


Zdaj pa zaženimo isti skript prek WSMAN (na daljavo):

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

In tukaj je rezultat:

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

Super, Errorlevel je nekam izginil, vendar moramo pridobiti vrednost iz skripta! Poskusimo naslednjo zasnovo:

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

To je še bolj zanimivo. Sporočilo v izhodu je nekam izginilo:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Kot lirično digresijo bom omenil, da če znotraj funkcije Powershell napišete Write-Output ali samo izraz, ne da bi ga dodelili kateri koli spremenljivki (in to implicitno implicira izhod v izhodni kanal), potem tudi ko se izvaja lokalno, na zaslonu se ne bo prikazalo nič! To je posledica arhitekture cevovoda powershell - vsaka funkcija ima svoj izhodni cevovod, zanjo je ustvarjena matrika in vse, kar gre vanj, velja za rezultat izvajanja funkcije, operator Return doda vrnjeno vrednost istemu cevovod kot zadnji element in prenese nadzor na klicno funkcijo. Za ponazoritev zaženimo ta skript lokalno:

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

In tukaj je rezultat:

Main: ParameterValue

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

Glavna funkcija (telo skripta) ima prav tako svoj izhodni cevovod in če zaženemo prvi skript iz CMD, preusmeri izhod v datoteko,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

potem bomo videli na zaslonu

ERRORLEVEL=1

in v datoteki

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

če izvedemo podoben klic iz powershell-a

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

potem bo na zaslonu

Out to host.
ExitCode: 1

in v datoteki

Out to output.
1

To se zgodi zato, ker CMD zažene powershell, ki v odsotnosti drugih navodil zmeša dve niti (Host in Output) in ju odda CMD-ju, ta pa vse, kar je prejel, pošlje v datoteko, v primeru zagona iz powershell-a pa ti dve niti obstajata ločeno in preusmeritve simbola vplivajo samo na izhod.

Če se vrnemo k glavni temi, naj spomnimo, da objektni model .NET znotraj powershell v celoti obstaja v enem računalniku (en OS), ko se koda izvaja na daljavo prek WSMAN, se prenos objektov zgodi s serializacijo XML, kar prinaša veliko dodatnega zanimanja naši raziskavi. Nadaljujmo s poskusi z zagonom naslednje kode:

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

In tole imamo na zaslonu:

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

Odličen rezultat! Pomeni, da se pri klicu Invoke-Command ohrani razdelitev cevovodov na dve niti (Host in Output), kar nam daje upanje na uspeh. Poskusimo pustiti samo eno vrednost v izhodnem toku, za katero bomo spremenili prvi skript, ki ga izvajamo na daljavo:

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

Zaženimo ga takole:

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

in... DA, izgleda kot zmaga!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Poskusimo ugotoviti, kaj se je zgodilo. Lokalno smo poklicali powershell, ki je nato poklical powershell na oddaljenem računalniku in tam izvedel naš skript. Dva toka (gostitelj in izhod) iz oddaljenega stroja sta bila serializirana in poslana nazaj, medtem ko je bil izhodni tok z eno samo digitalno vrednostjo pretvorjen v tip Int32 in kot tak poslan na sprejemno stran, sprejemna stran pa ga je uporabila kot izhodno kodo PowerShell klicatelja.

In kot zadnje preverjanje, ustvarimo opravilo v enem koraku na strežniku SQL z vrsto »Operacijski sistem (cmdexec)« z naslednjim besedilom:

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

URA! Naloga končana z napako, besedilo v dnevniku:

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

Sklepi:

  • Izogibajte se uporabi Write-Output in podajanju izrazov brez dodelitve. Zavedajte se, da lahko premik te kode drugam v skriptu povzroči nepričakovane rezultate.
  • V skriptih, ki niso namenjeni ročnemu zagonu, ampak za uporabo v vaših avtomatizacijskih mehanizmih, zlasti za oddaljene klice prek WINRM, izvedite ročno obravnavanje napak prek Try/Catch in zagotovite, da v vsakem razvoju dogodkov ta skript pošlje točno eno vrednost primitivnega tipa . Če želite dobiti klasično raven napake, mora biti ta vrednost številska.

Vir: www.habr.com

Dodaj komentar