Private/Get-BsnRecordingCommandLine.ps1
|
<#
.SYNOPSIS Builds the Windows process command line used for a recording. .DESCRIPTION When -Command is given, the command is wrapped in pwsh -EncodedCommand (Base64-encoded UTF-16LE) instead of cmd.exe /d /c. This avoids all cmd.exe metacharacter issues (|, &, <, >, ^, quotes) that mangle the recorded command (issue #7): the exact command text is preserved bit-for-bit through the encoding. The wrapper bootstraps the console codepage to UTF-8 before the recorded command runs (issue #5, second root cause): conhost decodes a child's stdout BYTES using the console output codepage, and a fresh pseudo console starts with the OEM codepage (e.g. 437), not UTF-8. TUIs that write UTF-8 bytes directly to the console handle (Go/Bubble Tea applications such as OpenCode, Node raw writes, 'cmd /c type') are then mangled before our recorder sees a single byte - every box-drawing character becomes three CP-decoded characters (e.g. U+2500 'E2 94 80' becomes 'Gamma-ohmega-acute' in CP437). pwsh's own output is immune (it writes through WriteConsoleW), which is why synthetic pwsh probes passed while real byte-writing TUIs produced mojibake. Setting [Console]::OutputEncoding / InputEncoding to UTF-8 is the programmatic equivalent of 'chcp 65001' and fixes the decoding for the whole recorded session, including applications started later inside it. Without -Command, the default interactive shell is returned; when that shell is pwsh or powershell, the same codepage bootstrap runs first, Bsn.Host is auto-imported into the nested session so Stop-InteractiveTranscript is available inside the recording, and the recording-aware prompt is installed when requested. #> function Get-BsnRecordingCommandLine { [CmdletBinding()] [OutputType([string])] param( [string]$Command, [switch]$ShowRecordingIndicator ) # ASCII-only bootstrap (this file stays ASCII per repo policy): UTF-8 # console codepage for the recorded session (issue #5, conhost CP decode). $codePageBootstrap = '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; [Console]::InputEncoding = [System.Text.Encoding]::UTF8;' if ($Command) { # Base64-encode the command to survive the CreateProcess command line # without any shell metacharacter interpretation (issue #7). $shellPath = Get-BsnDefaultShell if ($shellPath -match ' ') { $shellPath = '"{0}"' -f $shellPath } $wrappedCommand = $codePageBootstrap + ' ' + $Command $encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($wrappedCommand)) return ('{0} -NoProfile -EncodedCommand {1}' -f $shellPath, $encoded) } $shellPath = Get-BsnDefaultShell if ($shellPath -match ' ') { $shellPath = '"{0}"' -f $shellPath } if ($shellPath -match '(pwsh|powershell)(\.exe)?("?)$') { $nestedCommand = $codePageBootstrap + ' Import-Module Bsn.Host' if ($ShowRecordingIndicator.IsPresent) { # Recording-aware prompt for the nested session; the glyph is built from # its code point so this file stays ASCII-only. $nestedCommand = $codePageBootstrap + ' Import-Module Bsn.Host; function prompt { ''{0} REC PS {1}> '' -f [char]0x23FA, (Get-Location).Path }' } return ('{0} -NoLogo -NoExit -Command "{1}"' -f $shellPath, $nestedCommand) } # Non-PowerShell default shells cannot run the pwsh bootstrap inline; the # console keeps its OEM codepage (byte-writing TUIs need 'chcp 65001' run # manually inside them on OEM-codepage-decoding conhosts). return $shellPath } |