CliExeHelpers.ps1
|
# Shared helper for the Copilot CLI cmdlets. # # Resolve-CliExe is the single place that resolves the copilot executable. # Discovery uses Invoke-CopilotDiscovery to check native status before parsing. # Mutating commands own their calls in Plugins.ps1 / Marketplaces.ps1. function Resolve-CliExe { <# .SYNOPSIS Resolve the path to the copilot executable. .PARAMETER Name Which executable to resolve. Currently only 'copilot' is supported. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('copilot')] [string]$Name ) (Get-Command $Name -CommandType Application -ErrorAction Stop | Select-Object -First 1).Source } function Invoke-WithUtf8Console { <# .SYNOPSIS Run a command while decoding native output as UTF-8. .DESCRIPTION Temporarily sets [Console]::OutputEncoding to UTF-8 while running the supplied script block, then restores the previous encoding in a finally block. Hosts that reject OutputEncoding changes are treated as best effort so callers still run normally. .PARAMETER ScriptBlock The script block to invoke while UTF-8 console output decoding is active. #> [CmdletBinding()] param( [Parameter(Mandatory)] [scriptblock]$ScriptBlock ) $previousEncoding = $null $hasPreviousEncoding = $false try { try { $previousEncoding = [Console]::OutputEncoding $hasPreviousEncoding = $true [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch { # Best effort: some hosts do not allow changing console encoding. } & $ScriptBlock } finally { if ($hasPreviousEncoding) { try { [Console]::OutputEncoding = $previousEncoding } catch { # Best effort: do not mask the script block result or error. } } } } function Invoke-CopilotDiscovery { [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$Arguments ) $exe = Resolve-CliExe -Name copilot $previousExitCode = Get-Variable -Name LASTEXITCODE -Scope Global -ErrorAction Ignore $previousExitCodeValue = if ($previousExitCode) { $previousExitCode.Value } try { $global:LASTEXITCODE = $null $result = Invoke-WithUtf8Console { # Report one error with the complete diagnostics, regardless of the native preference. $PSNativeCommandUseErrorActionPreference = $false $ErrorActionPreference = 'Stop' $output = @(& $exe @Arguments 2>&1) $exitCode = $global:LASTEXITCODE [pscustomobject]@{ ExitCode = $exitCode Output = $output } } } finally { if ($previousExitCode) { $global:LASTEXITCODE = $previousExitCodeValue } else { Remove-Variable -Name LASTEXITCODE -Scope Global -WhatIf:$false -Confirm:$false } } if ($result.ExitCode -isnot [int] -or $result.ExitCode -ne 0) { $diagnostics = ($result.Output | ForEach-Object { "$_" }) -join [Environment]::NewLine $status = if ($result.ExitCode -is [int]) { "exit code $($result.ExitCode)" } else { 'an unknown native exit status' } $message = "copilot $($Arguments -join ' ') failed with $status." if ($diagnostics) { $message += [Environment]::NewLine + $diagnostics } Write-Error -Message $message -ErrorId CopilotDiscoveryFailed -Category InvalidOperation -TargetObject $result return } $result.Output } function Test-CopilotShimArgument { param( [AllowEmptyString()] [string]$Value, [string]$Pattern = '^[A-Za-z0-9][A-Za-z0-9._@/#:-]*$' ) return [bool]($Value -and $Value -notmatch '[<>&|%!^`"()]' -and $Value -match $Pattern) } function Assert-CopilotShimArgument { param( [AllowEmptyString()] [string]$Value, [Parameter(Mandatory)] [string]$ParameterName, [string]$Pattern = '^[A-Za-z0-9][A-Za-z0-9._@/#:-]*$' ) if (-not (Test-CopilotShimArgument -Value $Value -Pattern $Pattern)) { throw [System.ArgumentException]::new( "Unsafe $ParameterName value. Values passed to the copilot CLI may only contain allow-listed characters and must not contain cmd.exe metacharacters.", $ParameterName) } } function Assert-CopilotShimTextArgument { param( [AllowEmptyString()] [string]$Value, [Parameter(Mandatory)] [string]$ParameterName ) if (-not $Value -or $Value -match '[<>&|%!^`"()]') { throw [System.ArgumentException]::new( "Unsafe $ParameterName value. Values passed to the copilot CLI must not contain cmd.exe metacharacters.", $ParameterName) } } |