Skip to content

Commit 2c25eaf

Browse files
author
Aliwoto
committed
Add support for running powershell scripts and commands.
Add test files new powershell commands feature. Signed-off-by: Aliwoto <[email protected]>
1 parent ee3a80d commit 2c25eaf

File tree

6 files changed

+330
-18
lines changed

6 files changed

+330
-18
lines changed

ssg/helpers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,20 @@ func RunCommandAsync(command string) *ExecuteCommandResult {
260260
return shellUtils.RunCommandAsync(command)
261261
}
262262

263+
// RunCommandAsyncWithChan runs the given command in another goroutine and returns back
264+
// from the function immediately. It will sends `true` to `finishedChan` the moment
265+
// it's done executing the command.
263266
func RunCommandAsyncWithChan(command string, finishedChan chan bool) *ExecuteCommandResult {
264267
return shellUtils.RunCommandAsyncWithChan(command, finishedChan)
265268
}
266269

270+
// RunCommandAsyncWithChan runs the given command in another goroutine and returns back
271+
// from the function immediately. It will sends `true` to `finishedChan` the moment
272+
// it's done executing the command.
273+
func RunPowerShellAsyncWithChan(command string, finishedChan chan bool) *ExecuteCommandResult {
274+
return shellUtils.RunPowerShellAsyncWithChan(command, finishedChan)
275+
}
276+
267277
func ToBool(str string) bool {
268278
return strongParser.BoolMapping[strings.ToLower(strings.TrimSpace(str))]
269279
}

ssg/shellUtils/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@ const (
77
" git rev-parse --verify HEAD &&" +
88
" git fetch && " +
99
"git rev-list --left-right --count origin/master...master"
10+
PowerShellCmd = "powershell"
11+
PromptIgnoreSSG = "PROMPT_IGNORE_SSG>"
12+
PowerShellPromptOverride = "function prompt { \"" + PromptIgnoreSSG + "\" }"
1013
)

ssg/shellUtils/helpers.go

Lines changed: 179 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package shellUtils
22

33
import (
44
"bytes"
5+
"fmt"
56
"os"
67
"os/exec"
8+
"strings"
79
"sync"
810
)
911

@@ -19,22 +21,63 @@ func RunCommandAsyncWithChan(command string, finishedChan chan bool) *ExecuteCom
1921
return runCommand(command, true, finishedChan)
2022
}
2123

