Kthimi i vlerës nga powershell invoke-command te SQL Server agenti

GjatĂ« krijimit tĂ« njĂ« metodologjie pĂ«r menaxhimin e kopjeve rezervĂ« nĂ« shumĂ« serverĂ« MS-SQL, kam shpenzuar shumĂ« kohĂ« duke studiuar mekanizmin e transmetimit tĂ« vlerave nĂ« powershell gjatĂ« thirrjeve tĂ« largĂ«ta, prandaj po shkruaj njĂ« shĂ«nim pĂ«r veten, ndoshta do t’i vijĂ« nĂ« ndihmĂ« edhe dikujt tjetĂ«r.

Pra, le të marrim për fillim një skenar të thjeshtë dhe ta ekzekutojmë atë lokal:

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

Për të ekzekutuar skripte do të përdor këtë skedë CMD, nuk do ta paraqes çdo herë:

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

Në ekran do të shohim si vijon:

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


Tani le të ekzekutojmë këtë skript përmes WSMAN (në distancë):

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

Dhe ja rezultati:

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

E mrekullueshme, Errorlevel ka humbur diçka, por ne duhet të marrim vlerën nga skripti! Provoni këtë ndërtim:

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

Këtu është edhe më interesante. E gjithë dalja në Output ka humbur:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Tani, si një përjashtim poetik, vë në dukje se nëse brenda një funksioni Powershell shkruani Write-Output ose thjesht një shprehje pa e caktuar si ndonjë variabël (dhe kjo nënkupton në mënyrë implicite daljen në kanalin Output), asgjë nuk do të shfaqet në ekran edhe gjatë ekzekutimit lokal! Kjo është një pasojë e arkitekturës me tubacion të powershell - çdo funksion ka tubacionin e tij Output, për të cilin krijohet një array, dhe gjithçka që hyjnë aty konsiderohet si rezultat i ekzekutimit të funksionit, operatori Return shton vlerën e kthimit si elementin e fundit në këtë tubacion dhe e kalon kontrollin te funksioni që e ka thirrur. Për të ilustruar, do të ekzekutojmë lokalisht skriptin e mëposhtëm:

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

Dhe ja rezultati i tij:

Main: ParameterValue

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

Funksioni kryesor (trupi i skriptit) gjithashtu ka një tub outputi, dhe nëse ne ekzekutojmë skriptin e parë nga CMD, duke redaktuar daljen në një skedar,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

ne do të shohim në ekran

ERRORLEVEL=1

ndërsa në skedar

Dalja në host.
Dalja në output.
ExitCode: 1
1

nëse bëjmë një thirrje të ngjashme nga powershell

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

atëherë në ekran do të shohim

Dalja në host.
ExitCode: 1

ndërsa në skedar

Dalja në output.
1

Kjo ndodh sepse CMD e ekzekuton powershell, i cili në mungesë të udhëzimeve të tjera i përzier dy rrjedhat (Host dhe Output) dhe i dorëzon ato në CMD, i cili dërgon në skedar gjithçka që merr, dhe në rastin e ekzekutimit nga powershell këto dy rrjedha ekzistojnë veçmas, dhe simboli i redirektimit ndikon vetëm në Output.

Duke u rikthyer në temën kryesore, kujtojmë se modeli objektiv .NET brenda powershell ekziston plotësisht brenda një kompjuteri (një SO), kur ekzekutohet kodi në distancë përmes WSMAN, transferimi i objekteve ndodh përmes XML-serializimit, që sjell shumë interes në kërkimet tona. Le të vazhdojmë eksperimentet duke ekzekutuar kodin e mëposhtëm:

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

Dhe ky është rezultati që kemi në ekran:

Dalja në host.

ExitCode: 3

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
Nuk mund të konvertohet argumenti "exitCode", me vlerën: "System.Object[]", për "SetShouldExit" në tipin "System.Int32": "Nuk mund të konvertohet vlera "System.Object[]" e tipit "System.Object[]" në tipin "System.Int32".
D:sqlagentTestOutput3.ps1:3 shenja:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + Kategoria e informacionit          : NotSpecified: (:) [], MethodException
    + ID e plotë e gabimit të cilësisë : MethodArgumentConversionInvalidCastArgument

ERRORLEVEL=0

Një rezultat fantastik! Kjo do të thotë se kur thirret Invoke-Command, ndarja e tubacioneve në dy rrjedha (Host dhe Output) ruhet, që na jep shpresë për sukses. Le të përpiqemi të lëmë vetëm një vlerë në rrjedhën Output, për këtë do të ndryshojmë skriptin e parë që ekzekutojmë në distancë:

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

Le të ekzekutojmë atë kështu:

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

dhe... PO, duket se është një fitore!

Dalja në host.
ExitCode: 4

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


ERRORLEVEL=4

Do të përpiqemi të kuptojmë se çfarë ndodhi. Ne thirrëm powershell lokal, i cili nga ana e tij e thirri powershell në kompjuterin e largët dhe ekzekutoi skriptin tonë atje. Dy rrjedha (Host dhe Output) nga makina e largët u serilizuan dhe u dërguan mbrapsht, ndërsa rrjedha Output, kur kishte një vlerë të vetme numerike, u konvertua në tipin Int32 dhe në këtë formë u dërgua në palën që priste, e cila e përdori atë si kod përfundimi të powershell-it të thirrur.

Dhe si një kontroll të fundit, do të krijojmë në server SQL një detyrë me një hap me llojin "Sistem operativ (cmdexec)" me tekstin e tillë:

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

URAH! Detyra përfundoi me një gabim, teksti në regjistër:

Ekzekutohet nga emri i përdoruesit: DOMAINagentuser. Më në fund. ExitCode: 6. Kodi përfundimtar i procesit 6. Hapi përfundoi me një gabim.

Përfundimet:

  • Shmangni pĂ«rdorimin e Write-Output dhe pĂ«rcaktimin e shprehive pa caktim. Mbani mend se transfertimi i kĂ«tij kodi nĂ« njĂ« vend tjetĂ«r tĂ« skriptit mund tĂ« sjellĂ« rezultate tĂ« papritura.
  • NĂ« skriptet qĂ« nuk janĂ« tĂ« destinuara pĂ«r ekzekutim manual, por pĂ«r t'u pĂ«rdorur nĂ« mekanizmat tuaja tĂ« automatizimit, veçanĂ«risht pĂ«r thirrjet e largĂ«ta pĂ«rmes WINRM, bĂ«ni pĂ«rpunim manual tĂ« gabimeve pĂ«rmes Try/Catch, dhe sigurohuni qĂ« nĂ« çdo rast ky skript tĂ« dĂ«rgojĂ« nĂ« rrjedhĂ«n Output njĂ« vlerĂ« tĂ« vetme primitive. NĂ«se doni tĂ« merrni Errorlevel klasike — kjo vlerĂ« duhet tĂ« jetĂ« numerike.

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster