Skip to content

Commit e24f11c

Browse files
Copilotjohlju
andcommitted
Document ValidateScript exception and clarify ErrorActionPreference usage
Co-authored-by: johlju <[email protected]>
1 parent 5606659 commit e24f11c

File tree

1 file changed

+107
-3
lines changed

1 file changed

+107
-3
lines changed

CONTRIBUTING.md

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ commands are located in the folder `./source/Public`.
312312

313313
Public commands should primarily use `Write-Error` for error handling, as it
314314
provides the most flexible and predictable behavior for callers. The statement
315-
`throw` shall never be used in public commands.
315+
`throw` shall never be used in public commands except within parameter
316+
validation attributes like `[ValidateScript()]` where it is the only option.
316317

317318
##### When to Use Write-Error
318319

@@ -416,6 +417,106 @@ Use `$PSCmdlet.ThrowTerminatingError()` only in these limited scenarios:
416417
In these cases, ensure callers are aware of the behavior and use
417418
`-ErrorAction 'Stop'` when calling the command from other commands.
418419

420+
##### Handling Errors from .NET Methods and Cmdlets
421+
422+
When working with .NET methods (like SMO objects) and PowerShell cmdlets, understand
423+
how exceptions are caught:
424+
425+
**For .NET methods (.NET Framework/Core APIs and SMO):**
426+
- Exceptions are **always caught** in try-catch blocks
427+
- No need to set `$ErrorActionPreference` - the exceptions throw naturally
428+
- Example: `$alertObject.Drop()`, `$database.Create()`, `[System.IO.File]::OpenRead()`
429+
430+
**For PowerShell cmdlets:**
431+
- Use `-ErrorAction 'Stop'` to make errors catchable in try-catch
432+
- **Do not** set `$ErrorActionPreference = 'Stop'` when using `-ErrorAction 'Stop'` - it's redundant
433+
- Only set `$ErrorActionPreference = 'Stop'` if you need blanket error handling for multiple cmdlets
434+
435+
**Recommended pattern for .NET methods:**
436+
437+
```powershell
438+
try
439+
{
440+
# .NET methods throw exceptions automatically - no ErrorActionPreference needed
441+
$alertObject.Drop()
442+
443+
Write-Verbose -Message ($script:localizedData.AlertRemoved -f $alertObject.Name)
444+
}
445+
catch
446+
{
447+
$errorMessage = $script:localizedData.RemoveFailed -f $alertObject.Name
448+
449+
$PSCmdlet.ThrowTerminatingError(
450+
[System.Management.Automation.ErrorRecord]::new(
451+
[System.InvalidOperationException]::new($errorMessage, $_.Exception),
452+
'RSAA0001',
453+
[System.Management.Automation.ErrorCategory]::InvalidOperation,
454+
$alertObject
455+
)
456+
)
457+
}
458+
```
459+
460+
**Pattern for cmdlets with blanket error handling:**
461+
462+
Only use `$ErrorActionPreference = 'Stop'` when you have multiple cmdlets
463+
and don't want to add `-ErrorAction 'Stop'` to each one:
464+
465+
```powershell
466+
try
467+
{
468+
$originalErrorActionPreference = $ErrorActionPreference
469+
$ErrorActionPreference = 'Stop'
470+
471+
# Multiple cmdlets without -ErrorAction on each
472+
$instance = Get-SqlDscManagedComputerInstance -InstanceName $InstanceName
473+
$protocol = Get-SqlDscServerProtocolName -ProtocolName $ProtocolName
474+
$serverProtocol = $instance.ServerProtocols[$protocol.ShortName]
475+
}
476+
catch
477+
{
478+
# Handle error
479+
}
480+
finally
481+
{
482+
$ErrorActionPreference = $originalErrorActionPreference
483+
}
484+
```
485+
486+
> [!IMPORTANT]
487+
> **Do not** use the pattern `$ErrorActionPreference = 'Stop'` followed by
488+
> `-ErrorAction 'Stop'` on the same cmdlet - this is redundant. The
489+
> `-ErrorAction` parameter always takes precedence over `$ErrorActionPreference`.
490+
491+
> [!NOTE]
492+
> The pattern of setting `$ErrorActionPreference = 'Stop'` in a try block
493+
> with restoration in finally is useful for blanket error handling, but NOT
494+
> necessary for .NET method calls (they always throw) or when using
495+
> `-ErrorAction 'Stop'` on individual cmdlets.
496+
497+
##### Parameter Validation with ValidateScript
498+
499+
When using `[ValidateScript()]` attribute on parameters, you **must** use
500+
`throw` for validation failures, as it is the only mechanism that works
501+
within validation attributes:
502+
503+
```powershell
504+
[Parameter()]
505+
[ValidateScript({
506+
if (-not (Test-Path -Path $_))
507+
{
508+
throw ($script:localizedData.Audit_PathParameterValueInvalid -f $_)
509+
}
510+
511+
return $true
512+
})]
513+
[System.String]
514+
$Path
515+
```
516+
517+
This is the **only acceptable use** of `throw` in public commands and private
518+
functions.
519+
419520
##### Exception Handling in Commands
420521

421522
When catching exceptions in try-catch blocks, re-throw errors using `Write-Error`
@@ -475,8 +576,11 @@ $results = $items | Process-Items -ErrorAction 'Stop'
475576
|----------|-----|-----|
476577
| General error handling in public commands | `Write-Error` | Provides consistent, predictable behavior; allows caller to control termination |
477578
| Pipeline processing with multiple items | `Write-Error` | Allows processing to continue for remaining items |
579+
| Catching .NET method exceptions | try-catch without setting `$ErrorActionPreference` | .NET exceptions are always caught automatically |
580+
| Blanket error handling for multiple cmdlets | Set `$ErrorActionPreference = 'Stop'` in try, restore in finally | Avoids adding `-ErrorAction 'Stop'` to each cmdlet |
581+
| Single cmdlet error handling | Use `-ErrorAction 'Stop'` on the cmdlet | Simpler and more explicit than setting `$ErrorActionPreference` |
478582
| Assert-style commands | `$PSCmdlet.ThrowTerminatingError()` | Command purpose is to throw on failure |
479583
| Private functions (internal use only) | `$PSCmdlet.ThrowTerminatingError()` or `Write-Error` | Behavior is understood by internal callers |
480-
| Any command | Never use `throw` | Poor error messages; unpredictable behavior |
481-
| Parameter validation attributes | `throw` | Only valid option within `[ValidateScript()]` |
584+
| Parameter validation in `[ValidateScript()]` | `throw` | Only valid option within validation attributes |
585+
| Any other scenario in commands | Never use `throw` | Poor error messages; unpredictable behavior |
482586
<!-- markdownlint-enable MD013 - Line length -->

0 commit comments

Comments
 (0)