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:**
- 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
- Use full parameter names
### Example
### Example - Naming Conventions
```powershell
function Get-UserProfile {
@@ -49,6 +49,9 @@ function Get-UserProfile {
)
process {
$outputString = "Searching for: '$($Username)'"
Write-Verbose -Message $outputString
Write-Verbose -Message "Profile type: $ProfileType"
# Logic here
}
}
@@ -75,12 +78,14 @@ function Get-UserProfile {
- Enable tab completion where possible
- **Switch Parameters:**
- Use [switch] for boolean flags
- Avoid $true/$false parameters
- Default to $false when omitted
- Use clear action names
- **ALWAYS** use `[switch]` for boolean flags, never `[bool]`
- **NEVER** use `[bool]$Parameter` or assign default values
- Switch parameters default to `$false` when omitted
- 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
function Set-ResourceConfiguration {
@@ -93,16 +98,24 @@ function Set-ResourceConfiguration {
[ValidateSet('Dev', 'Test', 'Prod')]
[string]$Environment = 'Dev',
# ✔️ CORRECT: Use `[switch]` with no default value
[Parameter()]
[switch]$Force,
# ❌ WRONG: Shows incorrect default assignment, however this is correct syntax (requires `[switch]` cast).
[Parameter()]
[switch]$Quiet = [switch]$true,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string[]]$Tags
)
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`
- Use verbose/warning for status updates
### Example
### Example - Pipeline and Output
```powershell
function Update-ResourceStatus {
@@ -163,7 +176,7 @@ function Update-ResourceStatus {
Name = $Name
Status = $Status
LastUpdated = $timestamp
UpdatedBy = $env:USERNAME
UpdatedBy = "$($env:USERNAME)"
}
# Only output if PassThru is specified
@@ -183,8 +196,8 @@ function Update-ResourceStatus {
- **ShouldProcess Implementation:**
- Use `[CmdletBinding(SupportsShouldProcess = $true)]`
- Set appropriate `ConfirmImpact` level
- Call `$PSCmdlet.ShouldProcess()` for system changes
- Use `ShouldContinue()` for additional confirmations
- Call `$PSCmdlet.ShouldProcess()` as close the the changes action
- Use `$PSCmdlet.ShouldContinue()` for additional confirmations
- **Message Streams:**
- `Write-Verbose` for operational details with `-Verbose`
@@ -209,69 +222,32 @@ function Update-ResourceStatus {
- Support automation scenarios
- Document all required inputs
### Example
### Example - Error Handling and Safety
```powershell
function Remove-UserAccount {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
function Remove-CacheFiles {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$Username,
[Parameter()]
[switch]$Force
[Parameter(Mandatory)]
[string]$Path
)
begin {
Write-Verbose 'Starting user account removal process'
$ErrorActionPreference = 'Stop'
}
process {
try {
# 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)
try {
$files = Get-ChildItem -Path $Path -Filter "*.cache" -ErrorAction Stop
# Demonstrates WhatIf support
if ($PSCmdlet.ShouldProcess($Path, 'Remove cache files')) {
$files | Remove-Item -Force -ErrorAction Stop
Write-Verbose "Removed $($files.Count) cache files from $Path"
}
}
end {
Write-Verbose 'User account removal process completed'
} catch {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
'RemovalFailed',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Path
)
$PSCmdlet.WriteError($errorRecord)
}
}
```
@@ -307,50 +283,77 @@ function Remove-UserAccount {
- Use `ForEach-Object` instead of `%`
- Use `Get-ChildItem` instead of `ls` or `dir`
---
## Full Example: End-to-End Cmdlet Pattern
```powershell
function New-Resource {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
function Remove-UserAccount {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[string]$Username,
[Parameter()]
[ValidateSet('Development', 'Production')]
[string]$Environment = 'Development'
[switch]$Force
)
begin {
Write-Verbose 'Starting resource creation process'
Write-Verbose 'Starting user account removal process'
$currentErrorActionValue = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
}
process {
try {
if ($PSCmdlet.ShouldProcess($Name, 'Create new resource')) {
# Resource creation logic here
Write-Output ([PSCustomObject]@{
Name = $Name
Environment = $Environment
Created = Get-Date
})
# 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
}
# 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 {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
'ResourceCreationFailed',
'UnexpectedError',
[System.Management.Automation.ErrorCategory]::NotSpecified,
$Name
$Username
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
}
end {
Write-Verbose 'Completed resource creation process'
Write-Verbose 'User account removal process completed'
# Set ErrorActionPreference back to the value it had
$ErrorActionPreference = $currentErrorActionValue
}
}
```