Update powershell.instructions.md (#1125)

* Update powershell.instructions.md

## Description

### Error Handling

- Update to the `powershell.instructions.md` file. Now includes less error handling in the examples. This means when using the instructions file the output script contains less of the structured error handling, however the output scripts are easier for beginners and powershell novices to read and understand.

### Switch parameter

- Updates to using the switch parameters should now prevent default values data type being a bool
- Using no default value is the way in PowerShell. Defaults to a false value and shouldn't be set to a true value. Although a note has been added to show the correct syntax that requires type casting

### Examples updates
- Now includes a better demonstration of using the `WhatIf` parameter via `$PSCmdlet.ShouldProcesss`
- Full Example: End-to-End Cmdlet Pattern updated with the `$Force` & `$PSCmdlet.ShouldContinue` pattern

* Update instructions/powershell.instructions.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update instructions/powershell.instructions.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update instructions/powershell.instructions.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update ShouldProcess and ShouldContinue guidance

Clarified ShouldProcess and ShouldContinue usage in PowerShell instructions.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
oceans-of-time
2026-04-09 09:55:07 +10:00
committed by GitHub
parent 52e14abc86
commit df81ca0319

View File

@@ -30,11 +30,11 @@ safe, and maintainable scripts. It aligns with Microsofts PowerShell cmdlet d
- **Alias Avoidance:** - **Alias Avoidance:**
- Use full cmdlet names - Use full cmdlet names
- Avoid using aliases in scripts (e.g., use Get-ChildItem instead of gci) - Avoid using aliases in scripts (e.g., use `Get-ChildItem` instead of `gci`)
- Document any custom aliases - Document any custom aliases
- Use full parameter names - Use full parameter names
### Example ### Example - Naming Conventions
```powershell ```powershell
function Get-UserProfile { function Get-UserProfile {
@@ -49,6 +49,9 @@ function Get-UserProfile {
) )
process { process {
$outputString = "Searching for: '$($Username)'"
Write-Verbose -Message $outputString
Write-Verbose -Message "Profile type: $ProfileType"
# Logic here # Logic here
} }
} }
@@ -75,12 +78,14 @@ function Get-UserProfile {
- Enable tab completion where possible - Enable tab completion where possible
- **Switch Parameters:** - **Switch Parameters:**
- Use [switch] for boolean flags - **ALWAYS** use `[switch]` for boolean flags, never `[bool]`
- Avoid $true/$false parameters - **NEVER** use `[bool]$Parameter` or assign default values
- Default to $false when omitted - Switch parameters default to `$false` when omitted
- Use clear action names - Use clear, action-oriented names
- Test presence with `.IsPresent`
- Using `$true`/`$false` in parameter attributes (e.g., `Mandatory = $true`) is acceptable
### Example ### Example - Parameter Design
```powershell ```powershell
function Set-ResourceConfiguration { function Set-ResourceConfiguration {
@@ -93,16 +98,24 @@ function Set-ResourceConfiguration {
[ValidateSet('Dev', 'Test', 'Prod')] [ValidateSet('Dev', 'Test', 'Prod')]
[string]$Environment = 'Dev', [string]$Environment = 'Dev',
# ✔️ CORRECT: Use `[switch]` with no default value
[Parameter()] [Parameter()]
[switch]$Force, [switch]$Force,
# ❌ WRONG: Shows incorrect default assignment, however this is correct syntax (requires `[switch]` cast).
[Parameter()]
[switch]$Quiet = [switch]$true,
[Parameter()] [Parameter()]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string[]]$Tags [string[]]$Tags
) )
process { process {
# Logic here # Use .IsPresent to check switch state
if ($Quiet.IsPresent) {
Write-Verbose "Quiet mode enabled"
}
} }
} }
``` ```
@@ -133,7 +146,7 @@ function Set-ResourceConfiguration {
- Return modified/created object with `-PassThru` - Return modified/created object with `-PassThru`
- Use verbose/warning for status updates - Use verbose/warning for status updates
### Example ### Example - Pipeline and Output
```powershell ```powershell
function Update-ResourceStatus { function Update-ResourceStatus {
@@ -163,7 +176,7 @@ function Update-ResourceStatus {
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
@@ -183,8 +196,8 @@ function Update-ResourceStatus {
- **ShouldProcess Implementation:** - **ShouldProcess Implementation:**
- Use `[CmdletBinding(SupportsShouldProcess = $true)]` - Use `[CmdletBinding(SupportsShouldProcess = $true)]`
- Set appropriate `ConfirmImpact` level - Set appropriate `ConfirmImpact` level
- Call `$PSCmdlet.ShouldProcess()` for system changes - Call `$PSCmdlet.ShouldProcess()` as close the the changes action
- Use `ShouldContinue()` for additional confirmations - Use `$PSCmdlet.ShouldContinue()` for additional confirmations
- **Message Streams:** - **Message Streams:**
- `Write-Verbose` for operational details with `-Verbose` - `Write-Verbose` for operational details with `-Verbose`
@@ -209,69 +222,32 @@ function Update-ResourceStatus {
- Support automation scenarios - Support automation scenarios
- Document all required inputs - Document all required inputs
### Example ### Example - Error Handling and Safety
```powershell ```powershell
function Remove-UserAccount { function Remove-CacheFiles {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param( param(
[Parameter(Mandatory, ValueFromPipeline)] [Parameter(Mandatory)]
[ValidateNotNullOrEmpty()] [string]$Path
[string]$Username,
[Parameter()]
[switch]$Force
) )
begin { try {
Write-Verbose 'Starting user account removal process' $files = Get-ChildItem -Path $Path -Filter "*.cache" -ErrorAction Stop
$ErrorActionPreference = 'Stop'
} # Demonstrates WhatIf support
if ($PSCmdlet.ShouldProcess($Path, 'Remove cache files')) {
process { $files | Remove-Item -Force -ErrorAction Stop
try { Write-Verbose "Removed $($files.Count) cache files from $Path"
# Validation
if (-not (Test-UserExists -Username $Username)) {
$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
}
# Confirmation
$shouldProcessMessage = "Remove user account '$Username'"
if ($Force -or $PSCmdlet.ShouldProcess($Username, $shouldProcessMessage)) {
Write-Verbose "Removing user account: $Username"
# Main operation
Remove-ADUser -Identity $Username -ErrorAction Stop
Write-Warning "User account '$Username' has been removed"
}
} catch [Microsoft.ActiveDirectory.Management.ADException] {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
'ActiveDirectoryError',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Username
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
} catch {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
'UnexpectedError',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Username
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
} }
} } catch {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
end { $_.Exception,
Write-Verbose 'User account removal process completed' 'RemovalFailed',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Path
)
$PSCmdlet.WriteError($errorRecord)
} }
} }
``` ```
@@ -307,50 +283,77 @@ function Remove-UserAccount {
- Use `ForEach-Object` instead of `%` - Use `ForEach-Object` instead of `%`
- Use `Get-ChildItem` instead of `ls` or `dir` - Use `Get-ChildItem` instead of `ls` or `dir`
---
## Full Example: End-to-End Cmdlet Pattern ## Full Example: End-to-End Cmdlet Pattern
```powershell ```powershell
function New-Resource { function Remove-UserAccount {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory, ValueFromPipeline)]
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string]$Name, [string]$Username,
[Parameter()] [Parameter()]
[ValidateSet('Development', 'Production')] [switch]$Force
[string]$Environment = 'Development'
) )
begin { begin {
Write-Verbose 'Starting resource creation process' Write-Verbose 'Starting user account removal process'
$currentErrorActionValue = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
} }
process { process {
try { try {
if ($PSCmdlet.ShouldProcess($Name, 'Create new resource')) { # Validation
# Resource creation logic here if (-not (Test-UserExists -Username $Username)) {
Write-Output ([PSCustomObject]@{ $errorRecord = [System.Management.Automation.ErrorRecord]::new(
Name = $Name [System.Exception]::new("User account '$Username' not found"),
Environment = $Environment 'UserNotFound',
Created = Get-Date [System.Management.Automation.ErrorCategory]::ObjectNotFound,
}) $Username
)
$PSCmdlet.WriteError($errorRecord)
return
} }
# ShouldProcess enables -WhatIf and -Confirm support
if ($PSCmdlet.ShouldProcess($Username, "Remove user account")) {
# ShouldContinue provides an additional confirmation prompt for high-impact operations
# This prompt is bypassed when -Force is specified
if ($Force -or $PSCmdlet.ShouldContinue("Are you sure you want to remove '$Username'?", "Confirm Removal")) {
Write-Verbose "Removing user account: $Username"
# Main operation
Remove-ADUser -Identity $Username -ErrorAction Stop
Write-Warning "User account '$Username' has been removed"
}
}
} catch [Microsoft.ActiveDirectory.Management.ADException] {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
'ActiveDirectoryError',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Username
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
} catch { } catch {
$errorRecord = [System.Management.Automation.ErrorRecord]::new( $errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception, $_.Exception,
'ResourceCreationFailed', 'UnexpectedError',
[System.Management.Automation.ErrorCategory]::NotSpecified, [System.Management.Automation.ErrorCategory]::NotSpecified,
$Name $Username
) )
$PSCmdlet.ThrowTerminatingError($errorRecord) $PSCmdlet.ThrowTerminatingError($errorRecord)
} }
} }
end { end {
Write-Verbose 'Completed resource creation process' Write-Verbose 'User account removal process completed'
# Set ErrorActionPreference back to the value it had
$ErrorActionPreference = $currentErrorActionValue
} }
} }
``` ```