ÅterlĂ€mna vĂ€rdet 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 spenderade jag mycket tid pÄ att studera mekanismen för överföring av vÀrden i PowerShell vid fjÀrranrop, sÄ jag skriver en pÄminnelse till mig sjÀlv, ifall den kan vara till nytta för nÄgon annan.

SÄ, lÄt oss börja med ett enkelt skript och köra det lokalt:

$exitcode = $args[0]
Write-Host 'Ut till vÀrden.'
Write-Output 'Ut till utdata.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

För att köra skripten kommer jag att anvÀnda följande CMD-fil, jag kommer inte att Äterge den varje gÄng:

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

PÄ skÀrmen kommer vi att se följande:

Ut till vÀrden.
Ut till utdata.
ExitCode: 1
1
ERRORLEVEL=1


Nu kör vi samma skript via WSMAN (fjÀrrÄtkomst):

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

Och hÀr Àr resultatet:

Ut till vÀrden.
Ut till utdata.
ExitCode: 2
2
ERRORLEVEL=0

UtmÀrkt, Errorlevel har försvunnit nÄgonstans, men vi behöver ÀndÄ fÄ vÀrdet frÄn skriptet! Vi provar nÀsta konstruktion:

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

HÀr blir det intressantare. All utskrift till utdata har pÄ nÄgot sÀtt försvunnit:

Ut till vÀrden.
ExitCode: 2
ERRORLEVEL=0

Nu som en lyrisk avstickare, noterar jag att om du inom en PowerShell-funktion skriver Write-Output eller helt enkelt uttrycket utan att tilldela det till nÄgon variabel (vilket implicit innebÀr att det skrivs ut till utdata), sÄ kommer inget att skrivas ut pÄ skÀrmen, Àven vid lokal körning! Detta Àr en följd av PowerShells rörledningsarkitektur - varje funktion har sin egen utdata-pipeline, dÀr ett array skapas, och allt som hamnar dÀr rÀknas som resultatet av funktionskörningen. Return-operatören lÀgger till det returnerade vÀrdet som sista element i denna pipeline och överför kontrollen till den anropande funktionen. För att illustrera kommer vi att köra följande skript lokalt:

Function Write-Log {
  Param( [Parameter(Mandatory=$false, ValueFromPipeline=$true)] [String[]] $OutString = "`r`n" )
  Write-Output ("Funktion: "+$OutString)
  Return "ReturnValue"
}
Write-Output ("Huvud: "+"ParameterValue")
$res = Write-Log "ParameterValue"
$res.GetType()
$res.Length
$res | Foreach-Object { Write-Host ("Huvud: "+$_) }

Och hÀr Àr dess resultat:

Huvud: ParameterValue

IsPublic IsSerial Namn                                     BasType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
2
Huvud: Funktion: ParameterValue
Huvud: ReturnValue

Huvudfunktionen (kroppen av skriptet) har ocksÄ sin egen utdata-pipeline, och om vi kör det första skriptet frÄn CMD och omdirigerar utdata till en fil,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

Det vi ser pÄ skÀrmen

ERRORLEVEL=1

och i filen

Ut till vÀrden.
Ut till utdata.
ExitCode: 1
1

om vi gör ett liknande anrop frÄn powershell

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

sÄ kommer det att visas pÄ skÀrmen

Ut till vÀrden.
ExitCode: 1

och i filen

Ut till utdata.
1

Detta hÀnder eftersom CMD startar powershell, som utan andra instruktioner blandar de tvÄ strömmarna (Host och Output) och skickar dem till CMD, som skickar allt den fÄtt till filen, medan i fallet med körning frÄn powershell existerar de tvÄ strömmarna separat, och omdirigeringssymbolen pÄverkar endast Output.

Åter till huvudĂ€mnet, kom ihĂ„g att objektmodellen .NET inom powershell fungerar fullt ut pĂ„ en enda dator (ett operativsystem), vid fjĂ€rranrop av kod via WSMAN sker överföringen av objekt genom XML-serialisering, vilket tillför mycket extra intresse i vĂ„ra forskningar. Vi fortsĂ€tter experimentera med 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 hÀr Àr vad vi ser pÄ skÀrmen:

Ut till vÀrden.

ExitCode: 3

IsPublic IsSerial Namn                                     BasType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
Det gÄr inte att konvertera argumentet "exitCode", med vÀrdet: "System.Object[]", för "SetShouldExit" till typ "System.Int32": "Det gÄr inte att konvertera vÀrdet "System.Object[]" av typen "System.Object[]" till typ "System
.Int32"."
D:sqlagentTestOutput3.ps1:3 tecken:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

ERRORLEVEL=0

Ett utmÀrkt resultat! Det betyder att nÀr vi anropar Invoke-Command behÄlls uppdelningen av rör till tvÄ strömmar (Host och Output), vilket ger oss hopp om framgÄng. LÄt oss försöka hÄlla kvar endast ett vÀrde i Output-strömmen, för vilket vi Àndrar det allra första skriptet som vi kör pÄ distans:

$exitcode = $args[0]
Write-Host 'Ut till vÀrden.'
#Write-Output 'Ut till utdata.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

Vi kör det sÄ hÀr:

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

och
 JA, det verkar vara en seger!

Ut till vÀrden.
ExitCode: 4

IsPublic IsSerial Namn                                     BasType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


ERRORLEVEL=4

LÄt oss försöka förstÄ vad som har hÀnt. Vi kallade lokalt pÄ PowerShell, som Ä sin sida anropade PowerShell pÄ den fjÀrrstyrda datorn och körde vÄrt skript dÀr. TvÄ strömmar (Host och Output) frÄn den fjÀrrstyrda maskinen serialiserades och skickades tillbaka, dÀr strömmen Output, om den innehöll ett enda numeriskt vÀrde, omvandlades till Int32-typ och skickades i detta format till mottagande part, och mottagande part anvÀnde det som avslutningskod för den anropade PowerShell.

Och som sista kontroll skapar vi pÄ servern SQL-jobb av ett steg 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)"

JIPPI! Jobbet avslutades med ett fel, texten i loggen:

Utförs under namnet: DOMAINagentuser. Ut till vÀrden. ExitCode: 6. Processens avslutningskod 6. Steget avslutades med ett fel.

Slutsatser:

  • Undvik att anvĂ€nda Write-Output och att ange uttryck utan tilldelning. Kom ihĂ„g att flytta denna kod till en annan plats i skriptet kan leda till ovĂ€ntade resultat.
  • I skript som inte Ă€r avsedda för manuell körning, utan för anvĂ€ndning i dina automatiseringssystem, sĂ€rskilt för fjĂ€rranrop via WINRM, gör manuell felhantering genom Try/Catch, och se till att detta skript skickar exakt ett vĂ€rde av primĂ€rtyp till Output-strömmen oavsett utfall. Om du vill fĂ„ den klassiska Errorlevel — detta vĂ€rde mĂ„ste vara numeriskt.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster