Quand devrais-je utiliser Write-Error contre Throw? Erreurs terminales et non terminales

145

En regardant un script Get-WebFile sur PoshCode, http://poshcode.org/3226 , j'ai remarqué cet étrange engin:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

Quelle en est la raison par opposition à la suivante?

$URL_Format_Error = [string]"..."
Throw $URL_Format_Error

Ou encore mieux:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error

Si je comprends bien, vous devez utiliser Write-Error pour les erreurs non terminantes et Throw pour les erreurs terminales, il me semble donc que vous ne devriez pas utiliser Write-Error suivi de Return. Y a-t-il une différence?

Bill Barry
la source
4
Que voulez-vous dire? Si Write_error permet au script de continuer, il est très compréhensible d'avoir une instruction de retour après Write-Error. L'erreur a été écrite et vous revenez au code qui a appelé la fonction en premier lieu. Puisque Throw est destiné à la fin des erreurs, il se terminera automatiquement de sorte qu'une instruction return dans une déclaration throw est inutile
Gisli
1
@Gisli: Il est important de noter que returncela ne revient pas à l'appelant dans le processbloc d'une fonction (avancée); à la place, il passe à l' objet d'entrée suivant dans le pipeline. En effet, c'est le scénario typique pour générer des erreurs sans fin: si le traitement d'autres objets d'entrée est toujours possible.
mklement0
1
Notez que Throwgénère une erreur de fin de script , qui n'est pas la même que erreurs de fin de déclaration déclenchées, par exemple, par Get-Item -NoSuchParameterou 1 / 0.
mklement0

Réponses:

189

Write-Errordoit être utilisé si vous souhaitez informer l'utilisateur d'une erreur non critique. Par défaut, il ne fait qu'imprimer un message d'erreur en texte rouge sur la console. Cela n'empêche pas un pipeline ou une boucle de se poursuivre. Throwd'autre part produit ce que l'on appelle une erreur de fin. Si vous utilisez throw, le pipeline et / ou la boucle de courant seront terminés. En fait, toute exécution sera interrompue à moins que vous n'utilisiez trapune try/catchstructure ou pour gérer l'erreur de fin.

Il y a une chose à noter, si vous définissez $ErrorActionPreferenceà"Stop" et utiliser Write-Erroril produira une erreur de terminaison .

Dans le script que vous avez lié, nous trouvons ceci:

if ($url.Contains("http")) {
       $request = [System.Net.HttpWebRequest]::Create($url)
}
else {
       $URL_Format_Error = [string]"Connection protocol not specified. Recommended action: Try again using protocol (for example 'http://" + $url + "') instead. Function aborting..."
       Write-Error $URL_Format_Error
    return
   }

Il semble que l'auteur de cette fonction ait voulu arrêter l'exécution de cette fonction et afficher un message d'erreur à l'écran mais ne voulait pas que le script entier s'arrête de s'exécuter. L'auteur du script aurait pu utiliser throwmais cela signifierait que vous devrez utiliser a try/catchlors de l'appel de la fonction.

returnquittera la portée actuelle qui peut être une fonction, un script ou un bloc de script. Ceci est mieux illustré avec du code:

# A foreach loop.
foreach ( $i in  (1..10) ) { Write-Host $i ; if ($i -eq 5) { return } }

# A for loop.
for ($i = 1; $i -le 10; $i++) { Write-Host $i ; if ($i -eq 5) { return } }

Sortie pour les deux:

1
2
3
4
5

Un gotcha ici utilise returnavec ForEach-Object. Cela n'interrompra pas le traitement comme on pourrait s'y attendre.

Plus d'information:

Andy Arismendi
la source
Ok, donc Throw arrêtera tout, Write-Error + return arrêtera uniquement la fonction en cours.
Bill Barry
@BillBarry J'ai mis à jour un peu ma réponse avec une explication de return.
Andy Arismendi
Qu'en est-il de Write-Error suivi de exit (1) pour garantir le retour du code d'erreur approprié au système d'exploitation? Est-ce toujours approprié?
pabrams
19

La principale différence entre le applet de commande Write-Error et le mot clé throw dans PowerShell est que la première imprime simplement du texte dans le flux d'erreur standard (stderr) , tandis que la seconde met fin en fait au traitement de la commande ou de la fonction en cours d'exécution, qui est alors gérée par PowerShell en envoyant des informations sur l'erreur à la console.

Vous pouvez observer le comportement différent des deux dans les exemples que vous avez fournis:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

Dans cet exemple, le returnmot - clé a été ajouté explicitement arrêter l'exécution du script après l'envoi du message d'erreur à la console. Dans le deuxième exemple, en revanche, le returnmot-clé n'est pas nécessaire car la terminaison est implicitement effectuée par throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Enrico Campidoglio
la source
2
si vous avez $ ErrorActionPreference = "Stop", Write-Error mettra également fin au processus.
Michael Freidgeim
4
Bonnes informations, mais alors que le flux d'erreur de PowerShell est analogue au flux stderr basé sur du texte dans d'autres shells, comme tous les flux PowerShell, il contient des objets , à savoir des [System.Management.Automation.ErrorRecord]instances, qui sont par défaut collectées dans la $Errorcollection automatique ( $Error[0]contient l'erreur la plus récente). Même si vous utilisez simplement Write-Erroravec une chaîne , cette chaîne est enveloppée dans une [System.Management.Automation.ErrorRecord]instance.
mklement0
16

Important : il existe 2 types d'erreurs de fin , que les rubriques d'aide actuelles confondent malheureusement :

  • Déclaration -terminating erreurs, tel que rapporté par cmdlets dans certaines situations non récupérables et des expressions dans lesquelles une exception .NET / une erreur d'exécution PS se produit; seule l' instruction est terminée et l' exécution du script se poursuit par défaut .

  • écriture -terminating erreurs (plus précisément: instance d' exécution de terminaison ), soit comme déclenchée parThrowou parescaladeun des autres types d'erreur parintermédiairepréférence variable action d'erreur / valeur de paramètreStop.
    À moins d'être interceptés, ils terminent l'espace d'exécution actuel (thread); c'est-à-dire qu'ils terminent non seulement le script courant, mais aussi tous ses appelants, le cas échéant).

Pour une présentation complète de la gestion des erreurs de PowerShell, consultez ce problème de documentation GitHub .

Le reste de cet article se concentre sur les erreurs non terminales par rapport aux erreurs de fin d'instruction .


Pour compléter les réponses utiles existantes en mettant l'accent sur le cœur de la question: Comment choisissez si vous souhaitez signaler un Instruction- terminaison ou non terminaison erreur ?

Le rapport d'erreurs de la cmdlet contient des instructions utiles; laissez-moi tenter un résumé pragmatique :

L'idée générale derrière les erreurs sans terminaison est de permettre le traitement "tolérant aux pannes" de grands ensembles d'entrées : l'échec du traitement d'un sous - ensemble des objets d'entrée ne doit pas (par défaut) interrompre le processus - potentiellement de longue durée - dans son ensemble , vous permettant d'inspecter les erreurs et de retraiter uniquement les objets ayant échoué ultérieurement - comme indiqué via les enregistrements d'erreurs collectés dans la variable automatique $Error.

  • Signalez une erreur NON-TERMINATING , si votre applet de commande / fonction avancée:

    • accepte PLUSIEURS objets d'entrée , via l'entrée du pipeline et / ou des paramètres de tableau, ET
    • des erreurs se produisent pour les objets d'entrée SPÉCIFIQUES , ET
    • ces erreurs N'empêchent PAS LE TRAITEMENT D'AUTRES objets d'entrée EN PRINCIPE ( situation , il peut y avoir aucune entrée gauche des objets et / ou des objets d'entrée précédents peuvent avoir déjà été traitées avec succès).
      • Dans les fonctions avancées, utilisez $PSCmdlet.WriteError()pour signaler une erreur sans fin ( Write-Errormalheureusement, cela ne provoque pas $?de définition $Falsedans la portée de l' appelant - voir ce problème GitHub ).
      • Gérer une erreur sans fin: $?vous indique si la commande la plus récente a signalé au moins un erreur sans fin.
        • Ainsi, $?être $Falsepeut signifier que tout sous-ensemble (non vide) - d'objets d'entrée n'a pas été correctement traité, peut-être l'ensemble entier.
        • La variable de préférence $ErrorActionPreferenceet / ou le paramètre de cmdlet commun -ErrorActionpeuvent modifier le comportement des erreurs sans fin (uniquement) en termes de comportement de sortie d'erreur et si les erreurs sans fin doivent être escaladées en erreurs de fin de script .
  • Signaler une erreur STATEMENT-TERMINATING dans tous les autres cas .

    • Notamment, si une erreur se produit dans une applet de commande / une fonction avancée qui n'accepte qu'un objet d'entrée SINGLE ou NO et génère un objet de sortie NO ou SINGLE ou prend uniquement l'entrée de paramètre et les valeurs de paramètre données empêchent une opération significative.
      • Dans les fonctions avancées, vous devez utiliser $PSCmdlet.ThrowTerminatingError() pour générer une erreur de fin d'instruction.
      • Notez que, en revanche, le Throwmot - clé génère une erreur de fin de script qui annule l' intégralité du script (techniquement: le thread actuel ).
      • Gérer une erreur de fin d'instruction: Un try/catchgestionnaire ou une trapinstruction peut être utilisé (qui ne peut pas être utilisé avec des erreurs sans fin ), mais notez que même les erreurs de fin d' instruction par défaut n'empêchent pas le reste du script de s'exécuter. Comme dans le cas de non-terminaison des erreurs, $?reflète $Falsesi l'instruction précédente a déclenché une erreur de traitement de terminaison.

Malheureusement, toutes les applets de commande principales de PowerShell ne respectent pas ces règles :

  • Bien qu'il soit peu probable d'échouer, New-TemporaryFile(PSv5 +) signalerait une erreur sans fin en cas d'échec, bien qu'il n'accepte pas l'entrée de pipeline et ne produise qu'un seul objet de sortie - cela a été corrigé depuis au moins PowerShell [Core] 7.0, cependant: voir ce GitHub problème .

  • Resume-JobL'aide de affirme que la transmission d'un type de travail non pris en charge (tel qu'un travail créé avec Start-Job, qui n'est pas pris en charge, car Resume-Jobne s'applique qu'aux travaux de flux de travail) provoque une erreur de fin, mais ce n'est pas le cas à partir de PSv5.1.

mklement0
la source
8

Write-Errorpermet au consommateur de la fonction de supprimer le message d'erreur avec -ErrorAction SilentlyContinue(alternativement -ea 0). Bien que thrownécessite untry{...} catch {..}

Pour utiliser un essai ... attraper avec Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}
Rynant
la source
Pourquoi avez-vous besoin d'appeler Write-Error si vous souhaitez supprimer le message d'erreur?
Michael Freidgeim
8

Ajout à la réponse d'Andy Arismendi :

Le fait que Write-Error termine le processus ou non dépend de la$ErrorActionPreference paramètre.

Pour les scripts non triviaux, $ErrorActionPreference = "Stop"est un paramètre recommandé pour échouer rapidement.

"Le comportement par défaut de PowerShell en ce qui concerne les erreurs, qui consiste à continuer en cas d'erreur ... semble très VB6" On Error Resume Next "-ish"

(à partir de http://codebetter.com/jameskovacs/2010/02/25/the-exec-problem/ )

Cependant, cela fait Write-Error fin aux appels.

Pour utiliser Write-Error comme commande sans fin indépendamment des autres paramètres d'environnement, vous pouvez utiliser le paramètre commun -ErrorAction avec la valeur Continue:

 Write-Error "Error Message" -ErrorAction:Continue
Michael Freidgeim
la source
0

Si votre lecture du code est correcte, vous avez raison. Les erreurs de terminaison doivent être utilisées throw, et si vous avez affaire à des types .NET, il est utile de suivre également les conventions d'exception .NET.

yzorg
la source