Vrátanie hodnoty z powershell invoke-command agentovi SQL-Server

Pri vytváraní svojej vlastnej metodiky správy záloh na viacerých serveroch MS-SQL som strávil veľa času štúdiom mechanizmu prenosu hodnôt v PowerShell pri vzdialených volaniach, preto píšem pre seba poznámku, možno sa to niekomu inému zíde.

Takže začneme s najjednoduchším skriptom a spustíme 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 budem používať nasledujúci CMD súbor, ktorý nebudem každýkrát opakovať:

@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 tento istý skript cez WSMAN (vzdialene):

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

Skvele, Errorlevel niekde zmizol, ale predsa potrebujeme získať hodnotu zo skriptu! Skúšame nasledujúcu konštrukciu:

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

Tu je to ešte zaujímavejšie. Celý výstup do Output niekde zmizol:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Teraz ako krátke zamyslenie poznamenám, že ak vo vnútri funkcie Powershell napíšete Write-Output alebo len výraz bez priradenia k nejakej premenej (čo implicitne naznačuje výstup do kanála Output), tak ani pri lokálnom spustení sa na obrazovke nič nezobrazí! Toto je dôsledok konštrukcie PowerShell — každá funkcia má vlastný kanál Output, pre ktorý sa vytvára pole, a všetko, čo do neho prijde, sa považuje za výsledok vykonania funkcie, operátor Return pridáva návratovú hodnotu ako poslednú položku do toho istého kanála a predáva ovládanie volajúcej funkcii. Na ilustráciu vykonáme lokálne nasledujúci skript:

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 jeho 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 vlastný kanál Output, a ak spustíme prvý skript z CMD a presmerujeme výstup do súboru,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

na obrazovke uvidíme

ERRORLEVEL=1

a v súbore

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

ak urobíme podobné volanie z powershell

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

na obrazovke bude

Out to host.
ExitCode: 1

a v súbore

Out to output.
1

To sa deje, pretože CMD spúšťa powershell, ktorý pri absencii iných pokynov zmieša dva prúdy (Host a Output) a pošle ich do CMD, ktorý zapisuje všetko, čo dostal, do súboru, zatiaľ čo pri spustení z powershell existujú tieto dva prúdy oddelene a znak presmerovania ovplyvňuje len Output.

Vráťme sa k hlavnej téme a pripomeňme, že objektový model .NET v rámci powershell existuje plne v rámci jedného počítača (jedného OS); pri vzdialenom spustení kódu cez WSMAN sa prenos objektov uskutočňuje cez XML-serializáciu, čo prináša veľa dodatočného záujmu do našich skúmaní. Pokračujeme experimentovaním 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
Nepodarilo sa previesť argument "exitCode" so hodnotou: "System.Object[]" na "SetShouldExit" do typu "System.Int32": "Nepodarilo sa previesť hodnotu "System.Object[]" typu "System.Object[]" do typu "System.Int32"."
D:sqlagentTestOutput3.ps1:3 znak:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

ERRORLEVEL=0

Skvelý výsledok! Znamená to, že pri volaní Invoke-Command sa zachová delenie potrubí na dva prúdy (Host a Output), čo nám dáva nádej na úspech. Skúsime nechať v prúde Output len jednu hodnotu, na čo zmeníme prvý skript, ktorý spúšťame vzdialene:

$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 ho takto:

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

a… ÁNO, zdá sa, že je to víťazstvo!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Pokúsme sa zistiť, čo sa nám stalo. Lokálne sme spustili PowerShell, ktorý spustil PowerShell na diaľkovom počítači a vykonal tam náš skript. Dva prúdy (Host a Output) z diaľkového zariadenia boli serializované a odoslané späť, pričom prúd Output bol, ak obsahoval jedno číselné hodnotenie, konvertovaný na typ Int32 a odoslaný prijímajúcej strane, ktorá ho použila ako kód ukončenia volaného PowerShellu.

A ako poslednú kontrolu vytvoríme na serveri SQL úlohu s jedným krokom typu „Operačný systém (cmdexec)“ s takýmto textom:

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

HURÁ! Úloha skončila chybou, text v denníku:

Spúšťa sa pod užívateľským menom: DOMAINagentuser. Out to host. ExitCode: 6. Kód ukončenia procesu 6. Krok skončil chybou.

Vývody:

  • Vyhnite sa používaniu Write-Output a uvádzaniu výrazov bez priradenia. Pamätajte, že prenášanie tohto kódu na iné miesto skriptu môže viesť k neočakávaným výsledkom.
  • V skriptoch, ktoré nie sú určené na ručné spustenie, ale na použitie vo vašich automatizačných mechanizmoch, najmä pre diaľkové volania cez WINRM, vykonávajte ručné spracovanie chýb pomocou Try/Catch a zabezpečte, aby za akýchkoľvek okolností tento skript odoslal do prúdu Output presne jednu hodnotu primitívneho typu. Ak chcete získať klasický Errorlevel — táto hodnota musí byť číselná.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster