Environment
- Azure DevOps Classic UI
- Task: PowerShell@2 (Windows PowerShell, not pwsh)
- Agent: Windows (Microsoft-hosted and self-hosted both repro)
- PowerShell version: Windows PowerShell 5.1
- Script encoding: UTF-8, CRLF line endings
- Task setting “Run script in separate scope”: tested both true and false
- The same logic also exists split across multiple inline PowerShell tasks and runs fine
Symptom When I run a single “File Path” PowerShell script (large-ish, ~600 lines), the task fails immediately with:
Missing closing '}' in statement block or type definition.
The Try statement is missing its Catch or Finally block.
CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : MissingEndCurlyBrace
In the pipeline logs it points to line 1 where the script starts with a top-level try {, and later to a closing }:
At D:\AzureAgentFolder\_work\r1\a\MyApp\install_myapp_script.ps1:1 char:5
+ try {
+ ~
Missing closing '}' in statement block or type definition.
At D:\AzureAgentFolder\_work\r1\a\MyApp\install_myapp_script.ps1:577 char:2
+ }
+ ~
The Try statement is missing its Catch or Finally block.
What’s puzzling
- The exact same code, when split into multiple inline PowerShell tasks, runs fine.
- Locally, the single unified script parses and executes under Windows PowerShell 5.1 without errors.
- VS Code (PowerShell extension) shows no parser errors.
- I’ve manually checked bracket balance and I’m not missing a }.
- File is saved with UTF-8 and CRLF. I also tried re-saving as “UTF‑8 with BOM” to satisfy PS 5.1’s encoding quirks.
What the script looks like (high-level)
- Everything is wrapped in a single top-level try { … } catch { … } to fail fast and emit nice logs.
- It does some pre-install cleanup, downloads artifacts, installs software, copies an .ssh tree, sets ACLs, and finally does some ssh-keyscan/ssh checks.
- There are several strings with embedded quotes for cmd.exe/icacls calls (e.g. "icacls
"$folder" …"). - I’ve already fixed ambiguous interpolations like "$sshKeyPath:" to "`${sshKeyPath}:" so the parser doesn’t think the colon is part of the variable name.
What I’ve tried (and observations)
- Verified in VS Code that the file has CRLF line endings and is UTF‑8. Also re-saved explicitly as UTF‑8 with BOM.
- Ran the script locally with Windows PowerShell 5.1 (not PowerShell 7): no parser errors, it runs.
- Grepped for trailing backticks (common cause of accidental line continuation) and removed any accidental ones.
- Ensured there are no unbalanced quotes in strings (heuristic: checked lines with odd numbers of double quotes).
- Ensured there aren’t any tasks that transform/replace tokens in the .ps1 file during the pipeline (to the best of my knowledge).
- Confirmed the PowerShell task is calling the file directly (File Path), not dot-sourcing it (also tried “Run script in separate scope” = true to be safe).
Diagnostic steps I added in the pipeline (to run on the agent against the exact file path the task uses)
- Parse the file before running it to get exact parser errors and context:
param([string]$Path)
[System.Management.Automation.Language.Token[]]$t=$null
[System.Management.Automation.Language.ParseError[]]$e=$null
$null=[System.Management.Automation.Language.Parser]::ParseFile($Path,[ref]$t,[ref]$e)
if($e){
$c=Get-Content $Path
foreach($err in $e){
$ln=$err.Extent.StartLineNumber;$col=$err.Extent.StartColumnNumber
"Error: $($err.Message) at line $ln, col $col"
$start=[Math]::Max(1,$ln-3);$end=[Math]::Min($c.Count,$ln+3)
for($i=$start;$i -le $end;$i++){($i -eq $ln?">>":" ")+("{0,5}: {1}" -f $i,$c[$i-1])}
""
}
exit 1
}else{"Parser reports no errors for $Path."}
- Scan for trailing backticks and lines with odd quote counts (heuristic):
param([string]$Path)
"Lines ending with backtick:"
Select-String -Path $Path -Pattern '`\s*$' | ForEach-Object { "$($_.LineNumber): $($_.Line)" }
"Lines with odd double-quote counts:"
$i=0; Get-Content $Path | % { $i++; $q=($_ -split '"').Count-1; if($q%2 -ne 0){"$i: $_"} }
- Verified bytes aren’t changing between my repo and the agent by logging a SHA256 of the file in the pipeline:
(Get-FileHash '$(System.DefaultWorkingDirectory)\MyApp\install_myapp_script.ps1' -Algorithm SHA256).Hash
What I suspect
- Either the file on the agent is being subtly altered before execution (encoding/line endings/token replacement), or there is an accidental line continuation / unclosed string that the agent environment exposes but local testing doesn’t (e.g., due to CR/LF normalization).
- PowerShell 5.1 treats UTF‑8 without BOM as ANSI, but the failure persists.
Questions
- Is there a known issue with Azure DevOps “PowerShell@2” File Path task + Windows PowerShell 5.1 causing “MissingEndCurlyBrace” when the same script parses locally and as inline steps?
- What else should I check to find what the agent’s parser is actually choking on, given that the file appears correct locally?
- Any best practices to harden large File Path scripts for ADO?
- Example: always save as UTF‑8 with BOM + CRLF, avoid trailing backticks, use ${var} or $() when interpolating punctuation, add preflight parse step, set “Run script in separate scope” = true, disable token-replacement steps on .ps1 files, etc.
Any guidance or pointers to known pitfalls would be greatly appreciated. Happy to post the parser preflight output (exact failing line + 6-line context) from the agent if that helps.
Thank you!
if() { ... }) until it starts running without an error - that'll help track down where it's "losing" a brace and you can focus on that bit of the code.