Skip to content

powershell.Cmd() still emits un-encoded -Command for simple one-liners → breaks on OpenSSH DefaultShell=PowerShell (residual of #346) #398

Description

@vikramhh

Module / version: github.com/k0sproject/rig/v2 v2.0.0 (latest v2 tag)
Related: #346 / #347 / #349 / #325 — those fixed many Windows-default-shell cases, but a general path remains.

Discovered while giving rig v2 a try (via the k0sctl dev branch) ahead of its tagged release.

Summary

powershell.Cmd() selects -EncodedCommand only when the script contains one of \n \r " % ! ^ & | < >. Any "simple" one-liner without those characters is instead emitted as an un-encoded -Command "$ProgressPreference='SilentlyContinue'; <cmd>". When the remote host's OpenSSH DefaultShell is powershell.exe, that outer login PowerShell parses and expands the $ tokens before the inner powershell.exe ever runs — expanding $ProgressPreference (an automatic variable, default value Continue) and any $env:… in the payload — so the command arrives corrupted.

Root cause

powershell/powershell.go:

func Cmd(psCmd string) string {
	if strings.ContainsAny(psCmd, "\n\r\"%!^&|<>") {
		return "... -NoP -E " + EncodeCmd(psCmd)                        // safe (base64)
	}
	return "... -NoP -Command \"" + withProgressPreference(psCmd) + "\""   // unsafe on a PowerShell login shell
}

withProgressPreference prepends $ProgressPreference='SilentlyContinue'; , which is exactly what the outer shell mangles. The -EncodedCommand branch is immune because base64 is opaque to the outer shell — which is why only the "simple" path reproduces this.

Symptom

Continue=SilentlyContinue : The term 'Continue=SilentlyContinue' is not recognized
as the name of a cmdlet, function, script file, or operable program.

and a non-zero exit. For a payload like $env:PROCESSOR_ARCHITECTURE the executed line becomes Continue='SilentlyContinue'; AMD64.

Reproduction

On a Windows host with OpenSSH DefaultShell = powershell.exe:

  1. Connect a rig.Client over SSH.
  2. Run client.ExecOutput("$env:PROCESSOR_ARCHITECTURE", cmd.PS()) (or any powershell.Cmd(<simple one-liner>)).
  3. Observe the Continue=SilentlyContinue … not recognized error / non-zero exit.

It also surfaces through k0sctl (the dev branch, which is on rig v2) during apply against such a host — both are simple one-liners routed via ps.Cmd():

  • Host.Arch() runs $env:PROCESSOR_ARCHITECTUREfailed to detect host architecture: architecture not detected.
  • the Windows configurer's Containers-feature check runs (Get-WindowsFeature -Name Containers -ErrorAction SilentlyContinue).InstallStatefailed to detect Containers feature state.

Why #346's fixes don't cover this

#347/#349 replaced cmd.exe-syntax commands (del /f, sc start, …) with cmd.PS()-wrapped PowerShell cmdlets. But cmd.PS()Cmd() still routes simple payloads through the un-encoded -Command form, so any simple $-containing one-liner remains exposed on a PowerShell default shell.

Suggested fix

Make Cmd() robust regardless of the remote default shell:

  1. Always use -EncodedCommand (EncodeCmd already exists; the only cost is less-readable command strings in trace logs, which can be mitigated by logging the decoded form). ~1-line change.
  2. Or keep -Command only when the runner knows the remote default shell is cmd.exe, else encode.
  3. Or expose an opt-in (e.g. WithForceEncodedPowerShell()) so consumers can force encoding on Windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions