@@ -308,85 +308,173 @@ If neither of those commands works in the scenarion then `throw` shall be used.
308308Commands are publicly exported commands from the module, and the source for
309309commands are located in the folder ` ./source/Public ` .
310310
311- #### Non-Terminating Error
311+ #### Error Handling Guidelines
312312
313- A non-terminating error should only be used when a command shall be able to
314- handle (ignoring) an error and continue processing and still give the user
315- an expected outcome .
313+ Public commands should primarily use ` Write-Error ` for error handling, as it
314+ provides the most flexible and predictable behavior for callers. The statement
315+ ` throw ` shall never be used in public commands .
316316
317- With a non-terminating error the user is able to decide whether the command
318- should throw or continue processing on error. The user can pass the
319- parameter and value ` -ErrorAction 'SilentlyContinue' ` to the command to
320- ignore the error and allowing the command to continue, for example the
321- command could then return ` $null ` . But if the user passes the parameter
322- and value ` -ErrorAction 'Stop' ` the same error will throw a terminating
323- error telling the user the expected outcome could not be achieved.
317+ ##### When to Use Write-Error
324318
325- The below example checks to see if a database exist, if it doesn't a
326- non-terminating error are called. The user is able to either ignore the
327- error or have it throw depending on what value the user specifies
328- in parameter ` ErrorAction ` (or ` $ErrorActionPreference ` ).
319+ Use ` Write-Error ` in most scenarios where a public command encounters an error:
320+
321+ - ** Validation failures** : When input parameters fail validation
322+ - ** Resource not found** : When a requested resource doesn't exist
323+ - ** Operation failures** : When an operation cannot be completed
324+ - ** Permission issues** : When access is denied
325+
326+ ` Write-Error ` generates a non-terminating error by default, allowing the caller
327+ to control behavior via the ` -ErrorAction ` parameter. This is the expected
328+ PowerShell pattern.
329+
330+ ** Example** : A command that retrieves a database returns an error if not found:
329331
330332``` powershell
331333if (-not $databaseExist)
332334{
333335 $errorMessage = $script:localizedData.MissingDatabase -f $DatabaseName
334336
335- Write-Error -Message $errorMessage -Category 'InvalidOperation' -ErrorId 'GS0001' -TargetObject $DatabaseName
337+ Write-Error -Message $errorMessage -Category 'ObjectNotFound' -ErrorId 'GSD0001' -TargetObject $DatabaseName
338+
339+ return
336340}
337341```
338342
339- #### Terminating Error
343+ The caller controls the behavior:
340344
341- A terminating error is an error that the user are not able to ignore by
342- passing a parameter to the command (like for non-terminating errors).
345+ ``` powershell
346+ # Returns $null, continues execution (non-terminating)
347+ $db = Get-Database -Name 'NonExistent'
343348
344- If a command shall throw an terminating error then the statement ` throw ` shall
345- not be used, neither shall the command ` Write-Error ` with the parameter
346- ` -ErrorAction Stop ` . Always use the method ` $PSCmdlet.ThrowTerminatingError() `
347- to throw a terminating error. The exception is when a ` [ValidateScript()] `
348- has to throw an error, then ` throw ` must be used.
349+ # Throws terminating error, stops execution
350+ $db = Get-Database -Name 'NonExistent' -ErrorAction 'Stop'
351+ ```
349352
350353> [ !IMPORTANT]
351- > Below output assumes ` $ErrorView ` is set to ` 'NormalView' ` in the
352- > PowerShell session.
353-
354- When using ` throw ` it will fail on the line with the throw statement
355- making it look like it is that statement inside the function that failed,
356- which is not correct since it is either a previous command or evaluation
357- that failed resulting in the line with the ` throw ` being called. This is
358- an example when using ` throw ` :
359-
360- ``` plaintext
361- Exception:
362- Line |
363- 2 | throw 'My error'
364- | ~~~~~~~~~~~~~~~~
365- | My error
366- ```
354+ > Always use ` return ` after ` Write-Error ` to prevent further processing in the
355+ > command. Without ` return ` , the command continues executing, which may cause
356+ > unexpected behavior or additional errors.
357+
358+ ##### Understanding $PSCmdlet.ThrowTerminatingError
367359
368- When instead using ` $PSCmdlet.ThrowTerminatingError() ` :
360+ The ` $PSCmdlet.ThrowTerminatingError() ` method is ** not recommended** for public
361+ commands despite its name suggesting it throws a terminating error. It actually
362+ throws a ** command-terminating error** which has unexpected behavior when called
363+ from another command.
364+
365+ ** The Problem** : When a command uses ` $PSCmdlet.ThrowTerminatingError() ` , the
366+ error behaves like a non-terminating error to the caller unless the caller
367+ explicitly uses ` -ErrorAction 'Stop' ` . This creates confusing behavior:
369368
370369``` powershell
371- $PSCmdlet.ThrowTerminatingError(
372- [System.Management.Automation.ErrorRecord]::new(
373- 'MyError',
374- 'GS0001',
375- [System.Management.Automation.ErrorCategory]::InvalidOperation,
376- 'MyObjectOrValue'
370+ function Get-Something
371+ {
372+ [CmdletBinding()]
373+ param ()
374+
375+ $PSCmdlet.ThrowTerminatingError(
376+ [System.Management.Automation.ErrorRecord]::new(
377+ 'Database not found',
378+ 'GS0001',
379+ [System.Management.Automation.ErrorCategory]::ObjectNotFound,
380+ 'MyDatabase'
381+ )
377382 )
378- )
383+
384+ # This line never executes (correctly stopped)
385+ Write-Output 'After error'
386+ }
387+
388+ function Start-Operation
389+ {
390+ [CmdletBinding()]
391+ param ()
392+
393+ Get-Something
394+
395+ # BUG: This line DOES execute even though Get-Something threw an error!
396+ Write-Output 'Operation completed'
397+ }
398+
399+ # Caller must use -ErrorAction 'Stop' to prevent continued execution
400+ Start-Operation -ErrorAction 'Stop'
379401```
380402
381- The result from ` $PSCmdlet.ThrowTerminatingError() ` shows that the command
382- failed (in this example ` Get-Something ` ) and returns a clear category and
383- error code.
384-
385- ``` plaintext
386- Get-Something : My Error
387- At line:1 char:1
388- + Get-Something
389- + ~~~~~~~~~~~~~
390- + CategoryInfo : InvalidOperation: (MyObjectOrValue:String) [Get-Something], Exception
391- + FullyQualifiedErrorId : GS0001,Get-Something
403+ Because of this behavior, ** use ` Write-Error ` instead** in public commands.
404+
405+ ##### When $PSCmdlet.ThrowTerminatingError Can Be Used
406+
407+ Use ` $PSCmdlet.ThrowTerminatingError() ` only in these limited scenarios:
408+
409+ 1 . ** Private functions** that are only called internally and where the behavior
410+ is well understood
411+ 2 . ** Assert-style commands** (like ` Assert-SqlDscLogin ` ) whose purpose is to
412+ throw on failure
413+ 3 . ** Commands with ` SupportsShouldProcess ` ** where an error in the
414+ ` ShouldProcess ` block must terminate (before state changes)
415+
416+ In these cases, ensure callers are aware of the behavior and use
417+ ` -ErrorAction 'Stop' ` when calling the command from other commands.
418+
419+ ##### Exception Handling in Commands
420+
421+ When catching exceptions in try-catch blocks, re-throw errors using ` Write-Error `
422+ with the original exception:
423+
424+ ``` powershell
425+ try
426+ {
427+ $database.Create()
428+ }
429+ catch
430+ {
431+ $errorMessage = $script:localizedData.CreateDatabaseFailed -f $DatabaseName
432+
433+ Write-Error -Message $errorMessage -Category 'InvalidOperation' -ErrorId 'CD0001' -TargetObject $DatabaseName -Exception $_.Exception
434+
435+ return
436+ }
392437```
438+
439+ ##### Pipeline Processing Considerations
440+
441+ For commands that accept pipeline input and process multiple items, use
442+ ` Write-Error ` to allow processing to continue for remaining items:
443+
444+ ``` powershell
445+ process
446+ {
447+ foreach ($item in $Items)
448+ {
449+ if (-not (Test-ItemValid $item))
450+ {
451+ Write-Error -Message "Invalid item: $item" -Category 'InvalidData' -ErrorId 'PI0001' -TargetObject $item
452+ continue
453+ }
454+
455+ # Process valid items
456+ Process-Item $item
457+ }
458+ }
459+ ```
460+
461+ The caller can control whether to stop on first error or continue:
462+
463+ ``` powershell
464+ # Continue processing all items, collect errors
465+ $results = $items | Process-Items
466+
467+ # Stop on first error
468+ $results = $items | Process-Items -ErrorAction 'Stop'
469+ ```
470+
471+ ##### Summary
472+
473+ | Scenario | Use | Why |
474+ | ----------| -----| -----|
475+ | General error handling in public commands | ` Write-Error ` | Provides consistent, predictable behavior; allows caller to control termination |
476+ | Pipeline processing with multiple items | ` Write-Error ` | Allows processing to continue for remaining items |
477+ | Assert-style commands | ` $PSCmdlet.ThrowTerminatingError() ` | Command purpose is to throw on failure |
478+ | Private functions (internal use only) | ` $PSCmdlet.ThrowTerminatingError() ` or ` Write-Error ` | Behavior is understood by internal callers |
479+ | Any command | Never use ` throw ` | Poor error messages; unpredictable behavior |
480+ | Parameter validation attributes | ` throw ` | Only valid option within ` [ValidateScript()] ` |
0 commit comments