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