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

Prilikom izrade vlastite metodologije za upravljanje sigurnosnim kopijama na više MS-SQL poslužitelja, proveo sam dosta vremena proučavajući mehanizam za prosljeđivanje vrijednosti u Powershellu tijekom udaljenih poziva, pa pišem podsjetnik za sebe u slučaju da bude korisno 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 skripti 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 putem WSMAN-a (daljinski):

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

I evo rezultata:

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

Super, Errorlevel je negdje nestao, ali moramo dobiti vrijednost iz skripte! Isprobajmo sljedeći dizajn:

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

Ovo je još zanimljivije. Poruka u izlazu 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 da ga dodijelite bilo kojoj varijabli (a to implicitno implicira izlaz na izlazni kanal), tada čak i kada se izvodi lokalno, ništa se neće prikazati na ekranu! To je posljedica arhitekture powershell cjevovoda - svaka funkcija ima svoj izlazni cjevovod, za nju se kreira niz, a sve što uđe u njega smatra se rezultatom izvršenja funkcije, Return operator dodaje povratnu vrijednost istoj cjevovod kao zadnji element i prenosi kontrolu na pozivnu funkciju. 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: "+$_) }

I evo 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, a 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 datoteku

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

ako napravimo 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 datoteku

Out to output.
1

To se događa jer CMD pokreće powershell koji u nedostatku drugih instrukcija miješa dvije dretve (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 preusmjeravanja simbola utječu samo na izlaz.

Vraćajući se na glavnu temu, prisjetimo se da .NET objektni model unutar powershell-a u potpunosti postoji unutar jednog računala (jednog OS-a), pri pokretanju koda na daljinu preko WSMAN-a, prijenos objekata se događa kroz XML serijalizaciju, što donosi puno dodatnih 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 ovo 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

Sjajan 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 za uspjeh. Pokušajmo ostaviti samo jednu vrijednost u izlaznom toku, za koju ćemo promijeniti prvu skriptu koju pokrenemo 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 pobjeda!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Pokušajmo shvatiti što se dogodilo. Lokalno smo pozvali powershell, koji je zauzvrat pozvao powershell na udaljenom računalu i tamo izvršio našu skriptu. Dva streama (Host i Output) s udaljenog stroja serijalizirana su i proslijeđena natrag, dok je Output stream, koji ima jednu digitalnu vrijednost u sebi, pretvoren u tip Int32 i kao takav proslijeđen strani primatelja, a strana primatelja koristila ga je kao izlazni kod powershell-a pozivatelja.

I kao posljednju provjeru, kreirajmo posao u jednom koraku na SQL poslužitelju s tipom "Operacijski sustav (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 dovršen s pogreškom, tekst u dnevniku:

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

Zaključak:

  • Izbjegavajte korištenje Write-Outputa i navođenje izraza bez dodjele. Imajte na umu da premještanje ovog koda negdje drugdje u skripti može proizvesti neočekivane rezultate.
  • U skriptama namijenjenim ne za ručno pokretanje, već za upotrebu u vašim mehanizmima automatizacije, posebno za udaljene pozive putem WINRM-a, izvršite ručno rukovanje pogreškama putem Try/Catch i osigurajte da, u bilo kojem razvoju događaja, ova skripta šalje točno jednu vrijednost primitivnog tipa . Ako želite dobiti klasični Errorlevel, ova vrijednost mora biti numerička.

Izvor: www.habr.com

Dodajte komentar