Vraćanje vrijednosti iz powershell invoke-command SQL Server agentu

Prilikom kreiranja vlastite metodologije za upravljanje sigurnosnom kopijom na više MS-SQL servera, proveo sam dosta vremena proučavajući mehanizam za prosljeđivanje vrijednosti ​​​​u Powershell-u tokom udaljenih poziva, pa pišem sebi podsjetnik u slučaju da bude koristan nekom drugom.

Dakle, počnimo s jednostavnom skriptom i pokrenimo je 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 pokretanje skripte koristit ću sljedeću CMD datoteku, neću je uključivati ​​svaki put:

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

Na ekranu ćemo vidjeti sljedeće:

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


Sada pokrenimo istu skriptu preko WSMAN-a (daljinski):

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

A evo i rezultata:

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

Odlično, nivo greške je negdje nestao, ali moramo dobiti vrijednost iz skripte! Pokušajmo sa sljedećim dizajnom:

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

Ovo je još zanimljivije. Poruka u Outputu je negdje nestala:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Sada, kao lirsku digresiju, primijetit ću da ako unutar funkcije Powershell napišete Write-Output ili samo izraz bez dodjeljivanja bilo kojoj varijabli (a to implicitno implicira izlaz na izlazni kanal), onda čak i kada se izvodi lokalno, ništa se neće prikazati na ekranu! Ovo je posljedica arhitekture powershell cjevovoda - svaka funkcija ima svoj izlazni cjevovod, za nju se kreira niz, a sve što ulazi u nju smatra se rezultatom izvršavanja funkcije, operator Return dodaje povratnu vrijednost istoj pipeline kao posljednji element i prenosi kontrolu na funkciju koja poziva. Za ilustraciju, pokrenimo sljedeću skriptu 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: "+$_) }

A evo i rezultata:

Main: ParameterValue

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

Glavna funkcija (tijelo skripte) također ima svoj izlazni cjevovod, i ako pokrenemo prvu skriptu iz CMD-a, preusmjeravajući izlaz u datoteku,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

onda ćemo vidjeti na ekranu

ERRORLEVEL=1

i u fajlu

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

ako uputimo sličan poziv iz powershell-a

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

onda će biti na ekranu

Out to host.
ExitCode: 1

i u fajlu

Out to output.
1

To se dešava zato što CMD pokreće powershell, koji, u nedostatku drugih instrukcija, miješa dvije niti (Host i Output) i daje ih CMD-u, koji šalje sve što je primio u datoteku, a u slučaju pokretanja iz powershell-a, ove dvije niti postoje odvojeno, a simbol preusmjeravanja utječe samo na izlaz.

Vraćajući se na glavnu temu, sjetimo se da .NET objektni model unutar powershell-a u potpunosti postoji unutar jednog računala (jedan OS), kada se kod daljinski izvodi preko WSMAN-a, prijenos objekata se odvija kroz XML serijalizaciju, što donosi mnogo dodatnog interesa našem istraživanju. Nastavimo naše eksperimente pokretanjem sljedećeg koda:

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

A evo šta imamo na ekranu:

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čan rezultat! To znači da se prilikom pozivanja Invoke-Command-a održava podjela cjevovoda na dvije niti (Host i Output), što nam daje nadu u uspjeh. Pokušajmo ostaviti samo jednu vrijednost u izlaznom toku, za koji ćemo promijeniti prvu skriptu koju pokrećemo na daljinu:

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

Pokrenimo to ovako:

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

i... DA, izgleda kao pobeda!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Hajde da pokušamo da shvatimo šta se dogodilo. Lokalno smo pozvali powershell, koji je zauzvrat pozvao powershell na udaljenom računaru i tamo izvršio našu skriptu. Dva toka (Host i Output) sa udaljene mašine su serijalizovana i prosleđena nazad, dok je izlazni tok, koji ima jednu digitalnu vrednost u sebi, konvertovan u tip Int32 i kao takav prosleđen na stranu koja prima, a strana koja ga je koristila kao izlazni kod powershell-a pozivaoca.

I kao konačnu provjeru, napravimo posao u jednom koraku na SQL serveru sa tipom “Operativni sistem (cmdexec)” sa sljedećim tekstom:

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

URA! Zadatak je završen sa greškom, tekst u dnevniku:

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

Zaključci:

  • Izbjegavajte korištenje Write-Outputa i specificiranje izraza bez dodjele. Imajte na umu da premještanje ovog koda na drugo mjesto u skripti može dovesti do neočekivanih rezultata.
  • U skriptama koje nisu namijenjene za ručno pokretanje, već za korištenje u vašim mehanizmima automatizacije, posebno za udaljene pozive putem WINRM-a, izvršite ručno rukovanje greškama putem Try/Catch i osigurajte da, u bilo kojem razvoju događaja, ova skripta šalje tačno jednu vrijednost primitivnog tipa . Ako želite da dobijete klasični nivo greške, ova vrednost mora biti numerička.

izvor: www.habr.com

Dodajte komentar