Érték visszaadása a powershell invoke-command parancsból az SQL Server ügynöknek

Amikor létrehoztam saját módszertant több MS-SQL szerveren végzett biztonsági mentések kezelésére, sok időt töltöttem azzal, hogy tanulmányozzam az értékek Powershellben való átadásának mechanizmusát távoli hívások során, ezért írok magamnak egy emlékeztetőt, hátha hasznos lesz. valaki másnak.

Tehát kezdjük egy egyszerű szkripttel, és futtassuk helyben:

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

A szkriptek futtatásához a következő CMD-fájlt fogom használni, nem teszem fel minden alkalommal:

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

A képernyőn a következőket fogjuk látni:

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


Most futtassuk ugyanazt a szkriptet a WSMAN-on keresztül (távol):

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

És íme az eredmény neked:

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

Remek, az Errorlevel valahol eltűnt, de az értéket a scriptből kell levonnunk! Próbáljuk ki a következő dizájnt:

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

Ez még érdekesebb. Az Output üzenet valahol eltűnt:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Most lírai kitérőként megjegyzem, hogy ha egy Powershell függvényen belül Write-Output vagy csak egy kifejezést írunk anélkül, hogy bármilyen változóhoz hozzárendelnénk (és ez implicit módon a kimeneti csatornára való kimenetet is jelenti), akkor még akkor is, ha lokálisan fut, semmi nem jelenik meg a képernyőn! Ez a powershell pipeline architektúra következménye - minden függvénynek megvan a saját kimeneti csővezetéke, egy tömb jön létre hozzá, és minden, ami bekerül, a függvény végrehajtásának eredményének számít, a Return operátor hozzáadja a visszatérési értéket ugyanahhoz. pipeline az utolsó elem, és átadja a vezérlést a hívó függvénynek. A szemléltetés kedvéért futtassuk helyben a következő szkriptet:

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

És íme az eredmény:

Main: ParameterValue

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

A fő függvénynek (szkript törzsének) is van saját Output pipeline, és ha az első szkriptet CMD-ből futtatjuk, a kimenetet átirányítjuk egy fájlba,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

akkor látni fogjuk a képernyőn

ERRORLEVEL=1

és a fájlban

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

ha hasonló hívást indítunk a powershelltől

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

akkor megjelenik a képernyőn

Out to host.
ExitCode: 1

és a fájlban

Out to output.
1

Ez azért van így, mert a CMD elindítja a powershell-t, amely egyéb utasítások hiányában két szálat (Host és Output) összekever, és átadja a CMD-nek, amely mindent, amit kapott, egy fájlba küldi, és powershell-ről való futtatás esetén ez a két szál külön létezik, és a szimbólum átirányítások csak a kimenetre vannak hatással.

Visszatérve a fő témához, ne feledjük, hogy a powershellben lévő .NET objektummodell teljes egészében egy számítógépen (egy operációs rendszeren) belül létezik; amikor a kódot távolról WSMAN-on keresztül futtatjuk, az objektumok XML szerializáción keresztül kerülnek átvitelre, ami további érdekességet jelent számunkra. kutatás. Folytassuk kísérleteinket a következő kód futtatásával:

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

És ez van a képernyőn:

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

Remek eredmény! Ez azt jelenti, hogy az Invoke-Command meghívásakor a csővezetékek két szálra (Host és Output) való felosztása megmarad, ami reményt ad a sikerre. Próbáljunk csak egy értéket hagyni az Output adatfolyamban, amihez a legelső távolról futtatott szkriptet módosítjuk:

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

Futtassuk így:

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

és... IGEN, győzelemnek tűnik!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Próbáljuk meg kitalálni, mi történt. Helyileg meghívtuk a powershell-t, ami viszont a távoli számítógépen a powershell-t, és ott végrehajtotta a szkriptünket. A távoli gépről származó két adatfolyamot (Host és Output) sorba rendeztük és visszaküldtük, míg az Output streamet, amelyben egyetlen digitális érték volt, Int32 típusúra konvertálták és így továbbították a fogadó oldalra, és a fogadó oldal azt használta. mint a hívó powershell kilépési kódja.

Utolsó ellenőrzésként hozzunk létre egy egylépcsős feladatot az SQL szerveren „Operating system (cmdexec)” típussal a következő szöveggel:

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

HURRÁ! A feladat hibával fejeződött be, szöveg a naplóban:

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

Következtetések:

  • Kerülje a Write-Output használatát és a kifejezések hozzárendelés nélküli megadását. Ne feledje, hogy ennek a kódnak a szkriptben máshová történő áthelyezése váratlan eredményeket eredményezhet.
  • A nem kézi indításra, hanem az automatizálási mechanizmusokban való használatra szánt szkriptekben, különösen a WINRM-en keresztüli távoli hívásoknál, végezzen manuális hibakezelést a Try/Catch segítségével, és győződjön meg arról, hogy az események bármilyen fejlesztése során ez a szkript pontosan egy primitív típusú értéket küld. . Ha a klasszikus hibaszintet szeretné elérni, ennek az értéknek numerikusnak kell lennie.

Forrás: will.com

Hozzászólás