Izstrādājot savu rezerves kopēšanas metodi uz vairākiem MS-SQL serveriem, es pavadīju daudz laika, izpētot, kā tiek nodotas vērtības PowerShell attālinātajos izsaukumos, tāpēc rakstu šo piezīmi sev, varbūt kādam citam tas noderēs.
Sāksim ar visvienkāršāko skriptu un palaidīsim to lokāli:
$exitcode = $args[0]
Write-Host 'Out to host.'
Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)Skriptu palaišanai izmantošu nākamo CMD failu, ikreiz to nepārkopēšu:
@Echo OFF
PowerShell .TestOutput1.ps1 1
ECHO ERRORLEVEL=%ERRORLEVEL%Ekrānā mēs redzēsim sekojošo:
Out to host.
Out to output.
ExitCode: 1
1
ERRORLEVEL=1
Tagad palaidīsim to pašu skriptu caur WSMAN (attālināti):
Invoke-Command -ComputerName . -ScriptBlock { & 'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]Un, lūk, rezultāts:
Out to host.
Out to output.
ExitCode: 2
2
ERRORLEVEL=0Brīnišķīgi, Errorlevel kaut kur pazudis, bet mums taču ir nepieciešams saņemt vērtību no skripta! Mēģināsim nākamo konstrukciju:
$res=Invoke-Command -ComputerName . -ScriptBlock { & 'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]Te vēl interesantāk. Visi izvadījumi kaut kur pazuduši:
Out to host.
ExitCode: 2
ERRORLEVEL=0Tagad kā lirisku atkāpi atzīmēšu, ka, ja PowerShell funkcijā jūs uzrakstīsiet Write-Output vai vienkārši izteicienu bez piešķiršanas kādai mainīgajai (tas implicit nozīmē izvadīt uz Output kanālu), tad pat lokālā palaišanas gadījumā nekas ekrānā netiks parādīts! Tas ir PowerShell cauruļu arhitektūras sekas — katrai funkcijai ir sava Output cauruļu sistēma, tiek izveidots masīvs, un viss, kas tajā nokļūst, tiek uzskatīts par funkcijas izpildes rezultātu; operators Return pievieno atgriezto vērtību šim pašam masīvam kā pēdējo elementu un nodod kontroli izsaucējfunkcijai. Lai ilustrētu, veiksim šādu lokālu skriptu:
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: " + $_) }
Un šeit ir tā rezultāts:
Main: ParameterValue
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
2
Main: Function: ParameterValue
Main: ReturnValuePamata funkcija (skriptu korpuss) arī satur savu Output kanālu, un, ja mēs izsauksim pirmo skriptu CMD, novirzot izeju uz failu,
PowerShell .TestOutput1.ps1 1 > TestOutput1.txt
tad ekrānā mēs redzēsim
ERRORLEVEL=1bet failā
Out to host.
Out to output.
ExitCode: 1
1
ja mēs veiksim līdzīgu pieprasījumu no PowerShell
PS D:sqlagent> .TestOutput1.ps1 1 > TestOutput1.txttad ekrānā būs
Out to host.
ExitCode: 1bet failā
Out to output.
1Tas notiek tāpēc, ka CMD palaiž PowerShell, kas, nepievienojot citas instrukcijas, sajauc divus plūsmus (Host un Output) un nosūta tos CMD, kas ieraksta visus saņemtos datus failā, bet, ja to palaiž no PowerShell, šie divi plūsmu pastāv atsevišķi, un novirzīšanas simbols ietekmē tikai Output.
Atgriežoties pie pamattēmas, atcerēsimies, ka .NET objekta modelis iekš PowerShell pilnībā eksistē vienas datorā (vienā OS) ietvaros; attālināti izpildot kodu caur WSMAN, objektu pārsūtīšana notiek caur XML serializāciju, kas pievieno daudz papildu interesantu informācijas mūsu izpētei. Turpināsim eksperimentus ar sekojošo kodu:
$res=Invoke-Command -ComputerName . -ScriptBlock { 'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$res.GetType()
$host.SetShouldExit($res)
Un šis ir tas, ko mēs redzam ekrānā:
Out to host.
ExitCode: 3
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Nevar pārvērst argumentu "exitCode", kur vērtība ir: "System.Object[]", "SetShouldExit" tipam "System.Int32": "Nevar pārvērst "System.Object[]" veida "System.Object[]" uz tipa "System.Int32"."
D:sqlagentTestOutput3.ps1:3 zīme:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
ERRORLEVEL=0Brīnišķīgs rezultāts! Tas nozīmē, ka, izsaucot Invoke-Command, kanālu dalīšana divās plūsmās (Host un Output) tiek saglabāta, kas dod mums cerības uz panākumiem. Mēģināsim atstāt plūsmā Output tikai vienu vērtību, tāpēc mainīsim visus, ko vispirms palaižam attālināti:
$exitcode = $args[0]
Write-Host 'Out to host.'
#Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)
Izpildīsim to šādi:
$res=Invoke-Command -ComputerName . -ScriptBlock { 'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$host.SetShouldExit($res)
un… JĀ, šķiet, tas ir uzvarējis!
Out to host.
ExitCode: 4
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
ERRORLEVEL=4Mēģināsim saprast, kas mums ir noticis. Mēs izsaucām lokāli PowerShell, kas, savukārt, izsauca PowerShell attālinātajā datorā un izpildīja tur mūsu skriptu. Divi plūsmu (Host un Output) no attālinātā datora tika serilizēti un nosūtīti atpakaļ, pie tam plūsma Output, ja tajā bija viens skaitlisks vērtējums, tika pārvērsta par Int32 tipu un šādā veidā nodota saņēmējai pusei, bet saņēmēja puse to izmantoja kā izsaukuma PowerShell kodu.
Un kā pēdējo pārbaudi izveidosim uz serveri SQL uzdevumu ar vienu soli, tipa „Operētājsistēma (cmdexec)”, ar šādu tekstu:
PowerShell -NonInteractive -NoProfile "$res=Invoke-Command -ComputerName BACKUPSERVER -ConfigurationName SQLAgent -ScriptBlock {&'D:sqlagentTestOutput1.ps1' 6}; $host.SetShouldExit($res)"URAAA! Uzdevums ir pabeigts ar kļūdu, teksts žurnālā:
Izpildīts kā lietotājs: DOMAINagentuser. Out to host. ExitCode: 6. Procesa iznākšanas kods 6. Solis beidzās ar kļūdu.
Secinājumi:
- Izvairieties no Write-Output izmantošanas un izteikumu norādīšanas bez piešķiršanas. Atcerieties, ka šī koda pārvietošana uz citu vietu skriptā var radīt negaidītus rezultātus.
- Skriptu, kas nav paredzēts manuālai palaišanai, bet gan izmantošanai jūsu automatizācijas mehānismos, īpaši attālinātajos izsaukumos caur WINRM, veidojiet ar manuālu kļūdu apstrādi, izmantojot Try/Catch, un panāciet, lai šis skripts jebkurā notikumu attīstībā nosūtītu plūsmā Output tieši vienu primitīvā tipa vērtību. Ja vēlaties iegūt klasisko Errorlevel — šai vērtībai jābūt skaitliskai.
Avots: habr.com
