Devolvendo un valor de powershell invoke-command ao axente de SQL Server

Ao crear a miña propia metodoloxía para xestionar copias de seguridade en varios servidores MS-SQL, pasei moito tempo estudando o mecanismo para pasar valores en Powershell durante as chamadas remotas, polo que estou escribindo un recordatorio para min por se fose útil. a outra persoa.

Entón, comecemos cun script sinxelo e executémolo localmente:

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

Para executar scripts, usarei o seguinte ficheiro CMD, non o incluirei cada vez:

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

Na pantalla veremos o seguinte:

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


Agora imos executar o mesmo script a través de WSMAN (remotamente):

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

E aquí está o resultado:

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

Xenial, Errorlevel desapareceu nalgún lugar, pero necesitamos obter o valor do script. Probemos a seguinte construción:

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

Isto é aínda máis interesante. A mensaxe de Saída desapareceu nalgún lugar:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Agora, como unha digresión lírica, notarei que se dentro dunha función de Powershell escribes Write-Output ou só unha expresión sen asignala a ningunha variable (e isto implica implícitamente saída á canle de saída), entón mesmo cando se executa localmente, non se mostrará nada na pantalla! Esta é unha consecuencia da arquitectura de pipeline de powershell: cada función ten o seu propio pipeline de saída, créase unha matriz para ela e todo o que entra nel considérase o resultado da execución da función, o operador Return engade o valor de retorno ao mesmo. pipeline como último elemento e transfire o control á función de chamada. Para ilustralo, executemos o seguinte script localmente:

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

E aquí está o resultado:

Main: ParameterValue

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

A función principal (corpo do script) tamén ten o seu propio pipeline de saída, e se executamos o primeiro script desde CMD, redirixindo a saída a un ficheiro,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

entón veremos na pantalla

ERRORLEVEL=1

e no arquivo

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

se facemos unha chamada similar desde powershell

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

entón estará na pantalla

Out to host.
ExitCode: 1

e no arquivo

Out to output.
1

Isto ocorre porque o CMD lanza powershell, que, a falta doutras instrucións, mestura dous fíos (Host e Output) e entrégaos ao CMD, que envía todo o que recibiu a un ficheiro, e no caso de lanzar desde powershell, estes dous fíos existen por separado e as redireccións de símbolos só afectan á saída.

Volvendo ao tema principal, lembremos que o modelo de obxectos .NET dentro de powershell existe plenamente nun ordenador (un SO), cando se executa código de forma remota a través de WSMAN, a transferencia de obxectos prodúcese a través da serialización XML, o que trae moito interese adicional. á nosa investigación. Continuemos cos nosos experimentos executando o seguinte código:

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

E isto é o que temos na 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 resultado! Significa que ao chamar a Invoke-Command, mantense a división das canalizacións en dous fíos (Host e Output), o que nos da esperanza de éxito. Tentemos deixar só un valor no fluxo de saída, para o que cambiaremos o primeiro script que executamos de forma remota:

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

Imos executalo así:

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

e... SI, parece unha vitoria!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Imos tentar descubrir o que pasou. Chamamos powershell localmente, que á súa vez chamou powershell no ordenador remoto e executou o noso script alí. Dous fluxos (Host e Output) da máquina remota foron serializados e devoltos, mentres que o fluxo de saída, que tiña un único valor dixital nel, converteuse ao tipo Int32 e, como tal, pasou ao lado receptor, e o lado receptor utilizouno. como o código de saída do Powershell da chamada.

E como comprobación final, creemos un traballo dun paso no servidor SQL co tipo "Sistema operativo (cmdexec)" co seguinte texto:

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

HORA! A tarefa completouse cun erro, texto no rexistro:

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

Conclusións:

  • Evite usar Write-Output e especificar expresións sen asignación. Teña en conta que mover este código a outro lugar do script pode producir resultados inesperados.
  • Nos scripts destinados non ao lanzamento manual, senón ao seu uso nos seus mecanismos de automatización, especialmente para chamadas remotas a través de WINRM, faga o tratamento manual de erros mediante Try/Catch e asegúrese de que, en calquera desenvolvemento de eventos, este script envíe exactamente un valor de tipo primitivo. . Se queres obter o nivel de erro clásico, este valor debe ser numérico.

Fonte: www.habr.com

Engadir un comentario