From ada52cee6ab1ddd33d00a0cd81f22d358f408e33 Mon Sep 17 00:00:00 2001 From: oceans-of-time <34587654+time-by-waves@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:03:20 +1100 Subject: [PATCH 1/3] 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 --- instructions/powershell.instructions.md | 176 ++++++++++++------------ 1 file changed, 89 insertions(+), 87 deletions(-) diff --git a/instructions/powershell.instructions.md b/instructions/powershell.instructions.md index 83be180b0..928258cb9 100644 --- a/instructions/powershell.instructions.md +++ b/instructions/powershell.instructions.md @@ -30,11 +30,11 @@ safe, and maintainable scripts. It aligns with Microsoft’s 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,8 +98,13 @@ function Set-ResourceConfiguration { [ValidateSet('Dev', 'Test', 'Prod')] [string]$Environment = 'Dev', + # ✔️ CORRECT: Use `[switch]` with no default value [Parameter()] - [switch]$Force, + [switch]$FalseByDefault, + + # ❌ WRONG: Shows incorrect default assignment, however this is correct syntax (requires `[switch]` cast). + [Parameter()] + [switch]$Quiet = [switch]$true, [Parameter()] [ValidateNotNullOrEmpty()] @@ -102,7 +112,10 @@ function Set-ResourceConfiguration { ) 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 @@ -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 + 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,76 @@ 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' + $ErrorActionPreference = $currentErrorActionValue + $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 with Force parameter + if ($PSCmdlet.ShouldProcess($Username, "Remove user account")) { + # ShouldContinue for additional confirmation when -Force is not used + 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 } } ``` From 9313106bb1cd36da1cca3b271a7b94fbb334aaad Mon Sep 17 00:00:00 2001 From: oceans-of-time <34587654+time-by-waves@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:03:10 +1100 Subject: [PATCH 2/3] Update instructions/powershell.instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- instructions/powershell.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instructions/powershell.instructions.md b/instructions/powershell.instructions.md index 928258cb9..6028e665b 100644 --- a/instructions/powershell.instructions.md +++ b/instructions/powershell.instructions.md @@ -34,7 +34,7 @@ safe, and maintainable scripts. It aligns with Microsoft’s PowerShell cmdlet d - Document any custom aliases - Use full parameter names -### Example - Naming Conventions +### Example - Naming Conventions ```powershell function Get-UserProfile { From 81d37e09f21d219cb5a7a9088fb53adaa9ee16ba Mon Sep 17 00:00:00 2001 From: oceans-of-time <34587654+time-by-waves@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:04:52 +1100 Subject: [PATCH 3/3] Update instructions/powershell.instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- instructions/powershell.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instructions/powershell.instructions.md b/instructions/powershell.instructions.md index 6028e665b..bf7ec4422 100644 --- a/instructions/powershell.instructions.md +++ b/instructions/powershell.instructions.md @@ -100,7 +100,7 @@ function Set-ResourceConfiguration { # ✔️ CORRECT: Use `[switch]` with no default value [Parameter()] - [switch]$FalseByDefault, + [switch]$Force, # ❌ WRONG: Shows incorrect default assignment, however this is correct syntax (requires `[switch]` cast). [Parameter()]