Returnerer en verdi fra powershell invoke-command til SQL Server-agent

Når jeg opprettet min egen metodikk for å administrere sikkerhetskopier på flere MS-SQL-servere, brukte jeg mye tid på å studere mekanismen for å overføre verdier i Powershell under eksterne samtaler, så jeg skriver en påminnelse til meg selv i tilfelle det er nyttig til noen andre.

Så la oss starte med et enkelt skript og kjø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 å kjøre skript, vil jeg bruke følgende CMD-fil, jeg vil ikke inkludere den hver gang:

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

På skjermen vil vi se følgende:

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


La oss nå kjøre det samme skriptet via WSMAN (eksternt):

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

Flott, feilnivået har forsvunnet et sted, men vi må hente verdien fra skriptet! La oss prøve følgende design:

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

Dette er enda mer interessant. Meldingen i Output har forsvunnet et sted:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Nå, som en lyrisk digresjon, vil jeg legge merke til at hvis du i en Powershell-funksjon skriver Write-Output eller bare et uttrykk uten å tilordne det til noen variabel (og dette implisitt innebærer utgang til Output-kanalen), så selv når du kjører lokalt, ingenting vil vises på skjermen! Dette er en konsekvens av powershell-pipeline-arkitekturen - hver funksjon har sin egen Output-pipeline, en matrise opprettes for den, og alt som går inn i den regnes som resultatet av funksjonsutførelsen, Return-operatøren legger til returverdien til den samme pipeline som siste element og overfører kontrollen til den anropende funksjonen. For å illustrere, la oss kjøre følgende 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: "+$_) }

Og her er resultatet:

Main: ParameterValue

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

Hovedfunksjonen (script body) har også sin egen Output pipeline, og hvis vi kjører det første skriptet fra CMD, omdirigerer utdataene til en fil,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

så får vi se på skjermen

ERRORLEVEL=1

og i filen

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

hvis vi foretar et lignende anrop fra powershell

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

så vil det være på skjermen

Out to host.
ExitCode: 1

og i filen

Out to output.
1

Dette skjer fordi CMD lanserer powershell, som, i mangel av andre instruksjoner, blander to tråder (vert og utgang) og gir dem til CMD, som sender alt den mottok til en fil, og i tilfelle det kjøres fra powershell, disse to trådene eksisterer hver for seg, og symbolomdirigeringer påvirker kun Output.

For å gå tilbake til hovedemnet, la oss huske at .NET-objektmodellen inne i powershell eksisterer fullt ut i én datamaskin (ett OS), når du kjører kode eksternt via WSMAN, skjer overføringen av objekter gjennom XML-serialisering, noe som gir mye ekstra interesse. til vår forskning. La oss fortsette eksperimentene våre ved å kjø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 hva vi har på skjermen:

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

Flott resultat! Det betyr at når du kaller Invoke-Command, opprettholdes oppdelingen av rørledningene i to tråder (Host og Output), noe som gir oss håp om suksess. La oss prøve å la bare være én verdi i utdatastrømmen, som vi vil endre det aller første skriptet som vi kjører eksternt for:

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

La oss kjøre det slik:

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

og... JA, det ser ut som en seier!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

La oss prøve å finne ut hva som skjedde. Vi kalte powershell lokalt, som igjen kalte powershell på den eksterne datamaskinen og utførte skriptet vårt der. To strømmer (vert og utgang) fra den eksterne maskinen ble serialisert og sendt tilbake, mens utgangsstrømmen, med en enkelt digital verdi i seg, ble konvertert til type Int32 og som sådan sendt til mottakersiden, og mottakersiden brukte den som utgangskoden til oppringerens powershell.

Og som en siste sjekk, la oss lage en ett-trinns jobb på SQL-serveren med typen "Operativsystem (cmdexec)" med følgende tekst:

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

HURRA! Oppgaven fullført med en feil, tekst i loggen:

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

Konklusjoner:

  • Unngå å bruke Write-Output og spesifisere uttrykk uten tilordning. Vær oppmerksom på at flytting av denne koden et annet sted i skriptet kan gi uventede resultater.
  • I skript som ikke er ment for manuell lansering, men for bruk i automatiseringsmekanismene dine, spesielt for eksterne anrop via WINRM, gjør manuell feilhåndtering via Try/Catch, og sørg for at, i enhver utvikling av hendelser, sender dette skriptet nøyaktig én primitiv typeverdi . Hvis du vil ha det klassiske feilnivået, må denne verdien være numerisk.

Kilde: www.habr.com

Legg til en kommentar