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=0E 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=0Tani, 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: ReturnValueFunksioni 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=1ndë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.txtatëherë në ekran do të shohim
Dalja në host.
ExitCode: 1ndërsa në skedar
Dalja në output.
1Kjo 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=0Një 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=4Do 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ë сервере 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
