Open source / Cross-platform tooling

Correct PowerShell quoting for terminal environment values

A shell-specific fix for VS Code’s runInTerminal request path: escape PowerShell quote delimiters without changing literal backslashes or the behavior of other shells.

Project
Visual Studio Code
Contribution
PR #331753
Related report
Issue #331751
Role
Issue reporter and PR author
Outcome
Merged 20 August 2026

Command construction has to follow the target shell’s grammar.

Visual Studio Code’s terminal integration can receive environment variables through a runInTerminal request and construct shell commands that assign those values before launching the requested process. In PowerShell, environment assignments use quoted string literals, so an embedded quote delimiter must be represented according to PowerShell syntax.

Issue #331751 showed that values containing single quotes were interpolated into a single-quoted assignment without the required escaping. Zain Nadeem reported the behavior and authored PR #331753, which was merged with focused regression coverage.

A valid environment value could become invalid PowerShell syntax.

A value such as it's ready contains the same character PowerShell uses to delimit a single-quoted string. Placing that value directly between single quotes ends the literal early. The remaining characters are then interpreted as syntax instead of part of the environment value.

Intended valueit's ready
PowerShell literal form'it''s ready'

The challenge was narrower than introducing a universal shell escaping function. The affected code already branches by shell type, and the environment-value position has different semantics from an ordinary command-line argument.

Existing quoting logic was close—but not semantically identical.

An obvious first direction was to reuse VS Code’s general argument-quoting helper. Reviewing that helper revealed an important difference: it deliberately adjusts trailing backslashes for argument contexts where an ending quote can interact with escape processing. A PowerShell single-quoted environment value does not need that transformation.

Reusing the helper wholesale would have corrected embedded quotes while risking an unrelated change to literal backslashes. The investigation therefore separated two responsibilities:

  • preserve the exact environment-variable value, including trailing backslashes;
  • escape only characters that can terminate or act as PowerShell single-quote delimiters.

Raw interpolation crossed a shell-language boundary.

The PowerShell branch constructed an environment assignment with a quoted value, but the value was inserted without first encoding the quote characters for that literal context. Data that was valid as an environment string was therefore treated as if it were already valid PowerShell source text.

Environment valueRaw interpolationSingle-quoted PowerShell literalBroken assignment

This boundary is easy to miss because the generated assignment looks structurally correct for ordinary strings. The defect appears only when the data contains a character that also participates in PowerShell’s literal grammar, which makes boundary-oriented input selection essential.

The core lesson is contextual: escaping is not a property of a string by itself. It depends on where the string will be parsed.

Encode quote delimiters while leaving every other byte alone.

PR #331753 introduced PowerShell-specific environment-value handling. ASCII single quotes are doubled, which is PowerShell’s representation for a literal single quote inside a single-quoted string. The patch also covers Unicode left and right single quotation marks handled by the command-building path.

// Simplified intent
const escapedValue = value.replace(powerShellQuoteCharacters, quote => quote + quote);
const assignment = `$env:${name}='${escapedValue}'`;

The fix intentionally preserves backslashes exactly. It is scoped to the PowerShell environment-assignment path; other shells and unrelated terminal command behavior are unchanged.

Correctness includes both escaping and non-transformation.

The merged tests exercise ordinary values, spaces, empty strings, a single ASCII quote, multiple ASCII quotes, Unicode left and right quote characters, a trailing backslash, and a combined quote-plus-backslash case.

CaseProperty protected
Simple / spaces / emptyExisting assignment behavior remains valid
One and multiple ASCII quotesEvery delimiter is doubled
Unicode quotation marksAlternative quote forms are handled consistently
Trailing backslashLiteral backslash remains unchanged
Quote plus backslashEscaping does not create collateral transformation

That final pair of cases is especially valuable: it verifies not only that the original input is fixed, but that the narrower solution avoids the behavior identified during review of the existing helper.

The reported edge case became a focused cross-platform regression.

Zain opened issue #331751 and authored the corresponding patch and tests in PR #331753. The pull request was merged on 20 August 2026 as commit fcaba3720e8f024d70b8e3c482f6aee93bd84c23.

The final implementation differs from the issue’s initial helper-reuse suggestion because the patch investigation established that environment values and command arguments require different treatment for trailing backslashes. The published record is therefore useful not only for the one-line symptom, but for the reasoning that narrowed the correct abstraction.

Reuse syntax helpers only when the parsing context matches.

  • Classify the destination grammar before selecting an escaping routine; shell name alone is not enough.
  • Test characters that are structurally meaningful to the target grammar and characters that a candidate helper might alter unnecessarily.
  • Keep platform-specific command construction narrow so fixes do not change other shells.
  • When investigation disproves an initial implementation idea, preserve the behavioral goal and change the mechanism.

Authoritative upstream record.