Vrátenie hodnoty z príkazu powershell invoke-command agentovi SQL Server

Pri vytváraní vlastnej metodiky na správu záloh na viacerých serveroch MS-SQL som strávil veľa času štúdiom mechanizmu odovzdávania hodnôt v Powershell počas vzdialených hovorov, takže si píšem pripomienku pre prípad, že by to bolo užitočné. niekomu inému.

Začnime teda s jednoduchým skriptom a spustite ho lokálne:

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

Na spustenie skriptov použijem nasledujúci súbor CMD, nebudem ho pridávať zakaždým:

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

Na obrazovke uvidíme nasledovné:

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


Teraz spustíme rovnaký skript cez WSMAN (na diaľku):

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

A tu je výsledok:

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

Skvelé, Errorlevel niekam zmizol, ale potrebujeme získať hodnotu zo skriptu! Skúsme nasledujúci dizajn:

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

Toto je ešte zaujímavejšie. Správa vo výstupe niekde zmizla:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Teraz, ako lyrická odbočka, si všimnem, že ak vo funkcii Powershell napíšete Write-Output alebo len výraz bez toho, aby ste ho priradili k akejkoľvek premennej (a to implicitne znamená výstup do výstupného kanála), potom aj keď beží lokálne, na obrazovke sa nič nezobrazí! Je to dôsledok architektúry powershell pipeline – každá funkcia má svoj vlastný výstupný kanál, vytvorí sa pre ňu pole a všetko, čo sa do nej dostane, sa považuje za výsledok vykonania funkcie, operátor Return k tej istej pripočíta návratovú hodnotu pipeline ako posledný prvok a prenáša riadenie na volajúcu funkciu. Pre ilustráciu spustíme nasledujúci skript lokálne:

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

A tu je výsledok:

Main: ParameterValue

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

Hlavná funkcia (telo skriptu) má tiež svoj výstupný kanál a ak spustíme prvý skript z CMD, presmerovanie výstupu do súboru,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

potom uvidíme na obrazovke

ERRORLEVEL=1

a v súbore

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

ak urobíme podobný hovor z powershell

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

potom to bude na obrazovke

Out to host.
ExitCode: 1

a v súbore

Out to output.
1

Deje sa tak preto, že CMD spustí powershell, ktorý pri absencii iných pokynov zmieša dve vlákna (Host a Output) a odovzdá ich CMD, ktoré všetko prijaté pošle do súboru a v prípade spustenia z powershell, tieto dve vlákna existujú oddelene a presmerovania symbolov ovplyvňujú iba výstup.

Vráťme sa k hlavnej téme, pripomeňme si, že objektový model .NET vo vnútri powershell plne existuje v rámci jedného počítača (jedného OS), pri vzdialenom spúšťaní kódu cez WSMAN dochádza k prenosu objektov prostredníctvom XML serializácie, čo prináša množstvo zaujímavostí navyše na náš výskum. Pokračujme v našich experimentoch spustením nasledujúceho kódu:

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

A toto máme na obrazovke:

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

Skvelý výsledok! Znamená to, že pri volaní Invoke-Command je zachované rozdelenie pipeline do dvoch vlákien (Host a Output), čo nám dáva nádej na úspech. Skúsme ponechať v Output streame len jednu hodnotu, pre ktorú zmeníme úplne prvý skript, ktorý spustíme na diaľku:

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

Spustíme to takto:

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

a... ÁNO, vyzerá to na víťazstvo!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Skúsme prísť na to, čo sa stalo. Lokálne sme zavolali powershell, ktorý následne zavolal powershell na vzdialenom počítači a tam spustil náš skript. Dva toky (hostiteľ a výstup) zo vzdialeného stroja boli serializované a odovzdané späť, zatiaľ čo výstupný tok, ktorý mal v sebe jedinú digitálnu hodnotu, bol konvertovaný na typ Int32 a ako taký prešiel na prijímaciu stranu a prijímajúca strana ho použila. ako výstupný kód powershell volajúceho.

A ako poslednú kontrolu vytvorte jednokrokovú úlohu na serveri SQL s typom „Operačný systém (cmdexec)“ s nasledujúcim textom:

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

HURÁ! Úloha dokončená s chybou, text v protokole:

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

Závery:

  • Vyhnite sa používaniu Write-Output a zadávaniu výrazov bez priradenia. Uvedomte si, že presunutie tohto kódu inam v skripte môže viesť k neočakávaným výsledkom.
  • V skriptoch určených nie na manuálne spustenie, ale na použitie vo vašich automatizačných mechanizmoch, najmä pre vzdialené volania cez WINRM, vykonajte manuálne spracovanie chýb pomocou Try/Catch a zabezpečte, aby pri akomkoľvek vývoji udalostí tento skript posielal presne jednu hodnotu primitívneho typu. . Ak chcete získať klasickú Errorlevel, táto hodnota musí byť číselná.

Zdroj: hab.com

Pridať komentár