- 05/23/2020
- 14 minuti a leggere
-
- j
- f
la gestione degli errori è solo una parte della vita, quando si tratta di scrivere codice. Spesso possiamo controllare e validarecondizioni per il comportamento previsto. Quando accade l’imprevisto, ci rivolgiamo alla gestione delle eccezioni., Youcan facilmente gestire le eccezioni generate dal codice di altre persone o è possibile generare il ownexceptions per gli altri a gestire.
Nota
La versione originale di questo articolo è apparso sul blog scritto da @KevinMarquette. Il team di PowerShell ringrazia Kevin per aver condiviso questo contenuto con noi. Si prega di controllare il suo blog atPowerShellExplained.com.
Terminologia di base
Abbiamo bisogno di coprire alcuni termini di base prima di saltare in questo.
Eccezione
Un’eccezione è come un evento che viene creato quando la normale gestione degli errori non può gestire il problema.,Cercando di dividere un numero per zero o esaurendo la memoria sono esempi di qualcosa che crea anexception. A volte l’autore del codice che stai utilizzando crea eccezioni per determinati problemiquando accadono.
Lancia e cattura
Quando si verifica un’eccezione, diciamo che viene generata un’eccezione. Per gestire un’eccezione lanciata, tubisogno di prenderlo. Se viene generata un’eccezione e non viene rilevata da qualcosa, lo script stopsexecuting.
Lo stack di chiamate
Lo stack di chiamate è l’elenco delle funzioni che si sono chiamate a vicenda., Quando viene chiamata una funzione, essaviene aggiunto allo stack o alla parte superiore dell’elenco. Quando la funzione esce o ritorna, viene rimossadallo stack.
Quando viene generata un’eccezione, lo stack di chiamate viene controllato in modo che un gestore di eccezioni possa intercettare.
Errori di terminazione e non terminazione
Un’eccezione è generalmente un errore di terminazione. Un’eccezione generata viene catturata o itterminates l’esecuzione corrente. Per impostazione predefinita, un errore non terminante viene generato da Write-Error
e aggiunge un errore al flusso di output senza generare un’eccezione.,
Lo faccio notare perchéWrite-Error
e altri errori non terminanti non attivanocatch
.
Ingoiare un’eccezione
Questo è quando si cattura un errore solo per sopprimerlo. Fatelo con cautela perché può rendere i problemi di doubleshooting molto difficili.
Sintassi di base dei comandi
Ecco una rapida panoramica della sintassi di base per la gestione delle eccezioni utilizzata in PowerShell.
Lancia
Per creare il nostro evento di eccezione, generiamo un’eccezione con la parola chiavethrow
.,
function Start-Something{ throw "Bad thing happened"}
Questo crea un’eccezione di runtime che è un errore di terminazione. È gestito da un catch
in una funzione di chiamata o esce dallo script con un messaggio come questo.
Write-Error-ErrorAction Stop
Ho detto cheWrite-Error
non genera un errore di terminazione per impostazione predefinita. Se si specifica-ErrorAction Stop
,Write-Error
genera un errore di terminazione che può essere gestito con uncatch
.,
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Grazie a Lee Dailey per aver ricordato di usare -ErrorAction Stop
in questo modo.
Cmdlet-ErrorAction Stop
Se si specifica-ErrorAction Stop
su qualsiasi funzione avanzata o cmdlet, si trasformano tutte le istruzioniWrite-Error
in errori di terminazione che interrompono l’esecuzione o che possono essere gestiti da uncatch
.,
Start-Something -ErrorAction Stop
Try/Catch
Il modo in cui la gestione delle eccezioni funziona in PowerShell (e in molte altre lingue) è che primatry
come sezione di codice e se genera un errore, puoicatch
. Ecco un rapido esempio.
Lo script catch
viene eseguito solo se si verifica un errore di terminazione. Se try
viene eseguito correttamente, thenit salta ilcatch
.,
Try/Finally
A volte non è necessario gestire un errore ma è comunque necessario eseguire del codice se si verifica un’eccezione o meno. Uno scriptfinally
fa esattamente questo.
Dai un’occhiata a questo esempio:
Ogni volta che apri o ti connetti a una risorsa, dovresti chiuderla. SeExecuteNonQuery()
genera un’eccezione, la connessione non viene chiusa. Ecco lo stesso codice all’interno di un blocco try/finally
.
In questo esempio, la connessione viene chiusa se si verifica un errore. È anche chiuso se non c’èerror., Lo scriptfinally
viene eseguito ogni volta.
Poiché non stai rilevando l’eccezione, viene comunque propagata nello stack delle chiamate.
Try/Catch/Finally
È perfettamente valido usarecatch
efinally
insieme. La maggior parte delle volte userai uno o l’altro, ma potresti trovare scenari in cui usi entrambi.
PS PSItem
Ora che abbiamo ottenuto le basi di mezzo, possiamo scavare un po ‘ più a fondo.,
All’interno del bloccocatch
, c’è una variabile automatica ($PSItem
o$_
) di tipoErrorRecord
che contiene i dettagli sull’eccezione. Ecco una rapida panoramica di alcune delle proprietà chiave.
Per questi esempi, ho usato un percorso non valido in ReadAllText
per generare questa eccezione.
::ReadAllText( '\\test\no\filefound.log')
PSItem.toString ()
Questo ti dà il messaggio più pulito da usare nella registrazione e nell’output generale., ToString()
viene chiamato automaticamente se $PSItem
è posizionato all’interno di una stringa.
catch{ Write-Output "Ran into an issue: $($PSItem.ToString())"}catch{ Write-Output "Ran into an issue: $PSItem"}
PS PSItem.InvocationInfo
Questa proprietà contiene informazioni aggiuntive raccolte da PowerShell sulla funzione o scriptwhere l’eccezione è stata generata. Ecco il InvocationInfo
dall’eccezione di esempio che Icreated.
I dettagli importanti qui mostrano il ScriptName
, il Line
del codice e il ScriptLineNumber
dove è iniziata l’invocazione.
PS PSItem.,ScriptStackTrace
Questa proprietà mostra l’ordine delle chiamate di funzione che ti hanno portato al codice in cui è stata generata l’eccezione.
Sto solo effettuando chiamate a funzioni nello stesso script, ma questo traccerebbe le chiamate se fossero coinvolti multiplescripts.
PS PSItem.Eccezione
Questa è l’eccezione effettiva che è stata generata.
PS PSItem.Eccezione.Message
Questo è il messaggio generale che descrive l’eccezione ed è un buon punto di partenza whentroubleshooting. La maggior parte delle eccezioni ha un messaggio predefinito, ma può anche essere impostata su qualcosa di personalizzato quando viene generata l’eccezione.,
PS> $PSItem.Exception.MessageException calling "ReadAllText" with "1" argument(s): "The network path was not found."
Questo è anche il messaggio restituito quando si chiama$PSItem.ToString()
se non c’era un set sulErrorRecord
.
PS PSItem.Eccezione.Le eccezioni InnerException
possono contenere eccezioni interne. Questo è spesso il caso quando il codice che stai chiamandocatches un’eccezione e genera un’eccezione diversa. L’eccezione originale è collocata all’internola nuova eccezione.
PS> $PSItem.Exception.InnerExceptionMessageThe network path was not found.
Lo rivisiterò più tardi quando parlerò di re-throwing eccezioni.
PS PSItem.Eccezione.,StackTrace
Questo èStackTrace
per l’eccezione. Ho mostrato unScriptStackTrace
sopra, ma questo è per le chiamate al codice gestito.
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 )
Si ottiene questa traccia dello stack solo quando l’evento viene generato dal codice gestito. Chiamo un .NETframework funzione direttamente in modo che è tutto quello che possiamo vedere in questo esempio. Generalmente quando stai cercando una traccia di stack, stai cercando dove il tuo codice si ferma e iniziano le chiamate di sistema.
Lavorare con le eccezioni
Ci sono più eccezioni rispetto alla sintassi di base e alle proprietà delle eccezioni.,
Cattura delle eccezioni tipizzate
Puoi essere selettivo con le eccezioni che catturi. Le eccezioni hanno un tipo e puoi specificareil tipo di eccezione che vuoi catturare.
Il tipo di eccezione viene controllato per ogni blocco catch
fino a quando non viene trovato uno che corrisponde al tuo exception.It è importante rendersi conto che le eccezioni possono ereditare da altre eccezioni. Nell’esempio precedente,FileNotFoundException
eredita daIOException
. Quindi se IOException
è stato il primo, allora verrebbe chiamato., Solo un blocco catch viene richiamato anche se ci sono più corrispondenze.
Se avessimo un System.IO.PathTooLongException
, il IOException
corrisponderebbe ma se avessimo unInsufficientMemoryException
allora nulla lo catturerebbe e si propagherebbe nello stack.
Cattura più tipi contemporaneamente
È possibile catturare più tipi di eccezioni con la stessa istruzionecatch
.
Grazie/u/Sheppard_Ra
per aver suggerito questa aggiunta.
Lancio di eccezioni tipizzate
Puoi lanciare eccezioni tipizzate in PowerShell., Invece di chiamarethrow
con una stringa:
throw "Could not find: $path"
Usa un acceleratore di eccezioni come questo:
throw "Could not find: $path"
Ma devi specificare un messaggio quando lo fai in questo modo.
È anche possibile creare una nuova istanza di un’eccezione da lanciare. Il messaggio è facoltativo quando lo si faquesto perché il sistema ha messaggi predefiniti per tutte le eccezioni incorporate.
throw ::new()throw ::new("Could not find path: $path")
Se non si utilizza PowerShell 5.0 o versione successiva, è necessario utilizzare l’approccio precedenteNew-Object
.,
Utilizzando un’eccezione digitata, tu (o altri) puoi catturare l’eccezione in base al tipo come menzionato nella sezione precedente.
Write-Error-Exception
Possiamo aggiungere queste eccezioni tipizzate aWrite-Error
e possiamo ancoracatch
gli errori di exceptiontype. Usa Write-Error
come in questi esempi:
Allora possiamo prenderlo in questo modo:
catch { Write-Log $PSItem.ToString()}
La grande lista di eccezioni .NET
Ho compilato una lista principale con l’aiuto della comunità Reddit/r/PowerShell che contienecentinaia di.,Eccezioni NETTE per completare questo post.
- La grande lista di eccezioni.NET
Comincio cercando quella lista per le eccezioni che si sentono come se fossero una buona misura per mysituation. Dovresti provare a usare le eccezioni nello spazio dei nomi System
di base.
Le eccezioni sono oggetti
Se inizi a utilizzare molte eccezioni digitate, ricorda che sono oggetti. Diverse eccezioniavere diversi costruttori e proprietà., Se guardiamo il FileNotFoundExceptiondocumentation per System.IO.FileNotFoundException
, vediamo che possiamo passare in un messaggio e un filepath.
::new("Could not find file", $path)
E ha una proprietàFileName
che espone il percorso del file.
catch { Write-Output $PSItem.Exception.FileName}
È necessario consultare la documentazione.NET per altri costruttori e proprietà dell’oggetto.
Re-gettando un’eccezione
Se tutto quello che farai nel tuo bloccocatch
èthrow
la stessa eccezione, allora non farlocatch
., Si dovrebbe solo catch
un’eccezione che si prevede di gestire o eseguire qualche azione quando ithappens.
Ci sono momenti in cui si desidera eseguire un’azione su un’eccezione, ma rieseguire l’eccezione in modo che qualcosa a valle possa gestirla. Potremmo scrivere un messaggio o registrare il problema vicino a dove lo scopriamo ma gestire il problema più in alto nello stack.
catch{ Write-Log $PSItem.ToString() throw $PSItem}
Abbastanza interessante, possiamo chiamare throw
dall’interno di catch
e ri-lancia la currentexception.,
catch{ Write-Log $PSItem.ToString() throw}
Vogliamo lanciare nuovamente l’eccezione per preservare le informazioni di esecuzione originali come lo script di origine e il numero di riga. Se lanciamo una nuova eccezione a questo punto, nasconde dove è iniziata l’eccezione.
Re-throwing una nuova eccezione
Se si cattura un’eccezione ma si desidera lanciarne una diversa, è necessario annidare l’originalexception all’interno di quella nuova. Ciò consente a qualcuno nello stack di accedervi come$PSItem.Exception.InnerException
.
catch{ throw ::new('Could not access field',$PSItem.Exception)}
P PSCmdlet.,ThrowTerminatingError ()
L’unica cosa che non mi piace nell’usare throw
per le eccezioni raw è che il messaggio di errore indica l’istruzione throw
e indica che la riga è dove si trova il problema.
Avere il messaggio di errore mi dice che il mio script è rotto perché ho chiamatothrow
sulla riga 31 è un messaggio abad per gli utenti del tuo script da vedere. Non dice loro nulla di utile.
Dexter Dhami ha sottolineato che posso usareThrowTerminatingError()
per correggerlo.,
Se assumiamo che ThrowTerminatingError()
è stato chiamato all’interno di una funzione chiamata Get-Resource
, alloraquesto è l’errore che vedremmo.
Vedi come punta alla funzioneGet-Resource
come fonte del problema? Questo dice all’utente qualcosa di utile.
Poiché $PSItem
è un ErrorRecord
, possiamo anche usare ThrowTerminatingError
in questo modo per ri-lanciare.,
catch{ $PSCmdlet.ThrowTerminatingError($PSItem)}
Questo cambia la fonte dell’errore nel Cmdlet e nasconde gli interni della tua funzione dagli utenti del tuo Cmdlet.
Try può creare errori di terminazione
Kirk Munro sottolinea che alcune eccezioni terminano gli errori solo quando vengono eseguiti all’interno di un bloccotry/catch
. Ecco l’esempio che mi ha dato che genera un’eccezione di runtime divide per zero.
function Start-Something { 1/(1-1) }
Quindi invocarlo in questo modo per vederlo generare l’errore e comunque inviare il messaggio.,
&{ Start-Something; Write-Output "We did it. Send Email" }
Ma inserendo lo stesso codice all’interno di untry/catch
, vediamo qualcos’altro accadere.
try{ &{ Start-Something; Write-Output "We did it. Send Email" }}catch{ Write-Output "Notify Admin to fix error and send email"}
Vediamo l’errore diventare un errore di terminazione e non emettere il primo messaggio. Quello che non mi piace di questo è che puoi avere questo codice in una funzione e agisce in modo diverso se qualcuno usa un try/catch
.
Non ho avuto problemi con questo me stesso, ma è un caso d’angolo di cui essere a conoscenza.
P PSCmdlet.,ThrowTerminatingError () all’interno di try/catch
Una sfumatura di $PSCmdlet.ThrowTerminatingError()
è che crea un errore di terminazione all’interno di yourCmdlet ma si trasforma in un errore non terminante dopo aver lasciato il Cmdlet. Questo lascia il burdenon il chiamante della tua funzione per decidere come gestire l’errore. Possono trasformarlo in un errore di terminazione usando -ErrorAction Stop
o chiamandolo da try{...}catch{...}
.,
Modelli di funzioni pubbliche
Un ultimo modo che ho avuto con la mia conversazione con Kirk Munro è stato quello di posizionare untry{...}catch{...}
intorno a ognibegin
,process
eend
blocca in tutte le sue funzioni avanzate. In quei blocchi catch generici, ha una singola riga usando $PSCmdlet.ThrowTerminatingError($PSItem)
per gestire tutte le eccezioni che lasciano le sue funzioni.
function Start-Something{ param() process { try { ... } catch { $PSCmdlet.ThrowTerminatingError($PSItem) } }}
Poiché tutto è in un’istruzione try
all’interno delle sue funzioni, tutto agisce in modo coerente., Thisalso dà errori puliti all’utente finale che nasconde il codice interno dall’errore generato.
Trappola
Mi sono concentrato sull’aspetto delle eccezionitry/catch
. Ma c’è una caratteristica legacy che devo menzionareprima di concludere questo.
A trap
viene inserito in uno script o una funzione per rilevare tutte le eccezioni che si verificano in quell’ambito. Quando si verifica un’eccezione, il codice nel trap
viene eseguito e quindi il codice normale continua. Se si verificano più eccezioni, la trappola viene chiamata più e più volte.,
Personalmente non ho mai adottato questo approccio, ma posso vedere il valore negli script di amministratore o controller che registrano tutte le eccezioni, quindi continuano a essere eseguiti.
Note di chiusura
L’aggiunta di una corretta gestione delle eccezioni agli script non solo li rende più stabili, ma rende anche più facile risolvere tali eccezioni.
Ho passato molto tempo a parlarethrow
perché è un concetto fondamentale quando si parla di exceptionhandling., PowerShell ci ha anche dato Write-Error
che gestisce tutte le situazioni in cui dovresti usarethrow
. Quindi non pensare che devi usare throw
dopo aver letto questo.
Ora che ho avuto il tempo di scrivere sulla gestione delle eccezioni in questo dettaglio, ho intenzione di passare all’utilizzo diWrite-Error -Stop
per generare errori nel mio codice. Ho anche intenzione di prendere il consiglio di Kirk e fare ThrowTerminatingError
il mio gestore di eccezioni goto per ogni funzione.