Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A hibakezelés csak része az életnek, amikor kódírásról van szó. Gyakran ellenőrizhetjük és érvényesíthetjük a várt viselkedés feltételeit. Amikor a váratlan esemény bekövetkezik, kivételkezelésre váltunk. Egyszerűen kezelheti a mások kódjai által létrehozott kivételeket, vagy létrehozhat saját kivételeket mások számára.
Megjegyzés
A cikk eredeti verziója@KevinMarquette blogján jelent meg. A PowerShell csapata köszönjük Kevinnek, hogy megosztotta velünk ezt a tartalmat. Kérjük, látogasd meg a blogot a PowerShellExplained.comoldalon.
Alapszintű terminológia
Néhány alapfogalmat meg kell fednünk, mielőtt belevágnánk ebbe.
Kivétel
A kivétel olyan esemény, amely akkor jön létre, ha a normál hibakezelés nem tud megbirkózni a problémával. A szám nullával való felosztása vagy a memória elfogyása olyan példák, amelyek kivételt okoznak. Előfordulhat, hogy a használt kód szerzője kivételeket hoz létre bizonyos problémák esetén.
Dobás és fogás
Amikor kivétel történik, azt mondjuk, hogy a rendszer kivételt ad ki. A kidobott kivétel kezeléséhez meg kell ragadnia. Ha kivétel keletkezik, és azt nem kezeli semmi, a szkript végrehajtása leáll.
A hívásverem
A hívásverem az egymást meghívó függvények listája. Amikor egy függvényt meghívnak, az hozzáadódik a veremhez vagy a lista elejéhez. Amikor a függvény kilép vagy visszatér, a függvény el lesz távolítva a veremből.
Kivétel dobásakor a hívásvermet ellenőrzik, hogy egy kivételkezelő elkapja azt.
Megszüntetési és nem megszüntetési hibák
A kivétel általában végzetes hiba. A rendszer egy kidobott kivételt kap, vagy leállítja az aktuális végrehajtást. Alapértelmezés szerint a rendszer nem végződő hibát hoz létre Write-Error , és kivétel nélkül hozzáad egy hibát a kimeneti streamhez.
Rámutatok erre, mert Write-Error és más, nem végződő hibák nem aktiválják a catch.
Kivétel elnyelése
Ekkor kap egy hibát csak azért, hogy elnyomja azt. Ezt körültekintően végezze el, mert ez megnehezítheti a hibaelhárítási problémákat.
Egyszerű parancsszintaxis
Íme egy gyors áttekintés a PowerShellben használt alapvető kivételkezelési szintaxisról.
Vet
A saját kivételesemény létrehozásához kivételt adunk a throw kulcsszóval.
function Start-Something
{
throw "Bad thing happened"
}
Ez létrehoz egy futásidejű kivételt, amely egy végzetes hiba. Egy hívó függvény kezeli catch , vagy kilép a szkriptből egy ilyen üzenettel.
PS> Start-Something
Bad thing happened
At line:1 char:1
+ throw "Bad thing happened"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Bad thing happened:String) [], RuntimeException
+ FullyQualifiedErrorId : Bad thing happened
Write-Error -ErrorAction Állj
Megemlítettem, hogy Write-Error alapértelmezés szerint nem okoz megszüntetési hibát. Ha megadja -ErrorAction Stop, Write-Error egy olyan megszűnési hibát hoz létre, amelyet egy catch-vel lehet kezelni.
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Köszönjük Lee Dailey-nek, hogy emlékeztetett az ilyen módszer használatára -ErrorAction Stop .
Parancsmag -ErrorAction leállítása
Ha bármilyen speciális függvényt vagy parancsmagot ad meg -ErrorAction Stop , az összes Write-Error utasítást befejező hibákká alakítja, amelyek leállítják a végrehajtást, vagy amelyek egy catchadott parancsmaggal kezelhetők.
Start-Something -ErrorAction Stop
Az ErrorAction paraméterről további információt a about_CommonParameters talál. A $ErrorActionPreference változóval kapcsolatos további információkért lásd: about_Preference_Variables.
Próbálja elkapni
A kivételkezelés működése a PowerShellben (és sok más nyelven) úgy történik, hogy először meghatároz egy kódrészt try, és ha ez hibát dob, akkor azt catch kezelheti. Íme egy gyors minta.
try
{
Start-Something
}
catch
{
Write-Output "Something threw an exception"
Write-Output $_
}
try
{
Start-Something -ErrorAction Stop
}
catch
{
Write-Output "Something threw an exception or used Write-Error"
Write-Output $_
}
A catch szkript csak akkor fut, ha megszüntetési hiba történt. Ha a try végrehajtás megfelelően történik, akkor átugrik a catch. A catch blokk kivételadatait a $_ változó használatával érheti el.
Próbál/Végül
Néha nem kell kezelnie egy hibát, de szükség van valamilyen kód végrehajtására, ha kivétel történik vagy sem. Egy finally szkript pontosan ezt teszi.
Tekintse meg ezt a példát:
$command = [System.Data.SqlClient.SqlCommand]::new(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()
Amikor megnyit vagy csatlakozik egy erőforráshoz, be kell zárni. Ha a ExecuteNonQuery() rendszer kivételt jelez, a kapcsolat nincs lezárva. Itt ugyanaz a kód található egy try/finally blokkon belül.
$command = [System.Data.SqlClient.SqlCommand]::new(queryString, connection)
try
{
$command.Connection.Open()
$command.ExecuteNonQuery()
}
finally
{
$command.Connection.Close()
}
Ebben a példában a kapcsolat megszakad, ha hiba történik. Akkor is bezárul, ha nincs hiba. A finally szkript minden alkalommal fut.
Mivel nem kapja meg a kivételt, a rendszer továbbra is propagálja a hívásvermet.
Próbáld ki/Kapd el/Végül
Teljesen érvényes a catch és a finally együttes használata. Legtöbbször az egyiket vagy a másikat fogja használni, de olyan forgatókönyveket is találhat, ahol mindkettőt használja.
$PSItem
Most, hogy túl vagyunk az alapokon, egy kicsit mélyebbre áshatunk.
catch A blokkon belül található egy automatikus változó ($PSItemvagy $_) típusErrorRecord, amely tartalmazza a kivétel részleteit. Az alábbiakban rövid áttekintést talál néhány kulcsfontosságú tulajdonságról.
Ezekben a példákban ReadAllText érvénytelen elérési utat használtam a kivétel létrehozásához.
[System.IO.File]::ReadAllText( '\\test\no\filefound.log')
PSItem.ToString()
Ez a legtisztább üzenetet adja a naplózáshoz és az általános kimenethez.
ToString() a rendszer automatikusan meghívja, ha $PSItem egy sztringben van elhelyezve.
catch
{
Write-Output "Ran into an issue: $($PSItem.ToString())"
}
catch
{
Write-Output "Ran into an issue: $PSItem"
}
$PSItem.InvocationInfo
Ez a tulajdonság további információkat tartalmaz, amelyeket a PowerShell gyűjt a kivételt tartalmazó függvényről vagy szkriptről. Itt van a InvocationInfo az általam létrehozott mintakivételből.
PS> $PSItem.InvocationInfo | Format-List *
MyCommand : Get-Resource
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 5
OffsetInLine : 5
ScriptName : C:\blog\throwerror.ps1
Line : Get-Resource
PositionMessage : At C:\blog\throwerror.ps1:5 char:5
+ Get-Resource
+ ~~~~~~~~~~~~
PSScriptRoot : C:\blog
PSCommandPath : C:\blog\throwerror.ps1
InvocationName : Get-Resource
A fontos részletek itt mutatják a ScriptName kódot, valamint a Line és ScriptLineNumber helyeket, ahol a hívás elkezdődött.
$PSItem.ScriptStackTrace
Ez a tulajdonság azt mutatja meg, hogy milyen sorrendben kerültek végrehajtásra a függvényhívások, amelyek elvezettek a kivételt generáló kódhoz.
PS> $PSItem.ScriptStackTrace
at Get-Resource, C:\blog\throwerror.ps1: line 13
at Start-Something, C:\blog\throwerror.ps1: line 5
at <ScriptBlock>, C:\blog\throwerror.ps1: line 18
Csak ugyanabban a szkriptben indítok hívásokat a függvényekre, de ez nyomon követné a hívásokat, ha több szkript is érintett volna.
$PSItem.Exception
Ez a tényleges kivétel, amely ki lett dobva.
$PSItem.Exception.Message
Ez az általános üzenet ismerteti a kivételt, és jó kiindulópont a hibaelhárításhoz. A legtöbb kivétel alapértelmezett üzenettel rendelkezik, de a kivétel eldobásakor egyénire is beállítható.
PS> $PSItem.Exception.Message
Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."
Ez a visszaküldött üzenet akkor jelentkezik, amikor a $PSItem.ToString() hívásakor nem volt üzenet beállítva a ErrorRecord.
$PSItem.Exception.InnerException
A kivételek belső kivételeket tartalmazhatnak. Ez gyakran akkor fordul elő, ha a hívott kód kivételt kap, és egy másik kivételt ad ki. Az eredeti kivétel az új kivételen belülre kerül.
PS> $PSItem.Exception.InnerExceptionMessage
The network path was not found.
Ezt később fogom újra áttekinteni, amikor a kivételek újravetéséről beszélek.
$PSItem.Exception.StackTrace
Ez a StackTrace kivétel. Megmutattam a ScriptStackTrace fent, de ez a hívásokra vonatkozik a felügyelt kódhoz.
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean
useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs,
String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32
bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean
checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks,
Int32 bufferSize, Boolean checkHost)
at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
at CallSite.Target(Closure , CallSite , Type , String )
Ezt a veremkövetést csak akkor kapja meg, ha az eseményt felügyelt kódból váltják ki. Egy .NET-keretrendszerfüggvényt hívok meg közvetlenül, ezért csak ezt láthatjuk ebben a példában. Általában ha egy veremkövetést keres, akkor azt keresi, hogy hol áll le a kód, és hol kezdődnek a rendszerhívások.
Kivételekkel való munka
Több kivétel létezik, mint az alapszintaxis és a kivétel tulajdonságai.
Típus szerint meghatározott kivételek kezelése
Szelektíven választhatja ki, mely kivételeket kezeli. A kivételeknek van egy típusa, és megadhatja, hogy milyen típusú kivételt szeretne elkapni.
try
{
Start-Something -Path $path
}
catch [System.IO.FileNotFoundException]
{
Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
Write-Output "IO error with the file: $path"
}
Az egyes catch blokkok kivételtípusa addig kerül ellenőrzésre, amíg nem találunk olyat, amely megfelel a kivételének.
Fontos tisztában lennie azzal, hogy a kivételek öröklődhetnek más kivételektől. A fenti példában a FileNotFoundException örökli a IOException tulajdonságait. Tehát ha az IOException lenne az első, akkor azt hívnák meg helyette. A rendszer csak egy fogási blokkot hív meg, még akkor is, ha több egyezés van.
Ha lenne egy System.IO.PathTooLongException, a IOException megfelelne, de ha lenne egy InsufficientMemoryException, akkor semmi sem fogná fel, és az továbbhaladna a veremben.
Több típus egyidejű elfogása
Több kivételtípust is elkaphat ugyanazzal catch az utasítással.
try
{
Start-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
Write-Output "IO error with the file: [$path]"
}
Köszönjük Redditornak u/Sheppard_Ra , hogy javasolta ezt a kiegészítést.
Beírt kivételek dobálása
Gépelt kivételeket a PowerShellben is megadhat.
throw Sztring helyett:
throw "Could not find: $path"
Használjon a következőhöz hasonló kivételgyorsítót:
throw [System.IO.FileNotFoundException] "Could not find: $path"
De ha így tesz, meg kell adnia egy üzenetet.
Létrehozhat egy új kivételpéldányt is, amely kiváltásra kerül. Ez az üzenet nem kötelező, mert a rendszer minden beépített kivételhez alapértelmezett üzenetekkel rendelkezik.
throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")
Ha nem a PowerShell 5.0-s vagy újabb verzióját használja, a régebbi New-Object megközelítést kell használnia.
throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")
Egy beírt kivétel használatával Ön (vagy mások) az előző szakaszban említett típus alapján is elkaphatja a kivételt.
Write-Error -Exception
Hozzáadhatjuk a típusozott kivételeket a Write-Error-hoz, és a hibákat továbbra is a kivételtípus szerint tudjuk kezelni catch. Használja Write-Error az alábbi példákhoz hasonlóan:
# with normal message
Write-Error -Message "Could not find path: $path" -Exception ([System.IO.FileNotFoundException]::new()) -ErrorAction Stop
# With message inside new exception
Write-Error -Exception ([System.IO.FileNotFoundException]::new("Could not find path: $path")) -ErrorAction Stop
# Pre PS 5.0
Write-Error -Exception ([System.IO.FileNotFoundException]"Could not find path: $path") -ErrorAction Stop
Write-Error -Message "Could not find path: $path" -Exception (New-Object -TypeName System.IO.FileNotFoundException) -ErrorAction Stop
Így foghatjuk meg:
catch [System.IO.FileNotFoundException]
{
Write-Log $PSItem.ToString()
}
A .NET-kivételek nagy listája
Összeállítottam egy mesterlistát a Reddit r/PowerShell közösség segítségével, amely több száz .NET-kivételt tartalmaz, hogy kiegészítse ezt a bejegyzést.
Először is megkeresem a kivételek listáját, amelyeket úgy érzek, hogy jól illeszkednek az én helyzetemhez. Próbálja meg kivételeket használni az alapnévtérben System .
A kivételek objektumok
Ha sok beírt kivételt kezd használni, ne feledje, hogy ezek objektumok. A különböző kivételek különböző konstruktorokkal és tulajdonságokkal rendelkeznek. Ha megnézzük a FileNotFoundException dokumentációját System.IO.FileNotFoundException, láthatjuk, hogy át tudunk adni egy üzenetet és egy fájl elérési útját.
[System.IO.FileNotFoundException]::new("Could not find file", $path)
És van egy FileName tulajdonsága, amely elérhetővé teszi a fájl elérési útját.
catch [System.IO.FileNotFoundException]
{
Write-Output $PSItem.Exception.FileName
}
Tekintse meg a .NET dokumentációját más konstruktorok és objektumtulajdonságok esetében.
Kivétel ismételt kivetése
Ha a catch blokkban csak ugyanazt a kivételt throw követné el, akkor ne tegye catch. Csak olyan kivételt kell kezelnie catch, amelyet akkor szeretne kezelni, vagy amikor valamilyen műveletet szeretne végrehajtani, amikor ez bekövetkezik.
Vannak olyan esetek, amikor egy kivételen műveletet szeretne végrehajtani, de a kivételt újra szeretné dobni, hogy valami az alsóbb rétegben kezelni tudja azt. Írhatunk egy üzenetet, vagy naplózhatjuk a problémát a felfedezés helye közelében, de a probléma kezelését később, a hívásveremben is folytathatjuk.
catch
{
Write-Log $PSItem.ToString()
throw $PSItem
}
Érdekes módon hívhatunk belülről throw a catch-ből, és újradobja az aktuális kivételt.
catch
{
Write-Log $PSItem.ToString()
throw
}
A kivételt újra szeretnénk dobni, hogy megőrizzük az eredeti végrehajtási adatokat, például a forrásszkriptet és a sorszámot. Ha ezen a ponton új kivételt adunk ki, az elrejti, hogy hol kezdődött a kivétel.
Új kivétel újradobása
Ha kivételt észlel, de egy másikat szeretne dobni, akkor az eredeti kivételt az újba kell ágyaznia. Ez lehetővé teszi, hogy valaki a verem alsó részén hozzáférjen, mint a $PSItem.Exception.InnerException.
catch
{
throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}
$PSCmdlet.ThrowTerminatingError()
Az egyetlen dolog, amit nem szeretek a throw használatában a nyers kivételek kezelésére, hogy a hibaüzenet az throw utasításra mutat, és jelzi, hogy az a sor, ahol a probléma van.
Unable to find the specified file.
At line:31 char:9
+ throw [System.IO.FileNotFoundException]::new()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], FileNotFoundException
+ FullyQualifiedErrorId : Unable to find the specified file.
Miután a hibaüzenet azt mondja, hogy a szkriptem hibás, mert a 31. sorban hívtam throw, ez rossz üzenet a szkript felhasználói számára. Nem mond nekik semmi hasznosat.
Dexter Dhami rámutatott, hogy ThrowTerminatingError() használatával ki tudom javítani azt.
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
([System.IO.FileNotFoundException]"Could not find $Path"),
'My.ID',
[System.Management.Automation.ErrorCategory]::OpenError,
$MyObject
)
)
Ha feltételezzük, hogy ezt ThrowTerminatingError() egy úgynevezett Get-Resourcefüggvényben hívták meg, akkor ez a hiba, amelyet látnánk.
Get-Resource : Could not find C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework\.NETPortable\v4.6\System.IO.xml
At line:6 char:5
+ Get-Resource -Path $Path
+ ~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Get-Resource], FileNotFoundException
+ FullyQualifiedErrorId : My.ID,Get-Resource
Látja, hogyan utal a Get-Resource függvényre a probléma forrásaként? Ez valami hasznosat jelez a felhasználónak.
Mivel $PSItemErrorRecord, mi is tudjuk használni ThrowTerminatingError ezt a módszert újradobásra.
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
Ez megváltoztatja a hiba forrását a parancsmagra, és elrejti a függvény belső elemeit a parancsmag felhasználói elől.
Megpróbálhat megszüntetési hibákat létrehozni
Kirk Munro rámutat, hogy egyes kivételek csak akkor eredményeznek végzetes hibákat, ha egy try/catch blokkon belül hajtják végre őket. Itt van az a példa, amit adott nekem, ami osztás nullával futásidejű kivételt hoz létre.
function Start-Something { 1/(1-1) }
Ezután hívja meg így, hogy lássa a hibát, és továbbra is ki tudja adni az üzenetet.
&{ Start-Something; Write-Output "We did it. Send Email" }
De azzal, hogy ugyanazt a kódot egy try/catch-ba helyezi, valami más történik.
try
{
&{ Start-Something; Write-Output "We did it. Send Email" }
}
catch
{
Write-Output "Notify Admin to fix error and send email"
}
Azt látjuk, hogy a hiba végzetes hibává válik, és nem adja ki az első üzenetet. Amit nem szeretek ebben, hogy ez a kód egy függvényben lehet, és másképp működik, ha valaki egy try/catch-t használ.
Nem futottam bele ilyen problémákba én magam, de ez egy sarokeset, amellyel tisztában kell lennie.
$PSCmdlet.ThrowTerminatingError() belül try/catch
Ennek egyik árnyalata $PSCmdlet.ThrowTerminatingError() , hogy megszüntetési hibát hoz létre a parancsmagon belül, de a parancsmag elhagyása után nem végződő hibává válik. Ennek a feladatnak az eldöntése, hogy hogyan kezeljük a hibát, a függvény hívójára hárul. Ismét véglegessé tehetik a hibát, ha a -ErrorAction Stop-t használják, vagy ha try{...}catch{...}-ből hívják meg.
Nyilvános függvénysablonok
A Kirk Munróval folytatott beszélgetésem egyik utolsó tanulsága az volt, hogy ő minden egyes begin, process és end blokk köré try{...}catch{...}-t helyez el minden fejlett funkciójában. Ezekben az általános fogási blokkokban egyetlen sorral, a $PSCmdlet.ThrowTerminatingError($PSItem) használatával kezeli a függvényeit elhagyó kivételeket.
function Start-Something
{
[CmdletBinding()]
param()
process
{
try
{
...
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
Mivel minden egy try utasításban van a funkcióiban, minden következetesen működik. Ez tiszta hibákat is ad a végfelhasználónak, amely elrejti a belső kódot a generált hiba elől.
Csapda
A kivételekre koncentráltam try/catch . De van egy régi funkció, amit meg kell említenem, mielőtt ezt becsomagolnánk.
A trap parancsprogramba vagy függvénybe kerül, hogy elkapja az adott hatókörben előforduló összes kivételt. Kivétel esetén a program végrehajtja a trap benne lévő kódot, majd a normál kód folytatódik. Ha több kivétel is előfordul, a csapda újra és újra hívódik meg.
trap
{
Write-Log $PSItem.ToString()
}
throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')
Én soha nem fogadtam el ezt a megközelítést, de látom az értéket az adminisztrátori vagy vezérlő scriptekben, amelyek naplóznak minden kivételt, majd folytatják a végrehajtást.
Záró megjegyzések
A megfelelő kivételkezelés hozzáadása a szkriptekhez nemcsak stabilabbá teszi őket, hanem megkönnyíti a kivételek elhárítását is.
Sok időt töltöttem a beszélgetéssel throw , mert ez egy alapvető fogalom, amikor a kivételkezelésről beszélek. A PowerShell azt is megadta nekünk Write-Error , hogy kezelje az összes olyan helyzetet, ahol ön használná throw. Ezért ne gondolja, hogy ezt elolvasása után kell használnia throw .
Most, hogy időt szántam arra, hogy részletesen írjak a kivételkezelésről, át fogok térni a Write-Error -Stop használatára a hibák előidézésére a kódban. Én is megfogadom Kirk tanácsát, és ThrowTerminatingError-t fogom használni minden egyes funkció kivételkezelőjeként.