Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Foutafhandeling is slechts een onderdeel van het leven als het gaat om het schrijven van code. We kunnen vaak voorwaarden voor verwacht gedrag controleren en valideren. Wanneer de onverwachte gebeurtenis zich voordoet, schakelen we over naar de afhandeling van uitzonderingen. U kunt eenvoudig uitzonderingen verwerken die zijn gegenereerd door de code van anderen of u kunt uw eigen uitzonderingen genereren die anderen kunnen verwerken.
Notitie
De oorspronkelijke versie van dit artikel verscheen op de blog geschreven door @KevinMarquette. Het PowerShell-team bedankt Kevin voor het delen van deze inhoud met ons. Bekijk zijn blog op PowerShellExplained.com.
Basisterminologie
We moeten enkele basistermen bespreken voordat we in deze gaan.
Uitzondering
Een uitzondering lijkt op een gebeurtenis die wordt gemaakt wanneer normale foutafhandeling het probleem niet kan oplossen. Een getal delen door nul of onvoldoende geheugen zijn voorbeelden van iets dat een uitzondering creëert. Soms maakt de auteur van de code die u gebruikt uitzonderingen voor bepaalde problemen wanneer deze optreden.
Gooien en vangen
Wanneer er een uitzondering optreedt, zeggen we dat er een uitzondering wordt gegenereerd. Als u een opgeworpen uitzondering wilt afhandelen, moet u deze vangen. Als er een uitzondering wordt gegenereerd en deze niet wordt onderschept door iets, stopt het uitvoeren van het script.
De aanroepstack
De aanroepstack is de lijst met functies die elkaar hebben aangeroepen. Wanneer een functie wordt aangeroepen, wordt deze toegevoegd aan de stack of de bovenkant van de lijst. Wanneer de functie wordt afgesloten of geretourneerd, wordt deze verwijderd uit de stack.
Wanneer er een uitzondering wordt gegenereerd, wordt die aanroepstack gecontroleerd, zodat een uitzonderingshandler deze kan vangen.
Afsluit- en niet-afsluitfouten
Een uitzondering is over het algemeen een afsluitfout. Een opgeworpen uitzondering wordt ofwel opgevangen of het beëindigt de huidige uitvoering. Standaard wordt een niet-terminerende fout gegenereerd door Write-Error en wordt er een fout aan de uitvoerstroom toegevoegd zonder dat er een uitzondering wordt opgeworpen.
Ik wijs hierop omdat Write-Error en andere niet-beëindigende fouten de catchniet activeren.
Een uitzondering inslikken
Dit is wanneer u een fout opvangt om deze te onderdrukken. Doe dit met voorzichtigheid, omdat het het oplossen van problemen heel moeilijk kan maken.
Syntaxis van de basisopdracht
Hier volgt een kort overzicht van de basissyntaxis voor het verwerken van uitzonderingen die worden gebruikt in PowerShell.
Werpen
Om onze eigen uitzondering te maken, werpen we een uitzondering met het trefwoord throw.
function Start-Something
{
throw "Bad thing happened"
}
Hiermee wordt een runtime-uitzondering gemaakt die een afsluitfout is. Het wordt afgehandeld door een catch in een aanroepende functie of het script wordt afgesloten met een bericht zoals dit.
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 stoppen
Ik heb gezegd dat Write-Error standaard geen afsluitfout genereert. Als u -ErrorAction Stopopgeeft, genereert Write-Error een afsluitfout die kan worden verwerkt met een catch.
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Dank u aan Lee Dailey om te herinneren aan het gebruik van -ErrorAction Stop op deze manier.
Cmdlet -ErrorAction Stoppen
Als u -ErrorAction Stop opgeeft voor een geavanceerde functie of cmdlet, worden alle Write-Error instructies omgezet in terminerende fouten waardoor de uitvoering stopt of die kunnen worden afgehandeld door een catch.
Start-Something -ErrorAction Stop
Zie about_CommonParametersvoor meer informatie over de parameter ErrorAction. Zie $ErrorActionPreferencevoor meer informatie over de variabele .
Proberen/Vangen
De manier waarop uitzonderingsafhandeling in PowerShell (en veel andere talen) werkt, is dat u eerst een codesectie try en als er een fout optreedt, kunt u deze catch. Hier volgt een snel voorbeeld.
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 $_
}
Het catch script wordt alleen uitgevoerd als er een afsluitfout optreedt. Als de try correct wordt uitgevoerd, wordt de catchovergeslagen. U hebt toegang tot de uitzonderingsgegevens in het catch blok met behulp van de variabele $_.
Probeer/ten slotte
Soms hoeft u geen fouten af te handelen, maar moet er nog steeds code worden uitgevoerd, ongeacht of er een uitzondering optreedt. Een finally script doet dat precies.
Bekijk dit voorbeeld:
$command = [System.Data.SqlClient.SqlCommand]::new(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()
Wanneer u een resource opent of er verbinding mee maakt, moet u deze sluiten. Als de ExecuteNonQuery() een uitzondering genereert, wordt de verbinding niet gesloten. Hier ziet u dezelfde code in een try/finally blok.
$command = [System.Data.SqlClient.SqlCommand]::new(queryString, connection)
try
{
$command.Connection.Open()
$command.ExecuteNonQuery()
}
finally
{
$command.Connection.Close()
}
In dit voorbeeld wordt de verbinding gesloten als er een fout optreedt. Het is ook gesloten als er geen fout is. Het finally script wordt elke keer uitgevoerd.
Omdat u de uitzondering niet afvangt, wordt deze nog steeds doorgegeven in de aanroepstack.
Try/Catch/Finally
Het is perfect geldig om catch en finally samen te gebruiken. Meestal gebruikt u een of meer, maar u kunt scenario's vinden waarin u beide gebruikt.
$PSItem
Nu we de basisbeginselen achter de rug hebben, kunnen we iets dieper graven.
In het catch blok bevindt zich een automatische variabele ($PSItem of $_) van het type ErrorRecord die de details over de uitzondering bevat. Hier volgt een kort overzicht van enkele van de belangrijkste eigenschappen.
Voor deze voorbeelden heb ik een ongeldig pad in ReadAllText gebruikt om deze uitzondering te genereren.
[System.IO.File]::ReadAllText( '\\test\no\filefound.log')
PSItem.ToString()
Dit geeft u het schoonste bericht dat u kunt gebruiken in logboekregistratie en algemene uitvoer.
ToString() wordt automatisch aangeroepen als $PSItem in een tekenreeks wordt geplaatst.
catch
{
Write-Output "Ran into an issue: $($PSItem.ToString())"
}
catch
{
Write-Output "Ran into an issue: $PSItem"
}
$PSItem.InvocationInfo
Deze eigenschap bevat aanvullende informatie die door PowerShell wordt verzameld over de functie of het script waarin de uitzondering is opgetreden. Hier is de InvocationInfo van de voorbeelduitzondering die ik heb gemaakt.
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
De belangrijke details hier tonen de ScriptName, de Line van de code en de ScriptLineNumber waarmee de oproep is gestart.
$PSItem.ScriptStackTrace
Deze eigenschap toont de volgorde van functie-aanroepen waarmee u naar de code bent gekomen waarin de uitzondering is gegenereerd.
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
Ik maak alleen aanroepen naar functies in hetzelfde script, maar hiermee worden de aanroepen bijgehouden als er meerdere scripts betrokken zijn.
$PSItem.Exception
Dit is de werkelijke uitzondering die is opgeworpen.
$PSItem.Exception.Message
Dit is het algemene bericht dat de uitzondering beschrijft en een goed uitgangspunt is bij het oplossen van problemen. De meeste uitzonderingen hebben een standaardbericht, maar kunnen ook worden ingesteld op iets aangepast wanneer de uitzondering wordt gegenereerd.
PS> $PSItem.Exception.Message
Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."
Dit is ook het bericht dat wordt geretourneerd bij het aanroepen van $PSItem.ToString() als er niet één is ingesteld op de ErrorRecord.
$PSItem.Exception.InnerException
Uitzonderingen kunnen interne uitzonderingen bevatten. Dit is vaak het geval wanneer de code die u aanroept, een uitzondering onderschept en een andere uitzondering genereert. De oorspronkelijke uitzondering wordt in de nieuwe uitzondering geplaatst.
PS> $PSItem.Exception.InnerExceptionMessage
The network path was not found.
Ik zal hier later op terugkomen wanneer ik het heb over het opnieuw gooien van uitzonderingen.
$PSItem.Exception.StackTrace
Dit is de StackTrace voor de uitzondering. Ik heb een ScriptStackTrace hierboven getoond, maar deze is bedoeld voor de aanroepen naar beheerde code.
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 )
U krijgt deze stack-trace alleen wanneer de gebeurtenis wordt gegenereerd vanuit beheerde code. Ik roep een .NET Framework-functie rechtstreeks aan, dus dat is alles wat we in dit voorbeeld kunnen zien. Over het algemeen zoekt u wanneer u een stacktracering bekijkt, naar waar uw code stopt en de systeemoproepen beginnen.
Werken met uitzonderingen
Er zijn meer uitzonderingen dan de basissyntaxis en uitzonderingseigenschappen.
Getypte uitzonderingen vangen
U kunt selectief zijn met de uitzonderingen die u opvangt. Uitzonderingen hebben een type en u kunt het type uitzondering opgeven dat u wilt vangen.
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"
}
Het uitzonderingstype wordt gecontroleerd op elk catch blok totdat er een wordt gevonden die overeenkomt met uw uitzondering.
Het is belangrijk om te beseffen dat uitzonderingen kunnen overnemen van andere uitzonderingen. In het bovenstaande voorbeeld neemt FileNotFoundException over van IOException. Dus als de IOException eerst was, zou het in plaats daarvan worden aangeroepen. Er wordt slechts één catch-blok aangeroepen, zelfs als er meerdere overeenkomsten zijn.
Als we een System.IO.PathTooLongExceptionhadden, zou de IOException overeenkomen, maar als we een InsufficientMemoryException hadden, zou niets het vangen, en zou het zich door de stapel heen verspreiden.
Meerdere typen tegelijk vangen
Het is mogelijk om meerdere uitzonderingstypen met dezelfde catch instructie te vangen.
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]"
}
Bedankt Redditor u/Sheppard_Ra voor het voorstellen van deze toevoeging.
Getypte uitzonderingen genereren
U kunt getypte uitzonderingen genereren in PowerShell. In plaats van throw aan te roepen met een tekenreeks:
throw "Could not find: $path"
Gebruik een uitzonderingsversneller als volgt:
throw [System.IO.FileNotFoundException] "Could not find: $path"
Maar u moet een bericht opgeven wanneer u dit op die manier doet.
U kunt ook een nieuw exemplaar van een uitzondering maken die moet worden gegenereerd. Het bericht is optioneel wanneer u dit doet omdat het systeem standaardberichten heeft voor alle ingebouwde uitzonderingen.
throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")
Als u PowerShell 5.0 of hoger niet gebruikt, moet u de oudere New-Object benadering gebruiken.
throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")
Met behulp van een getypte uitzondering kunt u (of anderen) de uitzondering opvangen op basis van het type, zoals in de vorige sectie is vermeld.
Write-Error -Exception
We kunnen deze getypte uitzonderingen toevoegen aan Write-Error en we kunnen de fouten nog steeds catch op uitzonderingstype. Gebruik Write-Error zoals in deze voorbeelden:
# 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
Dan kunnen we het als volgt vangen:
catch [System.IO.FileNotFoundException]
{
Write-Log $PSItem.ToString()
}
De grote lijst met .NET-uitzonderingen
Ik heb een hoofdlijst samengesteld met behulp van de Reddit r/PowerShell-community die honderden .NET-uitzonderingen bevat om dit bericht aan te vullen.
Ik begin met het zoeken naar die lijst naar uitzonderingen die het gevoel hebben dat ze geschikt zijn voor mijn situatie. U moet uitzonderingen gebruiken in de basis-System naamruimte.
Uitzonderingen zijn objecten
Als u veel getypte uitzonderingen gaat gebruiken, moet u er rekening mee houden dat het objecten zijn. Verschillende uitzonderingen hebben verschillende constructors en eigenschappen. Als we de FileNotFoundException-documentatie voor System.IO.FileNotFoundExceptionbekijken, zien we dat we een bericht en een bestandspad kunnen doorgeven.
[System.IO.FileNotFoundException]::new("Could not find file", $path)
En het heeft een FileName eigenschap die dat bestandspad beschikbaar maakt.
catch [System.IO.FileNotFoundException]
{
Write-Output $PSItem.Exception.FileName
}
Raadpleeg de .NET-documentatie voor andere constructors en objecteigenschappen.
Een uitzondering opnieuw opwerpen
Als alles wat u in uw catch blok gaat doen, dezelfde uitzondering is throw, catch u het niet. Je moet alleen een uitzondering opwerpen catch die je van plan bent af te handelen of waar je actie op wilt ondernemen wanneer het gebeurt.
Soms wil je een handeling uitvoeren bij een uitzondering, maar vervolgens de uitzondering opnieuw wilt gooien, zodat er verderop in het proces mee kan worden omgegaan. We kunnen een bericht schrijven of het probleem loggen in de buurt van de plaats waar we het ontdekken, maar het probleem hoger in de stack afhandelen.
catch
{
Write-Log $PSItem.ToString()
throw $PSItem
}
Interessant genoeg kunnen we throw aanroepen vanuit de catch en wordt de huidige uitzondering opnieuw opgegooid.
catch
{
Write-Log $PSItem.ToString()
throw
}
We willen de uitzondering opnieuw genereren om de oorspronkelijke uitvoeringsinformatie, zoals bronscript en regelnummer, te behouden. Als we op dit moment een nieuwe uitzondering gooien, wordt waar de uitzondering is gestart verborgen.
Een nieuwe uitzondering opnieuw opwerpen
Als u een uitzondering onderschept maar een andere wilt werpen, moet u de oorspronkelijke uitzondering in de nieuwe opnemen. Hierdoor kan iemand die verder in de stack zit er toegang tot krijgen als de $PSItem.Exception.InnerException.
catch
{
throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}
$PSCmdlet.ThrowTerminatingError()
Het enige wat ik niet leuk vind aan het gebruik van throw voor onbewerkte uitzonderingen, is dat het foutbericht naar de throw-instructie verwijst en aangeeft dat daar het probleem ligt.
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.
Als de foutmelding aangeeft dat mijn script niet werkt omdat ik throw op regel 31 heb aangeroepen, is dat een slechte melding voor gebruikers van uw script. Het vertelt ze niets nuttigs.
Dexter Dhami wees erop dat ik ThrowTerminatingError() kan gebruiken om dat te corrigeren.
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
([System.IO.FileNotFoundException]"Could not find $Path"),
'My.ID',
[System.Management.Automation.ErrorCategory]::OpenError,
$MyObject
)
)
Als we ervan uitgaan dat ThrowTerminatingError() is aangeroepen binnen een functie genaamd Get-Resource, dan is dit de fout die we zouden zien.
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
Ziet u hoe deze verwijst naar de Get-Resource functie als de bron van het probleem? Dat vertelt de gebruiker iets nuttigs.
Omdat $PSItem een ErrorRecordis, kunnen we ook ThrowTerminatingError deze manier gebruiken om opnieuw te gooien.
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
Hiermee wijzigt u de bron van de fout in de cmdlet en verbergt u de interne functies van uw functie voor de gebruikers van uw cmdlet.
Probeer beëindigingsfouten te veroorzaken
Kirk Munro wijst erop dat sommige uitzonderingen alleen beëindigingsfouten zijn bij uitvoering binnen een try/catch blok. Hier is het voorbeeld dat hij me gaf dat een delen-door-nul runtime-uitzondering genereert.
function Start-Something { 1/(1-1) }
Roep het op deze manier aan om te zien hoe het de fout genereert en toch het bericht uitvoert.
&{ Start-Something; Write-Output "We did it. Send Email" }
Maar door diezelfde code in een try/catchte plaatsen, zien we iets anders.
try
{
&{ Start-Something; Write-Output "We did it. Send Email" }
}
catch
{
Write-Output "Notify Admin to fix error and send email"
}
We zien dat de fout een fatale fout wordt en dat het eerste bericht niet wordt weergegeven. Wat ik niet leuk vind aan deze is dat u deze code in een functie kunt hebben en dat deze anders werkt als iemand een try/catchgebruikt.
Ik heb hier zelf geen problemen mee gehad, maar het is een hoekzaak waar ik rekening mee moet houden.
$PSCmdlet.ThrowTerminatingError() in try/catch
Een nuance van $PSCmdlet.ThrowTerminatingError() is dat het een afsluitfout binnen uw Cmdlet creëert, maar het verandert in een niet-afsluitfout nadat het uw Cmdlet heeft verlaten. Hierdoor wordt de aanroeper van uw functie belast om te bepalen hoe de fout moet worden afgehandeld. Ze kunnen deze weer omzetten in een afsluitfout met behulp van -ErrorAction Stop of het aanroepen vanuit een try{...}catch{...}.
Sjablonen voor openbare functies
Een laatste inzicht dat ik uit mijn gesprek met Kirk Munro meeneem, is dat hij een try{...}catch{...} plaatst rond elk begin-, process- en end-blok in al zijn geavanceerde functies. In die algemene catch blocks heeft hij één regel met behulp van $PSCmdlet.ThrowTerminatingError($PSItem) om alle uitzonderingen af te handelen die zijn functies verlaten.
function Start-Something
{
[CmdletBinding()]
param()
process
{
try
{
...
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
Omdat alles zich in een try verklaring binnen zijn functies bevindt, werkt alles consistent. Dit geeft ook schone fouten aan de eindgebruiker die de interne code van de gegenereerde fout verbergt.
Trap
Ik heb me gericht op het try/catch aspect van uitzonderingen. Maar er is één legacyfunctie die ik moet vermelden voordat we dit afsluiten.
Een trap wordt in een script of functie geplaatst om alle uitzonderingen te ondervangen die in dat bereik plaatsvinden. Wanneer er een uitzondering optreedt, wordt de code in de trap uitgevoerd en wordt de normale code voortgezet. Als er meerdere uitzonderingen optreden, wordt de trap steeds opnieuw aangeroepen.
trap
{
Write-Log $PSItem.ToString()
}
throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')
Ik heb deze benadering nooit gebruikt, maar ik kan de waarde zien in admin- of controllerscripts die alle uitzonderingen registreren en vervolgens nog steeds worden uitgevoerd.
Opmerkingen bij afsluiting
Het toevoegen van de juiste verwerking van uitzonderingen aan uw scripts maakt ze niet alleen stabieler, maar maakt het ook eenvoudiger voor u om problemen met deze uitzonderingen op te lossen.
Ik heb veel tijd besteed aan over throw te praten omdat het een kernconcept is in het omgaan met uitzonderingen. PowerShell gaf ons ook Write-Error die alle situaties afhandelt waarin u throwzou gebruiken. Denk dus niet dat u throw moet gebruiken nadat u dit hebt gelezen.
Nu ik de tijd heb genomen om over de verwerking van uitzonderingen in dit detail te schrijven, ga ik overschakelen naar het gebruik van Write-Error -Stop om fouten in mijn code te genereren. Ik ga ook Kirk's advies opvolgen en ThrowTerminatingError mijn standaard foutafhandelaar maken voor alle functies.