In wearde weromjaan fan powershell invoke-command nei SQL Server-agint

By it meitsjen fan myn eigen metodyk foar it behearen fan backups op meardere MS-SQL-tsjinners, haw ik in protte tiid bestege oan it bestudearjen fan it meganisme foar it trochjaan fan wearden yn Powershell tidens oproppen op ôfstân, dus ik skriuw mysels in herinnering foar it gefal dat it nuttich is oan in oar.

Dat, lit ús begjinne mei in ienfâldich skript en it lokaal útfiere:

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

Om skripts út te fieren, sil ik it folgjende CMD-bestân brûke, ik sil it net elke kear opnimme:

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

Op it skerm sille wy it folgjende sjen:

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


Litte wy no itselde skript útfiere fia WSMAN (op ôfstân):

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

En hjir is it resultaat:

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

Geweldich, Errorlevel is earne ferdwûn, mar wy moatte de wearde fan it skript krije! Litte wy it folgjende ûntwerp besykje:

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

Dit is noch nijsgjirriger. It berjocht yn Utfier is earne ferdwûn:

Out to host.
ExitCode: 2
ERRORLEVEL=0

No, as in lyryske digression, sil ik opmerke dat as jo binnen in Powershell-funksje Write-Output skriuwe of gewoan in ekspresje sûnder it oan ien fariabele te jaan (en dit ymplisyt ymplisyt útfier nei it útfierkanaal), dan sels as jo lokaal rinne, neat sil werjûn wurde op it skerm! Dit is in gefolch fan 'e powershell-pipeline-arsjitektuer - elke funksje hat syn eigen útfierpipeline, in array wurdt dêrfoar makke, en alles wat deryn giet wurdt beskôge as it resultaat fan' e útfiering fan 'e funksje, de Return-operator foeget de returnwearde ta oan deselde pipeline as it lêste elemint en stjoert kontrôle oer nei de opropfunksje. Om te yllustrearjen, litte wy it folgjende skript lokaal útfiere:

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: "+$_) }

En hjir is it resultaat:

Main: ParameterValue

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

De haadfunksje (skriptlichem) hat ek in eigen útfierpipeline, en as wy it earste skript fan CMD útfiere, omliede de útfier nei in bestân,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

dan sille wy sjen op it skerm

ERRORLEVEL=1

en yn de triem

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

as wy in ferlykbere oprop meitsje fan powershell

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

dan sil it op it skerm wêze

Out to host.
ExitCode: 1

en yn de triem

Out to output.
1

Dit bart om't de CMD powershell lanseart, dy't, by it ûntbrekken fan oare ynstruksjes, twa threaden mingt (Host en Output) en jout se oan 'e CMD, dy't alles stjoert dat it ûntfongen is nei in bestân, en yn it gefal fan lansearring fan powershell, dizze twa triedden besteane apart, en it symboal trochferwizings allinne effekt op Output.

Werom nei it haadûnderwerp, lit ús betinke dat it .NET-objektmodel binnen powershell folslein bestiet binnen ien kompjûter (ien OS), by it útfieren fan koade op ôfstân fia WSMAN, komt de oerdracht fan objekten troch XML-serialisaasje, wat in protte ekstra ynteresse bringt. oan ús ûndersyk. Litte wy ús eksperiminten trochgean troch de folgjende koade út te fieren:

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

En dit is wat wy op it skerm hawwe:

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

Geweldich resultaat! It betsjut dat by it oproppen fan Invoke-Command de ferdieling fan 'e pipelines yn twa triedden (Host en Output) bewarre wurdt, wat ús hope foar sukses jout. Litte wy besykje mar ien wearde yn 'e útfierstream te litten, wêrfoar wy it earste skript feroarje dat wy op ôfstân útfiere:

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

Litte wy it sa útfiere:

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

en... JA, it liket op in oerwinning!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Litte wy besykje út te finen wat der bard is. Wy neamden powershell lokaal, dy't op syn beurt powershell neamde op 'e kompjûter op ôfstân en dêr ús skript útfierde. Twa streamen (host en útfier) ​​fan 'e masine op ôfstân waarden serialisearre en weromjûn, wylst de útfierstream, mei in inkele digitale wearde deryn, waard omboud ta type Int32 en as sadanich trochjûn nei de ûntfangende kant, en de ûntfangende kant brûkte it as de útgongskoade fan 'e beller powershell.

En as lêste kontrôle, litte wy in ienstaps taak oanmeitsje op 'e SQL-tsjinner mei it type "Bestjoeringssysteem (cmdexec)" mei de folgjende tekst:

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

HOERA! De taak foltôge mei in flater, tekst yn it log:

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

Konklúzjes:

  • Mije it brûken fan Write-Output en it opjaan fan útdrukkingen sûnder opdracht. Wês bewust dat it ferpleatsen fan dizze koade earne oars yn it skript ûnferwachte resultaten kin produsearje.
  • Yn skripts dy't net bedoeld binne foar manuele lansearring, mar foar gebrûk yn jo automatisearringsmeganismen, foaral foar oproppen op ôfstân fia WINRM, doch manuele flaterôfhanneling fia Try/Catch, en soargje derfoar dat, yn elke ûntwikkeling fan eveneminten, dit skript krekt ien primitive typewearde stjoert . As jo ​​​​it klassike Errorlevel wolle krije, moat dizze wearde numerike wêze.

Boarne: www.habr.com

Add a comment