Da jeg oprettede 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 til powershell under fjernopkald, så jeg skriver en påmindelse til mig selv, hvis det er nyttigt for en anden.
Så lad os tage det enkleste script først 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 give 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=0Fantastisk, 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]Det er endnu mere interessant her. Hele outputtet i Output forsvandt et sted:
Out to host.
ExitCode: 2
ERRORLEVEL=0Nu, 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, vil der ikke blive vist noget på skærmen! Dette er en konsekvens af pipeline-arkitekturen i PowerShell - hver funktion har sin egen output-pipeline, der oprettes et array til den, og alt, hvad der kommer ind i den, betragtes som resultatet af funktionsudførelsen, Return-operatøren tilføjer returværdien til den samme pipeline som det sidste element og overfører kontrollen til den kaldende funktion. For at illustrere det, lad os udfø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: ReturnValueHovedfunktionen (scriptets krop) 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=1og 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.txtså vil der være på skærmen
Out to host.
ExitCode: 1og i filen
Out to output.
1Dette sker fordi CMD kører powershell, som, hvis ikke andet er angivet, blander de to streams (Host og Output) og giver dem til CMD, som sender alt det modtager til filen, mens når de køres fra powershell, er de to streams adskilte, og omdirigeringssymbolet 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 rammerne af én computer (et OS), når koden køres eksternt via WSMAN, sker overførslen af objekter via XML-serialisering, hvilket bringer en masse yderligere 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=0Fremragende resultat! Det betyder, at når man kalder Invoke-Command, bevares opdelingen af pipelines i to strømme (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 ser ud til at være en sejr!
Out to host.
ExitCode: 4
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
ERRORLEVEL=4Lad os prøve at finde ud af, hvad der skete med os. 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, og Output-strømmen, hvis den havde én digital værdi, blev konverteret til Int32-typen og videregivet til den modtagende side i denne form, og den modtagende side brugte den som exit-koden for den kaldende powershell.
Og som en sidste kontrol, lad os oprette server En SQL-opgave i ét trin af 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! 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. Bemærk venligst, at flytning af denne kode til en anden placering i dit script kan give uventede resultater.
- I scripts, der ikke er beregnet til manuel eksekvering, 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 uanset hvad der sker, sender dette script præcis én værdi af en primitiv type til Output-strømmen. Hvis du vil have det klassiske Errorlevel, skal denne værdi være numerisk.
Kilde: www.habr.com
