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=0UtmÀ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=0Nu 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: ReturnValueHuvudfunktionen (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=1och 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.txtsÄ kommer det att visas pÄ skÀrmen
Ut till vÀrden.
ExitCode: 1och i filen
Ut till utdata.
1Detta 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=0Ett 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=4LÄ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
