Add powerShell(script): run PowerShell without quoting or escaping - #160
Conversation
) The script travels base64-encoded as powershell.exe -NoProfile -NonInteractive -EncodedCommand, so quotes, pipes, newlines and $variables pass through verbatim. Returns the regular CommandRequest: every option and both terminals apply unchanged. Scripts whose encoded invocation exceeds cmd.exe's 8191-character line limit are rejected with a pointer at command("powershell -File ...").upload(...). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b99c3ecef5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| final String commandLine = POWERSHELL_PREFIX | ||
| + Base64.getEncoder().encodeToString(script.getBytes(StandardCharsets.UTF_16LE)); |
There was a problem hiding this comment.
Preserve upload path rewriting for PowerShell scripts
When a caller uses client.powerShell("Get-Content C:\\local\\x").upload(path), this encodes the local path before CommandRequest.prepare() invokes ShellFileCopy.copyLocalFilesToRemote(). That helper can only replace plaintext paths in the command line, so it transfers the file but sends a payload that still decodes to the client-side path, causing the remote script to fail to find it. Encode only after upload rewriting, or explicitly reject .upload() for these requests, rather than returning an ordinary CommandRequest whose documented option silently misbehaves.
Useful? React with 👍 / 👎.
| // PowerShell — delivered encoded (-EncodedCommand): no quoting or escaping needed | ||
| CommandResult ps = client.powerShell("Get-Service | Where-Object Status -eq 'Running'").execute(); |
There was a problem hiding this comment.
Keep the PowerShell option out of the terse README
This adds a new API option to the quick-start even though the existing quick-start and examples remain valid. The repository rules reserve README changes for updates that invalidate or alter existing material and explicitly prohibit cataloguing minor features there; the full PowerShell documentation already belongs in the site documentation added by this commit, so remove these README lines.
AGENTS.md reference: AGENTS.md:L25-L29
Useful? React with 👍 / 👎.
Codex review: powerShell(script).upload(file) encoded the script before ShellFileCopy rewrote the local paths, so the transferred file was never referenced by the running script. The raw script now rides the CommandRequest and is encoded in prepare(), after the rewriting; the over-long-script check still fails eagerly at the powerShell() call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codex feedback addressed in e4e9e16:
@codex please review again |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4e9e16f6c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| actualCommand = String.format("CMD.EXE /C (%s)", updatedCommand); | ||
| // A PowerShell script is encoded only now, AFTER the rewriting: the local paths it | ||
| // references must be replaced while they are still plain text. | ||
| actualCommand = String.format("CMD.EXE /C (%s)", powerShell ? encodePowerShell(updatedCommand) : updatedCommand); |
There was a problem hiding this comment.
Include the upload wrapper in the PowerShell length check
When .upload() is used and the encoded invocation is 8179–8191 characters long, encodePowerShell(updatedCommand) accepts it, but this line adds the 13-character CMD.EXE /C (...) wrapper afterward. The resulting command exceeds the documented 8191-character cmd.exe limit and fails remotely instead of being rejected with the intended IllegalArgumentException; account for the wrapper when validating uploaded PowerShell requests.
Useful? React with 👍 / 👎.
…157) Codex review: an encoded invocation of 8179-8191 characters passed the eager check but exceeded cmd.exe's 8191-character limit once upload() added the 13-character CMD.EXE /C ( ) wrapper. The check now always reserves that room, so a script accepted by powerShell() stays valid whether or not upload() is called afterward. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
P2 addressed: the length check in @codex please review again |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Instead of rejecting a script whose -EncodedCommand invocation exceeds cmd.exe's 8191-character line limit, powerShell() now handles it itself: the script is written to a local temporary .ps1 file (UTF-8 with a BOM, so -File decodes non-ASCII correctly), transferred through the WinRM channel exactly like an upload(), and run with powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File. The remote copy is content-addressed, so re-running an identical script skips the transfer. The caller never sees the command-line length limit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
New in this PR: Wire-level tests cover both sides of the threshold: a 3040-character script still rides the command line encoded, a longer one runs from the content-addressed remote file. @codex please review again |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 623ac520de
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (encoded != null) { | ||
| actualCommand = encoded; | ||
| } else { | ||
| actualCommand = powerShellFromFile(actualCommand, timeoutMillis, start); |
There was a problem hiding this comment.
Preserve PowerShell invocation semantics across the fallback
When an oversized script inspects $PSScriptRoot or $MyInvocation.MyCommand.Path, switching it to -File changes observable behavior: the encoded form has no backing script path, while the fallback exposes the content-addressed remote .ps1 path. Consequently, merely pushing an otherwise identical script over the length threshold can change path resolution or control flow, despite the fallback being presented as transparent; execute the transferred contents in command/script-block mode or explicitly account for this semantic difference.
Useful? React with 👍 / 👎.
Codex review: -File gives the script a backing file path, so $PSScriptRoot and $MyInvocation.MyCommand.Path suddenly resolve when a script crosses the length threshold — the fallback was not observably identical to the encoded form. The invocation is now powershell.exe -Command "& ([ScriptBlock]::Create((Get-Content -Raw -LiteralPath '<remote>')))": the script stays pathless in both forms, top-level param(...) blocks keep working, and the execution policy (which only governs script files) never applies, so -ExecutionPolicy Bypass is gone too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
P2 addressed: the fallback no longer uses @codex please review again |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a20c0202f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * policy — which only governs script files — never applies. | ||
| */ | ||
| private static final String POWERSHELL_FILE_INVOCATION = "powershell.exe -NoProfile -NonInteractive -Command " + | ||
| "\"& ([ScriptBlock]::Create((Get-Content -Raw -LiteralPath '%s')))\""; |
There was a problem hiding this comment.
Preserve invocation metadata in the long-script fallback
For long scripts that inspect $MyInvocation.InvocationName or $MyInvocation.Line, the new & wrapper is itself observable: the transferred script block reports that it was invoked with the call operator and exposes the wrapper line, whereas the top-level -EncodedCommand form does not. A script can therefore still change output or control flow solely by crossing the length threshold; this is fresh evidence introduced by the replacement invocation, so either use a fallback with matching invocation semantics or document the remaining difference instead of describing the forms as observably identical.
Useful? React with 👍 / 👎.
…ence (#157) Codex review: the call-operator wrapper was itself observable through $MyInvocation.InvocationName/.Line, so the fallback could not honestly be called observably identical. The script block is now dot-sourced, which additionally runs the top level in the session scope exactly like -EncodedCommand does; the documentation now names the one remaining observable difference ($MyInvocation's own metadata) instead of claiming identity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
P2 addressed, both halves:
@codex please review again |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1024b26bc6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * this wrapper invocation for a transferred script. | ||
| */ | ||
| private static final String POWERSHELL_FILE_INVOCATION = "powershell.exe -NoProfile -NonInteractive -Command " + | ||
| "\". ([ScriptBlock]::Create((Get-Content -Raw -LiteralPath '%s')))\""; |
There was a problem hiding this comment.
Avoid
Get-Content -Raw on PowerShell 2 hosts
On Windows Server 2008 R2 with its built-in Windows PowerShell 2.0—a host version explicitly accommodated elsewhere in this repository—Get-Content does not support the -Raw parameter. Short scripts still work through -EncodedCommand, but scripts crossing the fallback threshold fail before their content is executed. Read the file through an API available on PowerShell 2, such as a BOM-aware .NET file method, or establish a newer minimum PowerShell version.
Useful? React with 👍 / 👎.
Codex review: Get-Content -Raw appeared in PowerShell 3.0, so the long-script fallback failed on Windows Server 2008 R2 hosts that the encoded form handles fine. The file is now read with the BOM-aware [System.IO.File]::ReadAllText, available since PowerShell 2.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
P2 addressed: the fallback now reads the transferred file with @codex please review again |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #157
What
Adds a
powerShell(String script)method toWinRMClient, alongsidecommand(...):How
powershell.exe -NoProfile -NonInteractive -EncodedCommand <base64>, so no quoting or escaping of the script is ever needed — quotes, pipes, newlines and$variablesreach PowerShell exactly as written.CommandRequest, so every option and both terminals keep working unchanged:timeout(...),charset(...),stdin(...),onStdout(...),execute()/start().IllegalArgumentExceptionpointing at the file-based alternative:command("powershell.exe -NoProfile -File ...").upload(...).Tests
FakeWsmanServer: the command element carries the-EncodedCommandinvocation, and decoding its base64 as UTF-16LE yields the exact script (quotes,$_, newline, non-ASCII).IllegalArgumentException.Documentation
src/site/markdown/commands.md: new "Running PowerShell" section (exit-code semantics, size limit and the-Filealternative, Windows PowerShell vspwsh).🤖 Generated with Claude Code