Returnerer en værdi fra powershell invoke-command til SQL Server-agent

Da jeg lavede min egen metode til at administrere sikkerhedskopier på flere MS-SQL-servere, brugte jeg meget tid på at studere mekanismen til at overføre værdier i Powershell under fjernopkald, så jeg skriver en påmindelse til mig selv, hvis det er nyttigt til en anden.

Så lad os starte med et simpelt script og køre det lokalt:

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

For at køre scripts vil jeg bruge følgende CMD-fil, jeg vil ikke inkludere den hver gang:

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

På skærmen vil vi se følgende:

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


Lad os nu køre det samme script via WSMAN (fjernt):

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

Og her er resultatet:

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

Fantastisk, fejlniveau er forsvundet et eller andet sted, men vi skal have værdien fra scriptet! Lad os prøve følgende konstruktion:

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

Dette er endnu mere interessant. Meddelelsen i Output er forsvundet et sted:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Nu, som en lyrisk digression, vil jeg bemærke, at hvis du inde i en Powershell-funktion skriver Write-Output eller blot et udtryk uden at tildele det til nogen variabel (og dette implicit indebærer output til Output-kanalen), så selv når du kører lokalt, intet vil blive vist på skærmen! Dette er en konsekvens af powershell-pipeline-arkitekturen - hver funktion har sin egen output-pipeline, der oprettes et array til det, og alt, hvad der går ind i det, betragtes som resultatet af funktionsudførelsen, Return-operatøren tilføjer returværdien til den samme pipeline som det sidste element og overfører kontrol til den kaldende funktion. For at illustrere det, lad os køre følgende script lokalt:

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

Og her er resultatet:

Main: ParameterValue

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

Hovedfunktionen (scriptbody) har også sin egen outputpipeline, og hvis vi kører det første script fra CMD, omdirigerer outputtet til en fil,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

så vil vi se på skærmen

ERRORLEVEL=1

og i filen

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

hvis vi laver et lignende opkald fra powershell

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

så vil det være på skærmen

Out to host.
ExitCode: 1

og i filen

Out to output.
1

Dette sker, fordi CMD'en starter powershell, som, i mangel af andre instruktioner, blander to tråde (Host og Output) og giver dem til CMD'en, som sender alt, hvad den har modtaget til en fil, og i tilfælde af lancering fra powershell, disse to tråde eksisterer hver for sig, og symbolomdirigeringer påvirker kun output.

For at vende tilbage til hovedemnet, så lad os huske, at .NET-objektmodellen inde i powershell eksisterer fuldt ud inden for én computer (et OS), når koden køres eksternt via WSMAN, sker overførslen af ​​objekter gennem XML-serialisering, hvilket bringer en masse ekstra interesse. til vores forskning. Lad os fortsætte vores eksperimenter ved at køre følgende kode:

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

Og dette er, hvad vi har på skærmen:

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

Flot resultat! Det betyder, at når man kalder Invoke-Command, opretholdes opdelingen af ​​pipelines i to tråde (Host og Output), hvilket giver os håb om succes. Lad os prøve kun at efterlade én værdi i Output-strømmen, for hvilken vi vil ændre det allerførste script, som vi kører eksternt:

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

Lad os køre det sådan her:

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

og... JA, det ligner en sejr!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Lad os prøve at finde ud af, hvad der skete. Vi kaldte powershell lokalt, som igen kaldte powershell på fjerncomputeren og udførte vores script der. To streams (Host og Output) fra den eksterne maskine blev serialiseret og sendt tilbage, mens Output-strømmen, der havde en enkelt digital værdi i sig, blev konverteret til type Int32 og som sådan videregivet til den modtagende side, og den modtagende side brugte den som udgangskoden for den opkaldende powershell.

Og som en sidste kontrol, lad os oprette et et-trinsjob på SQL-serveren med typen "Operating system (cmdexec)" med følgende tekst:

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

HURRA! Opgaven afsluttet med en fejl, tekst i loggen:

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

Konklusioner:

  • Undgå at bruge Write-Output og at angive udtryk uden tildeling. Vær opmærksom på, at flytning af denne kode et andet sted i scriptet kan give uventede resultater.
  • I scripts, der ikke er beregnet til manuel lancering, men til brug i dine automatiseringsmekanismer, især til fjernopkald via WINRM, skal du udføre manuel fejlhåndtering via Try/Catch, og sikre, at i enhver udvikling af hændelser sender dette script præcis én primitiv typeværdi . Hvis du vil have det klassiske Errorlevel, skal denne værdi være numerisk.

Kilde: www.habr.com

Tilføj en kommentar