fix: improve error handling in PowerShell guidelines (#280)

Update error handling examples to use $PSCmdlet.WriteError() and
$PSCmdlet.ThrowTerminatingError() instead of Write-Error and throw for better PowerShell cmdlet integration.
This commit is contained in:
oceans-of-time 2025-10-08 11:18:27 +11:00 committed by GitHub
parent c95af033c9
commit 897d61b03f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,11 +1,12 @@
--- ---
applyTo: '**/*.ps1,**/*.psm1' applyTo: '**/*.ps1,**/*.psm1'
description: 'PowerShell cmdlet and scripting best practices based on Microsoft guidelines' description: 'PowerShell cmdlet and scripting best practices based on Microsoft guidelines'
--- ---
# PowerShell Cmdlet Development Guidelines # PowerShell Cmdlet Development Guidelines
This guide provides PowerShell-specific instructions to help GitHub Copilot generate idiomatic, safe, and maintainable scripts. It aligns with Microsofts PowerShell cmdlet development guidelines. This guide provides PowerShell-specific instructions to help GitHub Copilot generate idiomatic,
safe, and maintainable scripts. It aligns with Microsofts PowerShell cmdlet development guidelines.
## Naming Conventions ## Naming Conventions
@ -87,19 +88,19 @@ function Set-ResourceConfiguration {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[Parameter()] [Parameter()]
[ValidateSet('Dev', 'Test', 'Prod')] [ValidateSet('Dev', 'Test', 'Prod')]
[string]$Environment = 'Dev', [string]$Environment = 'Dev',
[Parameter()] [Parameter()]
[switch]$Force, [switch]$Force,
[Parameter()] [Parameter()]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string[]]$Tags [string[]]$Tags
) )
process { process {
# Logic here # Logic here
} }
@ -150,32 +151,32 @@ function Update-ResourceStatus {
) )
begin { begin {
Write-Verbose "Starting resource status update process" Write-Verbose 'Starting resource status update process'
$timestamp = Get-Date $timestamp = Get-Date
} }
process { process {
# Process each resource individually # Process each resource individually
Write-Verbose "Processing resource: $Name" Write-Verbose "Processing resource: $Name"
$resource = [PSCustomObject]@{ $resource = [PSCustomObject]@{
Name = $Name Name = $Name
Status = $Status Status = $Status
LastUpdated = $timestamp LastUpdated = $timestamp
UpdatedBy = $env:USERNAME UpdatedBy = $env:USERNAME
} }
# Only output if PassThru is specified # Only output if PassThru is specified
if ($PassThru) { if ($PassThru.IsPresent) {
Write-Output $resource Write-Output $resource
} }
} }
end { end {
Write-Verbose "Resource status update process completed" Write-Verbose 'Resource status update process completed'
} }
} }
``` ```
## Error Handling and Safety ## Error Handling and Safety
@ -198,6 +199,9 @@ function Update-ResourceStatus {
- Return meaningful error messages - Return meaningful error messages
- Use ErrorVariable when needed - Use ErrorVariable when needed
- Include proper terminating vs non-terminating error handling - Include proper terminating vs non-terminating error handling
- In advanced functions with `[CmdletBinding()]`, prefer `$PSCmdlet.WriteError()` over `Write-Error`
- In advanced functions with `[CmdletBinding()]`, prefer `$PSCmdlet.ThrowTerminatingError()` over `throw`
- Construct proper ErrorRecord objects with category, target, and exception details
- **Non-Interactive Design:** - **Non-Interactive Design:**
- Accept input via parameters - Accept input via parameters
@ -220,7 +224,7 @@ function Remove-UserAccount {
) )
begin { begin {
Write-Verbose "Starting user account removal process" Write-Verbose 'Starting user account removal process'
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
} }
@ -228,7 +232,13 @@ function Remove-UserAccount {
try { try {
# Validation # Validation
if (-not (Test-UserExists -Username $Username)) { if (-not (Test-UserExists -Username $Username)) {
Write-Error "User account '$Username' not found" $errorRecord = [System.Management.Automation.ErrorRecord]::new(
[System.Exception]::new("User account '$Username' not found"),
'UserNotFound',
[System.Management.Automation.ErrorCategory]::ObjectNotFound,
$Username
)
$PSCmdlet.WriteError($errorRecord)
return return
} }
@ -236,24 +246,32 @@ function Remove-UserAccount {
$shouldProcessMessage = "Remove user account '$Username'" $shouldProcessMessage = "Remove user account '$Username'"
if ($Force -or $PSCmdlet.ShouldProcess($Username, $shouldProcessMessage)) { if ($Force -or $PSCmdlet.ShouldProcess($Username, $shouldProcessMessage)) {
Write-Verbose "Removing user account: $Username" Write-Verbose "Removing user account: $Username"
# Main operation # Main operation
Remove-ADUser -Identity $Username -ErrorAction Stop Remove-ADUser -Identity $Username -ErrorAction Stop
Write-Warning "User account '$Username' has been removed" Write-Warning "User account '$Username' has been removed"
} }
} } catch [Microsoft.ActiveDirectory.Management.ADException] {
catch [Microsoft.ActiveDirectory.Management.ADException] { $errorRecord = [System.Management.Automation.ErrorRecord]::new(
Write-Error "Active Directory error: $_" $_.Exception,
throw 'ActiveDirectoryError',
} [System.Management.Automation.ErrorCategory]::NotSpecified,
catch { $Username
Write-Error "Unexpected error removing user account: $_" )
throw $PSCmdlet.ThrowTerminatingError($errorRecord)
} catch {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
'UnexpectedError',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Username
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
} }
} }
end { end {
Write-Verbose "User account removal process completed" Write-Verbose 'User account removal process completed'
} }
} }
``` ```
@ -296,8 +314,8 @@ function New-Resource {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true, ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)] ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string]$Name, [string]$Name,
@ -305,29 +323,34 @@ function New-Resource {
[ValidateSet('Development', 'Production')] [ValidateSet('Development', 'Production')]
[string]$Environment = 'Development' [string]$Environment = 'Development'
) )
begin { begin {
Write-Verbose "Starting resource creation process" Write-Verbose 'Starting resource creation process'
} }
process { process {
try { try {
if ($PSCmdlet.ShouldProcess($Name, "Create new resource")) { if ($PSCmdlet.ShouldProcess($Name, 'Create new resource')) {
# Resource creation logic here # Resource creation logic here
Write-Output ([PSCustomObject]@{ Write-Output ([PSCustomObject]@{
Name = $Name Name = $Name
Environment = $Environment Environment = $Environment
Created = Get-Date Created = Get-Date
}) })
} }
} } catch {
catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new(
Write-Error "Failed to create resource: $_" $_.Exception,
'ResourceCreationFailed',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Name
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
} }
} }
end { end {
Write-Verbose "Completed resource creation process" Write-Verbose 'Completed resource creation process'
} }
} }
``` ```