24+
func RunPowerShellAsyncWithChan(command string, finishedChan chan bool) *ExecuteCommandResult {
25+
return runPowerShell(command, true, finishedChan)
26+
}
27+
2228
func runCommand(command string, isAsync bool, finishedChan chan bool) *ExecuteCommandResult {
2329
stdout := new(bytes.Buffer)
2430
stderr := new(bytes.Buffer)
2531
var result *ExecuteCommandResult
2632
if isAsync {
2733
result = executeCommand(command, &ExecuteCommandConfig{
34+
TargetRunner: GetCommandTargetRunner(),
35+
PrimaryArgs: GetCommandPrimaryArgs(),
2836
Stdout: stdout,
2937
Stderr: stderr,
3038
autoSetOutput: true,
3139
FinishedChan: finishedChan,
32-
}, true)
40+
IsAsync: true,
41+
})
42+
return result
43+
} else {
44+
result = executeCommand(command, &ExecuteCommandConfig{
45+
TargetRunner: GetCommandTargetRunner(),
46+
PrimaryArgs: GetCommandPrimaryArgs(),
47+
Stdout: stdout,
48+
Stderr: stderr,
49+
})
50+
}
51+
52+
result.Stdout = stdout.String()
53+
result.Stderr = stderr.String()
54+
55+
return result
56+
}
57+
58+
func runPowerShell(command string, isAsync bool, finishedChan chan bool) *ExecuteCommandResult {
59+
stdout := new(bytes.Buffer)
60+
stderr := new(bytes.Buffer)
61+
var result *ExecuteCommandResult
62+
if isAsync {
63+
result = executePowerShell(command, &ExecuteCommandConfig{
64+
TargetRunner: GetPowerShellRunner(),
65+
PrimaryArgs: GetPowerShellPrimaryArgs(),
66+
Stdout: stdout,
67+
Stderr: stderr,
68+
autoSetOutput: true,
69+
FinishedChan: finishedChan,
70+
IsAsync: true,
71+
RemovePowerShellPrompt: true,
72+
})
3373
return result
3474
} else {
35-
result = ExecuteCommand(command, &ExecuteCommandConfig{
36-
Stdout: stdout,
37-
Stderr: stderr,
75+
result = executePowerShell(command, &ExecuteCommandConfig{
76+
TargetRunner: GetCommandTargetRunner(),
77+
PrimaryArgs: GetPowerShellPrimaryArgs(),
78+
Stdout: stdout,
79+
Stderr: stderr,
80+
RemovePowerShellPrompt: true,
3881
})
3982
}
4083

@@ -45,45 +88,165 @@ func runCommand(command string, isAsync bool, finishedChan chan bool) *ExecuteCo
4588
}
4689

4790
func ExecuteCommand(command string, config *ExecuteCommandConfig) *ExecuteCommandResult {
48-
return executeCommand(command, config, false)
91+
if config == nil {
92+
config = &ExecuteCommandConfig{
93+
TargetRunner: GetCommandTargetRunner(),
94+
PrimaryArgs: GetCommandPrimaryArgs(),
95+
}
96+
} else if config.IsAsync {
97+
config.IsAsync = false
98+
}
99+
100+
return executeCommand(command, config)
49101
}
50102

51103
func ExecuteCommandAsync(command string, config *ExecuteCommandConfig) *ExecuteCommandResult {
52-
return executeCommand(command, config, true)
104+
if config == nil {
105+
config = &ExecuteCommandConfig{
106+
TargetRunner: GetCommandTargetRunner(),
107+
PrimaryArgs: GetCommandPrimaryArgs(),
108+
IsAsync: true,
109+
}
110+
}
111+
112+
return executeCommand(command, config)
113+
}
114+
115+
func ExecutePowerShellAsync(command string, config *ExecuteCommandConfig) *ExecuteCommandResult {
116+
if config == nil {
117+
config = &ExecuteCommandConfig{
118+
TargetRunner: GetPowerShellRunner(),
119+
PrimaryArgs: GetPowerShellPrimaryArgs(),
120+
IsAsync: true,
121+
}
122+
}
123+
124+
return executePowerShell(command, config)
125+
}
126+
127+
func GetCommandTargetRunner() string {
128+
if os.PathSeparator == '/' {
129+
return ShellToUseUnix
130+
} else {
131+
return ShellToUseWin
132+
}
133+
}
134+
135+
func GetPowerShellRunner() string {
136+
return PowerShellCmd
137+
}
138+
139+
func GetCommandPrimaryArgs() []string {
140+
if os.PathSeparator == '/' {
141+
return []string{"-c"}
142+
}
143+
return []string{"/c"}
53144
}
54145

146+
func GetPowerShellPrimaryArgs() []string {
147+
return []string{"-nologo", "-noprofile", "-NonInteractive"}
148+
}
149+
150+
// executeCommand is the internal version of the execute command function.
151+
// WARNING: the config argument MUST NOT be nil.
55152
func executeCommand(
56153
command string,
57154
config *ExecuteCommandConfig,
58-
isAsync bool,
59155
) *ExecuteCommandResult {
60-
if config == nil {
61-
config = &ExecuteCommandConfig{}
156+
var cmd *exec.Cmd
157+
result := &ExecuteCommandResult{
158+
autoSetOutput: config.autoSetOutput,
159+
mutex: &sync.Mutex{},
160+
}
161+
162+
cmd = exec.Command(config.TargetRunner, append(config.PrimaryArgs, command)...)
163+
if len(config.ExtraFiles) != 0 {
164+
cmd.ExtraFiles = append(cmd.ExtraFiles, config.ExtraFiles...)
62165
}
63166

167+
cmd.Stdout = config.Stdout
168+
cmd.Stdin = config.Stdin
169+
cmd.Stderr = config.Stderr
170+
cmd.Args = append(cmd.Args, config.AdditionalArgs...)
171+
cmd.Env = append(cmd.Env, config.AdditionalEnv...)
172+
173+
result.cmd = cmd
174+
result.FinishedChan = config.FinishedChan
175+
176+
finishUpCommand(cmd, config, result)
177+
178+
return result
179+
}
180+
181+
// executeCommand is the internal version of the execute powershell function.
182+
// executes the given powershell script/command/set of commands using the
183+
// "powershell" (it might be powershell 5.1 which ships with windows by default).
184+
// WARNING: the config argument MUST NOT be nil.
185+
func executePowerShell(
186+
command string,
187+
config *ExecuteCommandConfig,
188+
) *ExecuteCommandResult {
64189
var cmd *exec.Cmd
65190
result := &ExecuteCommandResult{
66191
autoSetOutput: config.autoSetOutput,
67192
mutex: &sync.Mutex{},
68193
}
69-
if os.PathSeparator == '/' {
70-
cmd = exec.Command(ShellToUseUnix, "-c", command)
194+
195+
if config.RemovePowerShellPrompt && !strings.Contains(command, "function prompt") {
196+
// hacky way of getting rid of powershell prompt
197+
command = PowerShellPromptOverride + command
198+
}
199+
200+
cmd = exec.Command(config.TargetRunner, config.PrimaryArgs...)
201+
if len(config.ExtraFiles) != 0 {
71202
cmd.ExtraFiles = append(cmd.ExtraFiles, config.ExtraFiles...)
72-
} else {
73-
cmd = exec.Command(ShellToUseWin, "/C", command)
74203
}
75204

76205
cmd.Stdout = config.Stdout
77-
cmd.Stdin = config.Stdin
206+
207+
pStdin, err := cmd.StdinPipe()
208+
if err != nil {
209+
result.Error = err
210+
return result
211+
}
212+
213+
if config.Stdin != nil {
214+
wrappedStdin := &StdinWrapper{
215+
InnerWriter: pStdin,
216+
}
217+
218+
result.pipedStdin = wrappedStdin
219+
wrappedStdin.OnWrite = append(wrappedStdin.OnWrite, func(p []byte) (n int, err error) {
220+
return config.Stdin.Read(p)
221+
})
222+
} else {
223+
result.pipedStdin = pStdin
224+
}
225+
78226
cmd.Stderr = config.Stderr
79227
cmd.Args = append(cmd.Args, config.AdditionalArgs...)
80228
cmd.Env = append(cmd.Env, config.AdditionalEnv...)
81229

82230
result.cmd = cmd
83231
result.FinishedChan = config.FinishedChan
232+
_, err = fmt.Fprint(result.pipedStdin, command)
233+
if err != nil {
234+
result.Error = err
235+
return result
236+
}
84237

85-
if isAsync {
238+
finishUpCommand(cmd, config, result)
239+
return result
240+
}
241+
242+
func finishUpCommand(
243+
cmd *exec.Cmd,
244+
config *ExecuteCommandConfig,
245+
result *ExecuteCommandResult,
246+
) {
247+
if config.IsAsync {
86248
go func() {
249+
result.ClosePipes()
87250
result.Error = cmd.Run()
88251
result.IsFinished = true
89252
if result.autoSetOutput {
@@ -103,11 +266,11 @@ func executeCommand(
103266
}
104267
}()
105268
} else {
269+
result.ClosePipes()
106270
result.Error = cmd.Run()
107271
result.IsFinished = true
108272
}
109273

110-
return result
111274
}
112275

113276
// GetGitStats function will return the git stats in the following format:

ssg/shellUtils/methods.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package shellUtils
22

33
import (
4+
"strings"
45
"time"
56
)
67

@@ -85,3 +86,85 @@ func (r *ExecuteCommandResult) Release() error {
8586

8687
return r.cmd.Process.Release()
8788
}
89+
90+
func (r *ExecuteCommandResult) ClosePipes() error {
91+
r.mutex.Lock()
92+
defer r.mutex.Unlock()
93+
94+
if r.pipedStdin != nil {
95+
err := r.pipedStdin.Close()
96+
r.pipedStdin = nil
97+
if err != nil {
98+
return err
99+
}
100+
}
101+
102+
return nil
103+
}
104+
105+
// PurifyOutput will purify the stdout of the result and will
106+
// set the purified version of the output. If you just want to
107+
// get the purified version without setting the new value, use
108+
func (r *ExecuteCommandResult) PurifyPowerShellOutput() string {
109+
r.Stdout = r.GetPurifiedPowerShellOutput()
110+
return r.Stdout
111+
}
112+
113+
func (r *ExecuteCommandResult) GetPurifiedPowerShellOutput() string {
114+
myStrs := strings.Split(r.Stdout, "\n")
115+
if len(myStrs) == 0 {
116+
return ""
117+
}
118+
119+
// if the override doesn't exist, don't do any operation, just
120+
// return the original value.
121+
if !strings.Contains(myStrs[0], PowerShellPromptOverride) {
122+
return r.Stdout
123+
}
124+
125+
purifiedOutput := &strings.Builder{}
126+
for i, currentLine := range myStrs {
127+
if i == 0 {
128+
// it's guaranteed that the first line is our prompt override.
129+
continue
130+
}
131+
132+
if strings.HasPrefix(currentLine, PromptIgnoreSSG) {
133+
continue
134+
}
135+
136+
purifiedOutput.WriteString(currentLine)
137+
purifiedOutput.WriteRune('\n')
138+
}
139+
140+
return strings.TrimSpace(purifiedOutput.String())
141+
}
142+
143+
// --------------------------------------------------------
144+
145+
func (r *StdinWrapper) Write(p []byte) (n int, err error) {
146+
if len(r.OnWrite) > 0 {
147+
for _, currentHandler := range r.OnWrite {
148+
n, err = currentHandler(p)
149+
if err != nil {
150+
return n, err
151+
}
152+
}
153+
}
154+
155+
return r.InnerWriter.Write(p)
156+
}
157+
158+
func (r *StdinWrapper) Close() error {
159+
if len(r.OnClose) > 0 {
160+
var err error
161+
for _, currentHandler := range r.OnClose {
162+
err = currentHandler()
163+
if err != nil {
164+
return err
165+
}
166+
}
167+
}
168+
169+
return r.InnerWriter.Close()
170+
}

0 commit comments

Comments
 (0)