Windows PowerShell safety
Four ways a PowerShell call on this machine reports something other than the truth,
even though nothing here is exotic — all found by tracing real failed tool calls, not
from general PowerShell knowledge.
| Symptom |
Cause |
Fix |
A cmdlet-only call (Get-ChildItem, Get-Process, a pipeline of both) prints correct output, no error text, but the wrapper reports it failed |
$LASTEXITCODE is set only by native executables, never reset by a cmdlet. An earlier native failure in the same session (a failed wsl.exe, adb, Gradle call — even one already known and not being fixed) leaves its stale value sitting there, misread as this call's result. |
Don't "fix" a command whose own output is correct. Check whether an earlier native command actually failed before trusting the flag. Prefer $? or a caught exception over $LASTEXITCODE for cmdlet-only calls. |
One PowerShell call that writes a heredoc (e.g. a Python script via @'...'@) and later runs Remove-Item in the same call gets blocked, citing a nonsense path like '\n'.join as the thing it refuses to delete |
The built-in Remove-Item destructive-path guard scans the whole command text for quoted fragments and can grab a piece of the heredoc body instead of the real target, when both appear together. |
Never put a heredoc and a Remove-Item call in the same invocation. Split: write in one call, run in a second, clean up in a third. Sequential dependency is the reason to split, not a reason to combine. |
git commit -m "$(cat <<'EOF' ... EOF)" errors, or only its first line becomes the commit message |
<< is bash heredoc syntax, not a PowerShell operator — it doesn't parse. Even PowerShell's own here-string handed to a native exe's single -m arg only survives as line one. |
Use the Bash tool for a multi-line git commit message. If it must be PowerShell, write the message to a temp file with a here-string and pass -F <file>. |
A file PowerShell wrote (Out-File, Set-Content, >) comes out garbled elsewhere — json.loads chokes, or text shows â€" / ðŸ" in place of an em-dash or emoji |
These cmdlets default to UTF-16 (with BOM) or the system codepage, not UTF-8. |
Pass -Encoding utf8 explicitly whenever another tool will read the result. Recover an already-mangled file with encoding="utf-8-sig" on the read side, but fix the write going forward. |
Why these four
Rows 1-2 are false signals — the wrapper's success/failure read is wrong. Rows 3-4 are
bash habits not transferring: syntax that looks equivalent but isn't. Each has already
cost a wasted debugging pass, a scrapped fix, or a mangled file.
1---2name: windows-powershell-safety3description: Use when driving PowerShell on this machine — before trusting a failed exit status, before combining a heredoc with a deletion in one call, or when writing a file another tool will parse.4---56# Windows PowerShell safety78Four ways a PowerShell call on this machine reports something other than the truth,9even though nothing here is exotic — all found by tracing real failed tool calls, not10from general PowerShell knowledge.1112| Symptom | Cause | Fix |13|---|---|---|14| A cmdlet-only call (`Get-ChildItem`, `Get-Process`, a pipeline of both) prints correct output, no error text, but the wrapper reports it **failed** | `$LASTEXITCODE` is set only by native executables, never reset by a cmdlet. An earlier native failure in the same session (a failed `wsl.exe`, `adb`, Gradle call — even one already known and not being fixed) leaves its stale value sitting there, misread as this call's result. | Don't "fix" a command whose own output is correct. Check whether an earlier native command actually failed before trusting the flag. Prefer `$?` or a caught exception over `$LASTEXITCODE` for cmdlet-only calls. |15| One PowerShell call that writes a heredoc (e.g. a Python script via `@'...'@`) and later runs `Remove-Item` in the *same* call gets blocked, citing a nonsense path like `'\n'.join` as the thing it refuses to delete | The built-in `Remove-Item` destructive-path guard scans the whole command text for quoted fragments and can grab a piece of the heredoc *body* instead of the real target, when both appear together. | Never put a heredoc and a `Remove-Item` call in the same invocation. Split: write in one call, run in a second, clean up in a third. Sequential dependency is the *reason* to split, not a reason to combine. |16| `git commit -m "$(cat <<'EOF' ... EOF)"` errors, or only its first line becomes the commit message | `<<` is bash heredoc syntax, not a PowerShell operator — it doesn't parse. Even PowerShell's own here-string handed to a native exe's single `-m` arg only survives as line one. | Use the Bash tool for a multi-line `git commit` message. If it must be PowerShell, write the message to a temp file with a here-string and pass `-F <file>`. |17| A file PowerShell wrote (`Out-File`, `Set-Content`, `>`) comes out garbled elsewhere — `json.loads` chokes, or text shows `â€"` / `ðŸ"` in place of an em-dash or emoji | These cmdlets default to UTF-16 (with BOM) or the system codepage, not UTF-8. | Pass `-Encoding utf8` explicitly whenever another tool will read the result. Recover an already-mangled file with `encoding="utf-8-sig"` on the read side, but fix the write going forward. |1819## Why these four2021Rows 1-2 are false signals — the wrapper's success/failure read is wrong. Rows 3-4 are22bash habits not transferring: syntax that looks equivalent but isn't. Each has already23cost a wasted debugging pass, a scrapped fix, or a mangled file.