Skip to content

Fix zip extraction on Windows when Group Policy blocks temporary .bat files - #15202

Merged
cderv merged 1 commit into
mainfrom
fix/issue-15198
Oct 27, 2026
Merged

cderv merged 1 commit into
mainfrom
fix/issue-15198

Conversation

@cderv

@cderv cderv commented Oct 27, 2026

Copy link
Copy Markdown
Member

On Windows machines where a Group Policy blocks running .bat files from the temporary folder, every zip extraction fails: quarto add, quarto use template and quarto use brand from GitHub (always downloaded as .zip on Windows), and quarto install tinytex. The debug log shows cmd /c ...\quarto-safe-exec*.bat then ERROR: Failed to unzip template. with the Group Policy block message.

This is the same environment as #14367, but a different call site. The fix for #14367 (#14273) moved dart-sass off the temporary .bat; unzip() in src/core/zip.ts still went through safeWindowsExec, which writes quarto-safe-exec*.bat into %TEMP% and runs it with cmd /c. That code is unchanged since 2022, so no released version works on such machines.

Fix

safeWindowsExec exists for batch files, whose arguments only cmd.exe can parse. powershell.exe and the self-extracting TinyTeX .exe are real executables, so the .bat brought nothing here. Both are now launched directly, and arguments go through as UTF-16, so accented and CJK paths still work without chcp 65001.

The archive and destination paths are embedded in the PowerShell script as single-quoted literals, with every single-quote character doubled, including the curly forms PowerShell also accepts. Before, a path with an apostrophe (O'Brien, D’Arcy) broke the script. The new unit test captures that case, as the Group Policy itself can't be reproduced in a test.

The script sets [Console]::OutputEncoding to UTF-8, which replaces the output side of the removed chcp 65001. Without it, PowerShell's error message shows a non-ASCII path as ? on a console using a code page such as 850.

Windows tar.exe (bsdtar) was considered instead of PowerShell and rejected: it fails to open an archive whose path is outside the ANSI code page, and skips zip entries with UTF-8 non-ASCII names.

Other safeWindowsExec callers (tlmgr, fmtutil, reg.exe lookups, openUrl) still use the temporary .bat and are not changed here.

Test plan

  • tests/unit/zip.test.ts fails before the fix (PowerShell ParserError on the apostrophe) and passes after, extracting non-ASCII entry names into a path with a space, ', ’, &, é and 日本
  • quarto add quarto-ext/fontawesome --log-level debug on Windows shows powershell launched directly, with no cmd /c ...quarto-safe-exec*.bat
  • Error text from a missing archive, run from a code page 850 console: path intact with OutputEncoding, garbled without it
  • TinyTeX .exe extraction is covered by the quarto install tinytex step of the Windows smoke tests job

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR
AI-assisted PR
  • AI tool used: Claude Code
  • Codebase grounding: local clone
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.

Fixes #15198

On Windows, unzip() launched PowerShell (for .zip files) and the
self-extracting TinyTeX .exe through safeWindowsExec, which writes a
quarto-safe-exec*.bat into %TEMP% and runs it with cmd /c. Group
Policies that block running .bat files from %TEMP% make every
extraction fail: quarto add, quarto use template, quarto use brand,
and the TinyTeX, veraPDF and Chrome for Testing installs (#15198).
This has been the case since 2022, so no released version works on
such machines; the dart-sass fix for #14367 did not cover unzip.

Both programs are real executables, so the .bat added nothing here.
safeWindowsExec exists for batch files and cmd quoting; unzip went
through it for #2998 (spaces in the profile path), which was actually
fixed by quoting the paths inside the PowerShell script. PowerShell is
now launched directly with -NoProfile -NonInteractive, and arguments
travel as UTF-16, so accented and CJK paths need no chcp.

Paths are embedded as PowerShell single-quoted literals with every
single-quote character doubled, including the curly forms PowerShell
also accepts. Before, a path containing an apostrophe (O'Brien, or
D’Arcy) broke the script; that is the failing case the new test
captures, since the Group Policy itself cannot be reproduced locally.

Windows System32 tar.exe was rejected: probing showed it fails on
archive paths outside the ANSI code page and on UTF-8 entry names.

The script sets [Console]::OutputEncoding to UTF-8, taking over the
output side of the removed chcp 65001. Measured from a code page 850
console, without it a non-ASCII path in PowerShell's error message
comes back as '?'. Like chcp, it leaves the shared console at 65001.
The unit test cannot detect its removal: the test process's console
is already at 65001 when it runs.
@posit-snyk-bot

posit-snyk-bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cderv
cderv merged commit 55a313c into main Oct 27, 2026
52 checks passed
@cderv
cderv deleted the fix/issue-15198 branch October 27, 2026 11:24
cderv added a commit that referenced this pull request Oct 27, 2026
…eline

#15202 adds four Deno.* calls in tests/unit/zip.test.ts (Deno.mkdirSync x2,
Deno.copyFileSync, Deno.readTextFileSync) to build its unicode zip fixture.
Its PR run reported them as warnings asking for the count to be accepted
before merging, but the baseline was not updated, so main has failed the
Runtime Boundary job since 55a313c.

These calls have no deno_ral equivalent yet (fs/temp helpers arrive with
PR 09 of the runtime migration), so the increase is accepted with
--update-baseline --accept-increase. Only the tests scope changes:
870 -> 874.
cderv added a commit that referenced this pull request Oct 27, 2026
In pull request mode, only a Deno.* call that src/deno_ral replaces
failed the Runtime Boundary check. Any other count increase was a
warning on a green job, so a PR could merge without its baseline update
and turn main red. #15202 did this, and main was fixed by 4bebcb4.
#15134 hit the same failure earlier.

The check now fails when a count is above the PR's own
tools/runtime-boundary-baseline.json. That is the same comparison the
push check makes on main after merge. The contributor has nothing to
change. A maintainer updates the branch with main, runs
--update-baseline --accept-increase, and pushes the baseline to the PR.
A count that fits under the PR's baseline and was not raised by the PR
is reported as a notice and does not fail the run. Covered Deno.* API
errors are unchanged.

Related to #15153, #15183, #15202, and #15134.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unzipping does not work on some windows environment

2 participants