diff --git a/pkg/util/shellutil/tokenswap.go b/pkg/util/shellutil/tokenswap.go index 302bfa59b8..cb0ba42708 100644 --- a/pkg/util/shellutil/tokenswap.go +++ b/pkg/util/shellutil/tokenswap.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "strings" "sync" "time" @@ -123,11 +124,12 @@ func encodeEnvVarsForFish(env map[string]string) (string, error) { func encodeEnvVarsForPowerShell(env map[string]string) (string, error) { var encoded string for k, v := range env { - // validate key - if !IsValidEnvVarName(k) { + // PowerShell's braced environment-variable syntax supports Windows names + // such as ProgramFiles(x86), but a closing brace cannot be represented. + if k == "" || strings.Contains(k, "}") { return "", fmt.Errorf("invalid env var name: %q", k) } - encoded += fmt.Sprintf("$env:%s = %s\n", k, HardQuotePowerShell(v)) + encoded += fmt.Sprintf("${env:%s} = %s\n", k, HardQuotePowerShell(v)) } return encoded, nil } diff --git a/pkg/util/shellutil/tokenswap_test.go b/pkg/util/shellutil/tokenswap_test.go new file mode 100644 index 0000000000..cc61df9326 --- /dev/null +++ b/pkg/util/shellutil/tokenswap_test.go @@ -0,0 +1,56 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package shellutil + +import "testing" + +func TestEncodeEnvVarsForPowerShell(t *testing.T) { + tests := []struct { + name string + envName string + envValue string + want string + }{ + { + name: "ProgramFiles(x86)", + envName: "ProgramFiles(x86)", + envValue: `C:\Program Files (x86)`, + want: "${env:ProgramFiles(x86)} = \"C:\\Program Files (x86)\"\n", + }, + { + name: "CommonProgramFiles(x86)", + envName: "CommonProgramFiles(x86)", + envValue: `C:\Program Files\Common Files (x86)`, + want: "${env:CommonProgramFiles(x86)} = \"C:\\Program Files\\Common Files (x86)\"\n", + }, + { + name: "PATH", + envName: "PATH", + envValue: `C:\Windows\System32`, + want: "${env:PATH} = \"C:\\Windows\\System32\"\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := EncodeEnvVarsForShell(ShellType_pwsh, map[string]string{tt.envName: tt.envValue}) + if err != nil { + t.Fatalf("EncodeEnvVarsForShell() returned error: %v", err) + } + if got != tt.want { + t.Errorf("EncodeEnvVarsForShell() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestEncodeEnvVarsForPowerShellRejectsUnrepresentableNames(t *testing.T) { + for _, name := range []string{"", "invalid}name"} { + t.Run(name, func(t *testing.T) { + if _, err := EncodeEnvVarsForShell(ShellType_pwsh, map[string]string{name: "value"}); err == nil { + t.Errorf("EncodeEnvVarsForShell() accepted unrepresentable name %q", name) + } + }) + } +}