Amikor saját módszertani megközelítést dolgoztam ki az MS-SQL szerverek biztonsági mentésének kezelésére, rengeteg időt töltöttem a powershell távoli hívások során a változók átadásának mechanizmusának tanulmányozásával, ezért írom ezt a magamnak szóló emlékeztetőt, hátha másnak is hasznos lesz.
Tehát először vegyünk egy egyszerű szkriptet, és futtassuk le helyben:
$exitcode = $args[0]
Write-Host 'Kimenet a hostra.'
Write-Output 'Kimenet a kimenetre.'
Write-Host ('Kilépési kód: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)A szkriptek futtatásához a következő CMD-fájlt fogom használni, nem fogom minden alkalommal leírni:
@Echo OFF
PowerShell .TestOutput1.ps1 1
ECHO ERRORLEVEL=%ERRORLEVEL%A képernyőn a következőt fogjuk látni:
Kimenet a hostra.
Kimenet a kimenetre.
Kilépési kód: 1
1
ERRORLEVEL=1
Most futtassuk ezt a szkriptet a WSMAN segítségével (távolról):
Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]És itt van az eredmény:
Kimenet a hostra.
Kimenet a kimenetre.
Kilépési kód: 2
2
ERRORLEVEL=0Kiváló, az Errorlevel valahova eltűnt, de nekünk szükségünk van a szkriptből származó értékre! Próbáljuk ki a következő konstrukciót:
$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]Itt még érdekesebb. Az Output-ba küldött teljes kimenet valahová eltűnt:
Kimenet a hostra.
Kilépési kód: 2
ERRORLEVEL=0Most, mint lírai eltérés, megjegyzem, hogy ha a Powershell függvényen belül kiírja a Write-Output parancsot, vagy csak egy kifejezést használ, amelyet nem rendel hozzá egy változóhoz (ez implicit módon a kimeneti csatornára való kiírást jelenti), akkor még helyi futtatásnál sem fog semmi megjelenni a képernyőn! Ez a powershell csöves architektúrájának következménye — minden függvény saját kimeneti csatornával rendelkezik, amihez egy tömb jön létre, és amit beleírnak, azt a függvény végrehajtásának eredményeként kezelik, a Return operátor az utolsó elemeként adja hozzá a visszatérési értéket ennek a csatornának, és átadja az irányítást a hívó függvénynek. Illusztrációként futtassuk le a következő szkriptet helyi módon:
Function Write-Log {
Param( [Parameter(Mandatory=$false, ValueFromPipeline=$true)] [String[]] $OutString = "`r`n" )
Write-Output ("Függvény: "+$OutString)
Return "Visszatérési érték"
}
Write-Output ("Fő: "+"ParameterValue")
$res = Write-Log "ParameterValue"
$res.GetType()
$res.Length
$res | Foreach-Object { Write-Host ("Fő: "+$_) }
És itt van az eredménye:
Fő: ParameterValue
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
2
Fő: Függvény: ParameterValue
Fő: Visszatérési értékA fő függvény (a szkript törzse) szintén rendelkezik saját kimeneti csatornával, és ha az első szkriptet a CMD-ből futtatjuk, átirányítva a kimenetet egy fájlba,
PowerShell .TestOutput1.ps1 1 > TestOutput1.txt
A képernyőn ezt fogjuk látni
ERRORLEVEL=1és a fájlban
Kimenet a gazdára.
Kimenet a kimenetre.
Kilépési Kód: 1
1
ha hasonló hívást végzünk powershell-ből
PS D:sqlagent> .TestOutput1.ps1 1 > TestOutput1.txta képernyőn ez fog megjelenni
Kimenet a gazdára.
Kilépési Kód: 1és a fájlban
Kimenet a kimenetre.
1Ez azért történik, mert a CMD elindítja a powershell-t, amely egyéb utasítások hiányában összekeveri a két áramlást (Host és Kimenet), és átadja őket a CMD-nek, ami minden kapott adatot a fájlba küld, míg a powershell-ből történő indításkor ez a két áramlás külön létezik, és az átirányító jel csak a Kimenetre gyakorol hatást.
Visszatérve az alapvető témához, emlékezzünk arra, hogy a .NET objektum modell teljes mértékben létezik a powershell keretein belül egy számítógépen (egy operációs rendszeren), távoli kódvégrehajtás esetén a WSMAN-en keresztül az objektumok átadása XML-sorozatsorközmön keresztül történik, ami sok további érdekességet visz a kutatásainkba. Folytatjuk a kísérleteket a következő kód végrehajtásával:
$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$res.GetType()
$host.SetShouldExit($res)
És ez van a képernyőn:
Kimenet a gazdára.
Kilépési Kód: 3
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Nem lehet a "exitCode" argumentumot, amelynek értéke: "System.Object[]", átkonvertálni a "SetShouldExit"-hoz a "System.Int32" típusra: "Nem lehet a "System.Object[]" típusú "System.Object[]" értéket "System.Int32"-ra átkonvertálni."
D:sqlagentTestOutput3.ps1:3 jel:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ KategóriaInformáció : Nem Megadott: (:) [], MethodException
+ Teljesen Megszólított Hibaazonosító : MethodArgumentConversionInvalidCastArgument
ERRORLEVEL=0Kiváló eredmény! Ez azt jelenti, hogy az Invoke-Command hívásakor megmarad a csővezetékek két áramra (Host és Kimenet) való felosztása, ami reményt ad nekünk a sikerre. Próbáljuk meg, hogy a Kimenet áramában csak egy érték maradjon, ezért módosítjuk azt az első skriptet, amelyet távolról indítunk:
$exitcode = $args[0]
Write-Host 'Kimenet a gazdára.'
#Write-Output 'Kimenet a kimenetre.'
Write-Host ('Kilépési Kód: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)
Futtassuk így:
$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$host.SetShouldExit($res)
és... IGEN, úgy tűnik, hogy ez egy győzelem!
Kimenet a gazdára.
Kilépési Kód: 4
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
ERRORLEVEL=4Próbáljuk megérteni, mi történt. Helyben a Powershellt hívtuk, amely viszont távoli számítógépen hívta meg a Powershellt, és ott futtatta a scriptünket. Két szál (Host és Output) a távoli gépről sorosítva lett és visszaküldve, miközben az Output szál, ha tartalmazott egy numerikus értéket, Int32 típussá lett átkonvertálva, és így került átadásra a fogadó félnek, aki ezt a Powershell hívásának kilépési kódjaként használta.
És mint utolsó ellenőrzés, készítünk egy szerveren SQL feladatot egy lépésben, az „Operációs rendszer (cmdexec)” típussal e szöveggel:
PowerShell -NonInteractive -NoProfile "$res=Invoke-Command -ComputerName BACKUPSERVER -ConfigurationName SQLAgent -ScriptBlock {&'D:sqlagentTestOutput1.ps1' 6}; $host.SetShouldExit($res)"HURRÁ! A feladat hibával zárult, a naplóban lévő szöveg:
A következő felhasználói név alatt fut: DOMAINagentuser. Kimeneti folyamat. Kilépési kód: 6. A folyamat kilépési kódja 6. A lépés hibával zárult.
Következtetések:
- Kerülje a Write-Output használatát és a kifejezések hozzárendelés nélküli megadását. Ne feledje, hogy a kód átmozgatása a script más helyére váratlan eredményekhez vezethet.
- Azokban a scriptetekben, amelyeket nem kézi indításra szántak, hanem az automatizálási mechanizmusaitokban történő felhasználásra, különösen a WINRM-en keresztüli távoli hívások esetén, végezzetek manuális hiba-kezelést Try/Catch segítségével, és ügyeljetek arra, hogy bármi is történjen, ez a script pontosan egy primitív típusú értéket küldjön az Output folyamatba. Ha klasszikus Errorlevelt szeretnétek kapni — ez az érték számérték kell, hogy legyen.
Forrás: habr.com
