Arvon palauttaminen powershell-invoke-komennosta SQL Server -agentille

Kun luon omaa menetelmääni useiden MS-SQL-palvelimien varmuuskopioiden hallintaan, käytin paljon aikaa arvojen välitysmekanismin tutkimiseen Powershellissä etäpuheluiden aikana, joten kirjoitan itselleni muistutuksen, jos siitä on hyötyä. jollekin muulle.

Joten aloitetaan yksinkertaisella skriptillä ja suoritetaan se paikallisesti:

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

Käytän komentosarjojen suorittamiseen seuraavaa CMD-tiedostoa, en sisällytä sitä joka kerta:

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

Näytöllä näemme seuraavaa:

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


Suoritetaan nyt sama komentosarja WSMANin kautta (etä):

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

Ja tässä tulos sinulle:

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

Hienoa, Errorlevel on kadonnut jonnekin, mutta meidän on saatava arvo käsikirjoituksesta! Kokeillaan seuraavaa mallia:

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

Tämä on vielä mielenkiintoisempaa. Viesti Outputista on kadonnut jonnekin:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Nyt lyyrisenä poikkeuksena huomautan, että jos Powershell-funktion sisällä kirjoitat Write-Output tai vain lausekkeen ilman, että määrität sitä mihinkään muuttujaan (ja tämä tarkoittaa implisiittisesti lähtöä Output-kanavalle), silloin jopa paikallisesti suoritettaessa, näytöllä ei näy mitään! Tämä on seurausta powershell-liukuhihnaarkkitehtuurista - jokaisella funktiolla on oma Output liukuhihna, sille luodaan taulukko ja kaikki siihen menevä katsotaan funktion suorituksen tuloksena, Return-operaattori lisää palautusarvon samaan. liukuhihna viimeisenä elementtinä ja siirtää ohjauksen kutsuvaan funktioon. Suoritetaan seuraava komentosarja paikallisesti.

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

Ja tässä tulos:

Main: ParameterValue

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

Päätoiminnolla (skriptin rungolla) on myös oma Output-liukuhihna, ja jos suoritamme ensimmäisen skriptin CMD:stä, ohjaamme lähdön tiedostoon,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

sitten näemme ruudulla

ERRORLEVEL=1

ja tiedostossa

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

jos soitamme vastaavan puhelun powershellistä

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

sitten se tulee näytölle

Out to host.
ExitCode: 1

ja tiedostossa

Out to output.
1

Tämä tapahtuu, koska CMD käynnistää powershellin, joka muiden ohjeiden puuttuessa sekoittaa kaksi säiettä (Host ja Output) ja antaa ne CMD:lle, joka lähettää kaiken vastaanottamansa tiedostoon, ja jos käynnistetään powershellistä, nämä kaksi säiettä ovat olemassa erikseen, ja symbolien uudelleenohjaukset vaikuttavat vain ulostuloon.

Palataksemme pääaiheeseen, muistetaan, että powershellin sisällä oleva .NET-objektimalli on täysin olemassa yhdessä tietokoneessa (yksi käyttöjärjestelmä), kun ajettaessa koodia etänä WSMAN:n kautta, objektien siirto tapahtuu XML-serialisoinnin kautta, mikä tuo paljon lisäkiinnostusta. tutkimukseemme. Jatketaan kokeiluja suorittamalla seuraava koodi:

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

Ja tämä on mitä meillä on näytöllä:

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

Hieno tulos! Se tarkoittaa, että kutsuttaessa Invoke-Commandia putkien jako kahteen säikeeseen (Host ja Output) säilyy, mikä antaa meille toivoa menestyksestä. Yritetään jättää vain yksi arvo Output-virtaan, jolle muutamme ensimmäistä etäajettavaa skriptiä:

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

Ajetaan se näin:

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

ja... KYLLÄ, se näyttää voitolta!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Yritetään selvittää mitä tapahtui. Kutsuimme paikallisesti powershelliä, joka puolestaan ​​kutsui powershelliä etätietokoneessa ja suoritti komentosarjamme siellä. Kaksi etäkoneen virtaa (Host ja Output) sarjoitettiin ja lähetettiin takaisin, kun taas Output-virta, jossa oli yksi digitaalinen arvo, muutettiin tyypiksi Int32 ja siirrettiin sellaisenaan vastaanottavalle puolelle, ja vastaanottava puoli käytti sitä. soittajan powershellin poistumiskoodina.

Ja viimeisenä tarkistuksena luodaan SQL-palvelimelle yksivaiheinen työ, jonka tyyppi on "Käyttöjärjestelmä (cmdexec)" ja jossa on seuraava teksti:

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

HURRAA! Tehtävä suoritettu virheellä, teksti lokissa:

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

Päätelmät:

  • Vältä Write-Output-toiminnon käyttöä ja lausekkeiden määrittämistä ilman määritystä. Huomaa, että tämän koodin siirtäminen muualle komentosarjassa voi tuottaa odottamattomia tuloksia.
  • Skripteissä, joita ei ole tarkoitettu manuaaliseen käynnistykseen, vaan käytettäväksi automaatiomekanismeissa, erityisesti WINRM:n kautta tapahtuvissa etäpuheluissa, suorita manuaalinen virheenkäsittely Try/Catch-toiminnolla ja varmista, että tämä komentosarja lähettää kaikissa tapahtumien kehityksessä täsmälleen yhden primitiivisen tyypin arvon. . Jos haluat saada klassisen virhetason, tämän arvon on oltava numeerinen.

Lähde: will.com

Lisää kommentti