Devolució d'un valor de l'ordre d'invocació de powershell a l'agent SQL Server

Quan vaig crear la meva pròpia metodologia per gestionar les còpies de seguretat en diversos servidors MS-SQL, vaig passar molt de temps estudiant el mecanisme per passar valors a Powershell durant les trucades remotes, així que m'escric un recordatori per si és útil. a algú altre.

Per tant, comencem amb un script senzill i executem-lo localment:

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

Per executar scripts, utilitzaré el següent fitxer CMD, no l'inclouré cada vegada:

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

A la pantalla veurem el següent:

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


Ara executem el mateix script mitjançant WSMAN (remotament):

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

I aquí teniu el resultat:

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

Genial, Errorlevel ha desaparegut en algun lloc, però hem d'obtenir el valor de l'script! Provem el següent disseny:

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

Això és encara més interessant. El missatge de la sortida ha desaparegut en algun lloc:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Ara, com a digressió lírica, notaré que si dins d'una funció Powershell escrius Write-Output o només una expressió sense assignar-la a cap variable (i això implica implícitament la sortida al canal de sortida), fins i tot quan s'executa localment, no es mostrarà res a la pantalla! Aquesta és una conseqüència de l'arquitectura de la canalització de powershell: cada funció té la seva pròpia canalització de sortida, es crea una matriu per a ella i tot el que hi entra es considera el resultat de l'execució de la funció, l'operador de retorn afegeix el valor de retorn al mateix. pipeline com a darrer element i transfereix el control a la funció de trucada. Per il·lustrar-ho, executem el següent script localment:

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 aquí teniu el resultat:

Main: ParameterValue

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

La funció principal (cos de l'script) també té el seu propi canal de sortida, i si executem el primer script des de CMD, redirigint la sortida a un fitxer,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

llavors ho veurem a la pantalla

ERRORLEVEL=1

i a l'arxiu

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

si fem una trucada similar des de Powershell

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

llavors estarà a la pantalla

Out to host.
ExitCode: 1

i a l'arxiu

Out to output.
1

Això passa perquè el CMD llança powershell, que, a falta d'altres instruccions, barreja dos fils (Host i Output) i els dóna al CMD, que envia tot el que ha rebut a un fitxer, i en el cas de llançar-se des de powershell, aquests dos fils existeixen per separat i les redireccions de símbol només afecten la sortida.

Tornant al tema principal, recordem que el model d'objectes .NET dins de powershell existeix completament dins d'un ordinador (un sistema operatiu), quan s'executa codi de manera remota mitjançant WSMAN, la transferència d'objectes es produeix mitjançant la serialització XML, la qual cosa aporta molt d'interès addicional. a la nostra recerca. Continuem els nostres experiments executant el codi següent:

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

I això és el que tenim a la pantalla:

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

Gran resultat! Vol dir que quan es crida a Invoke-Command, es manté la divisió de les canalitzacions en dos fils (amfitrió i sortida), cosa que ens dóna esperança d'èxit. Intentem deixar només un valor al flux de sortida, per al qual canviarem el primer script que executem de manera remota:

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

Executem-ho així:

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

i... SÍ, sembla una victòria!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Intentem esbrinar què va passar. Vam cridar powershell localment, que al seu torn va anomenar powershell a l'ordinador remot i hi va executar el nostre script. Es van serialitzar i tornar a passar dos fluxos (amfitrió i sortida) de la màquina remota, mentre que el flux de sortida, que tenia un únic valor digital, es va convertir al tipus Int32 i, com a tal, es va passar al costat receptor i el costat receptor l'utilitzava. com el codi de sortida del Powershell de la trucada.

I com a comprovació final, creem un treball d'un sol pas al servidor SQL amb el tipus "Sistema operatiu (cmdexec)" amb el text següent:

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

HORA! La tasca s'ha completat amb un error, text al registre:

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

Conclusions:

  • Eviteu utilitzar Write-Output i especificar expressions sense assignació. Tingueu en compte que moure aquest codi a un altre lloc de l'script pot produir resultats inesperats.
  • En els scripts no destinats al llançament manual, sinó per al seu ús en els vostres mecanismes d'automatització, especialment per a trucades remotes mitjançant WINRM, feu la gestió manual d'errors mitjançant Try/Catch i assegureu-vos que, en qualsevol desenvolupament d'esdeveniments, aquest script envia exactament un valor de tipus primitiu. . Si voleu obtenir el nivell d'error clàssic, aquest valor ha de ser numèric.

Font: www.habr.com

Afegeix comentari