Skip to content
46 changes: 40 additions & 6 deletions cli/azd/pkg/tools/powershell/powershell.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,63 @@ package powershell

import (
"context"
"fmt"
"strings"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/tools"
)

// Creates a new PowershellScript command runner
func NewPowershellScript(commandRunner exec.CommandRunner, cwd string, envVars []string) tools.Script {
return &powershellScript{
commandRunner: commandRunner,
cwd: cwd,
envVars: envVars,
commandRunner: commandRunner,
cwd: cwd,
envVars: envVars,
checkInstalled: checkPath,
}
}

// for testing
func NewPowershellScriptWithMockCheckPath(
commandRunner exec.CommandRunner,
cwd string,
envVars []string,
mockCheckPath checkInstalled) tools.Script {
return &powershellScript{
commandRunner: commandRunner,
cwd: cwd,
envVars: envVars,
checkInstalled: mockCheckPath,
}
}

type powershellScript struct {
commandRunner exec.CommandRunner
cwd string
envVars []string
commandRunner exec.CommandRunner
cwd string
envVars []string
checkInstalled checkInstalled
}

type checkInstalled func(options tools.ExecOptions) error

func checkPath(options tools.ExecOptions) (err error) {
return tools.ToolInPath(strings.Split(options.UserPwsh, " ")[0])
}

// Executes the specified powershell script
// When interactive is true will attach to stdin, stdout & stderr
func (bs *powershellScript) Execute(ctx context.Context, path string, options tools.ExecOptions) (exec.RunResult, error) {
if err := bs.checkInstalled(options); err != nil {
return exec.RunResult{}, &internal.ErrorWithSuggestion{
Err: err,
Suggestion: fmt.Sprintf("PowerShell 7 is not installed or not in the path. To install PowerShell 7, visit %s",
Comment thread
hemarina marked this conversation as resolved.
output.WithLinkFormat("https://learn.microsoft.com/powershell/scripting/install/installing-powershell")),
}
}

runArgs := exec.NewRunArgs(options.UserPwsh, path).
WithCwd(bs.cwd).
WithEnv(bs.envVars).
Expand Down
40 changes: 36 additions & 4 deletions cli/azd/pkg/tools/powershell/powershell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package powershell
import (
"context"
"errors"
"strings"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
Expand All @@ -29,7 +30,7 @@ func Test_Powershell_Execute(t *testing.T) {
// #nosec G101
userPwsh := "pwsh -NoProfile"
mockContext.CommandRunner.When(func(args exec.RunArgs, command string) bool {
return true
return strings.Contains(args.Cmd, userPwsh)
}).RespondFn(func(args exec.RunArgs) (exec.RunResult, error) {
require.Equal(t, userPwsh, args.Cmd)
require.Equal(t, workingDir, args.Cwd)
Expand All @@ -39,7 +40,13 @@ func Test_Powershell_Execute(t *testing.T) {
return exec.NewRunResult(0, "", ""), nil
})

PowershellScript := NewPowershellScript(mockContext.CommandRunner, workingDir, env)
PowershellScript := NewPowershellScriptWithMockCheckPath(
mockContext.CommandRunner,
workingDir,
env,
func(options tools.ExecOptions) error {
return nil
})
runResult, err := PowershellScript.Execute(
*mockContext.Context,
scriptPath,
Expand All @@ -59,7 +66,13 @@ func Test_Powershell_Execute(t *testing.T) {
return exec.NewRunResult(1, "", "error message"), errors.New("error message")
})

PowershellScript := NewPowershellScript(mockContext.CommandRunner, workingDir, env)
PowershellScript := NewPowershellScriptWithMockCheckPath(
mockContext.CommandRunner,
workingDir,
env,
func(options tools.ExecOptions) error {
return nil
})
runResult, err := PowershellScript.Execute(
*mockContext.Context,
scriptPath,
Expand All @@ -70,6 +83,19 @@ func Test_Powershell_Execute(t *testing.T) {
require.Error(t, err)
})

t.Run("NoPowerShellInstalled", func(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())

PowershellScript := NewPowershellScript(mockContext.CommandRunner, workingDir, env)
_, err := PowershellScript.Execute(
*mockContext.Context,
scriptPath,
tools.ExecOptions{Interactive: to.Ptr(true)},
)

require.Error(t, err)
})

tests := []struct {
name string
value tools.ExecOptions
Expand All @@ -89,7 +115,13 @@ func Test_Powershell_Execute(t *testing.T) {
return exec.NewRunResult(0, "", ""), nil
})

PowershellScript := NewPowershellScript(mockContext.CommandRunner, workingDir, env)
PowershellScript := NewPowershellScriptWithMockCheckPath(
mockContext.CommandRunner,
workingDir,
env,
func(options tools.ExecOptions) error {
return nil
})
runResult, err := PowershellScript.Execute(*mockContext.Context, scriptPath, test.value)

require.NotNil(t, runResult)
Expand Down