Returnerar ett värde från powershell invoke-command till SQL Server-agent

När jag skapade min egen metod för att hantera säkerhetskopior på flera MS-SQL-servrar, ägnade jag mycket tid åt att studera mekanismen för att skicka värden i Powershell under fjärrsamtal, så jag skriver en påminnelse till mig själv om det är användbart till någon annan.

Så låt oss börja med ett enkelt skript och köra 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)

För att köra skript kommer jag att använda följande CMD-fil, jag kommer inte att inkludera den varje gång:

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

På skärmen ser vi följande:

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


Låt oss nu köra samma skript via WSMAN (på distans):

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

Och här är resultatet för dig:

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

Bra, felnivån har försvunnit någonstans, men vi måste få värdet från skriptet! Låt oss prova följande konstruktion:

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

Det här är ännu mer intressant. Meddelandet i Output har försvunnit någonstans:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Nu, som en lyrisk utvikning, ska jag notera att om du inuti en Powershell-funktion skriver Write-Output eller bara ett uttryck utan att tilldela det till någon variabel (och detta innebär implicit utdata till Output-kanalen), även när du kör lokalt, ingenting kommer att visas på skärmen! Detta är en konsekvens av powershell-pipeline-arkitekturen - varje funktion har sin egen Output-pipeline, en array skapas för den, och allt som går in i den anses vara resultatet av funktionsexekveringen, Return-operatorn lägger till returvärdet till densamma pipeline som det sista elementet och överför kontrollen till den anropande funktionen. För att illustrera, låt oss köra följande skript 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: "+$_) }

Och här är resultatet:

Main: ParameterValue

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

Huvudfunktionen (skriptkropp) har också sin egen utdatapipeline, och om vi kör det första skriptet från CMD, omdirigerar utdata till en fil,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

då får vi se på skärmen

ERRORLEVEL=1

och i filen

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

om vi ringer ett liknande samtal från powershell

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

då kommer det att visas på skärmen

Out to host.
ExitCode: 1

och i filen

Out to output.
1

Detta händer eftersom CMD:n startar powershell, som, i avsaknad av andra instruktioner, blandar två trådar (värd och utgång) och ger dem till CMD, som skickar allt den tagit emot till en fil, och i fallet med start från powershell, dessa två trådar existerar separat, och symbolomdirigeringar påverkar endast Output.

För att återgå till huvudämnet, låt oss komma ihåg att .NET-objektmodellen inuti powershell finns helt och hållet inom en dator (ett operativsystem), när kod körs på distans via WSMAN sker överföringen av objekt genom XML-serialisering, vilket ger mycket extra intresse till vår forskning. Låt oss fortsätta våra experiment genom att köra följande kod:

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

Och det här är vad 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

Jättebra resultat! Det betyder att när man anropar Invoke-Command bibehålls uppdelningen av pipelines i två trådar (Host och Output), vilket ger oss hopp om framgång. Låt oss försöka att bara lämna ett värde i Output-strömmen, för vilket vi kommer att ändra det allra första skriptet som vi kör på distans:

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

Låt oss köra det så här:

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

och... JA, det ser ut som en seger!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Låt oss försöka ta reda på vad som hände. Vi anropade powershell lokalt, som i sin tur anropade powershell på fjärrdatorn och körde vårt skript där. Två strömmar (värd och utgång) från fjärrmaskinen serialiserades och skickades tillbaka, medan utgångsströmmen, som hade ett enda digitalt värde i sig, konverterades till typ Int32 och som sådan skickades till den mottagande sidan, och den mottagande sidan använde den som utgångskoden för uppringarens powershell.

Och som en sista kontroll, låt oss skapa ett enstegsjobb på SQL-servern med typen "Operativsystem (cmdexec)" med följande text:

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

HURRA! Uppgiften slutfördes med ett fel, text i loggen:

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

Slutsatser:

  • Undvik att använda Write-Output och ange uttryck utan tilldelning. Tänk på att flytta den här koden någon annanstans i skriptet kan ge oväntade resultat.
  • I skript som inte är avsedda för manuell lansering, utan för användning i dina automatiseringsmekanismer, särskilt för fjärrsamtal via WINRM, gör manuell felhantering via Try/Catch, och se till att detta skript skickar exakt ett primitivt typvärde vid utveckling av händelser. . Om du vill få den klassiska Errorlevel måste detta värde vara numeriskt.

Källa: will.com

Lägg en kommentar