Vērtības atgriešana no PowerShell invoke-command SQL Server aģentam

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=0

Brī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=0

Tagad 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: ReturnValue

Pamata 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=1

bet 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.txt

tad ekrānā būs

Out to host.
ExitCode: 1

bet failā

Out to output.
1

Tas 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=0

Brī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=4

Mēģ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

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster