mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
CHORE: Vendor go-powershell (#2837)
This commit is contained in:
21
pkg/powershell/LICENSE
Normal file
21
pkg/powershell/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
Copyright (c) 2017, Gorillalabs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
http://www.opensource.org/licenses/MIT
|
110
pkg/powershell/README.md
Normal file
110
pkg/powershell/README.md
Normal file
@ -0,0 +1,110 @@
|
||||
# go-powershell
|
||||
|
||||
This package is inspired by [jPowerShell](https://github.com/profesorfalken/jPowerShell)
|
||||
and allows one to run and remote-control a PowerShell session. Use this if you
|
||||
don't have a static script that you want to execute, bur rather run dynamic
|
||||
commands.
|
||||
|
||||
## Installation
|
||||
|
||||
go get github.com/bhendo/go-powershell
|
||||
|
||||
## Usage
|
||||
|
||||
To start a PowerShell shell, you need a backend. Backends take care of starting
|
||||
and controlling the actual powershell.exe process. In most cases, you will want
|
||||
to use the Local backend, which just uses ``os/exec`` to start the process.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ps "github.com/bhendo/go-powershell"
|
||||
"github.com/bhendo/go-powershell/backend"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// choose a backend
|
||||
back := &backend.Local{}
|
||||
|
||||
// start a local powershell process
|
||||
shell, err := ps.New(back)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer shell.Exit()
|
||||
|
||||
// ... and interact with it
|
||||
stdout, stderr, err := shell.Execute("Get-WmiObject -Class Win32_Processor")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(stdout)
|
||||
}
|
||||
```
|
||||
|
||||
## Remote Sessions
|
||||
|
||||
You can use an existing PS shell to use PSSession cmdlets to connect to remote
|
||||
computers. Instead of manually handling that, you can use the Session middleware,
|
||||
which takes care of authentication. Note that you can still use the "raw" shell
|
||||
to execute commands on the computer where the powershell host process is running.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ps "github.com/bhendo/go-powershell"
|
||||
"github.com/bhendo/go-powershell/backend"
|
||||
"github.com/bhendo/go-powershell/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// choose a backend
|
||||
back := &backend.Local{}
|
||||
|
||||
// start a local powershell process
|
||||
shell, err := ps.New(back)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// prepare remote session configuration
|
||||
config := middleware.NewSessionConfig()
|
||||
config.ComputerName = "remote-pc-1"
|
||||
|
||||
// create a new shell by wrapping the existing one in the session middleware
|
||||
session, err := middleware.NewSession(shell, config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer session.Exit() // will also close the underlying ps shell!
|
||||
|
||||
// everything run via the session is run on the remote machine
|
||||
stdout, stderr, err = session.Execute("Get-WmiObject -Class Win32_Processor")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(stdout)
|
||||
}
|
||||
```
|
||||
|
||||
Note that a single shell instance is not safe for concurrent use, as are remote
|
||||
sessions. You can have as many remote sessions using the same shell as you like,
|
||||
but you must execute commands serially. If you need concurrency, you can just
|
||||
spawn multiple PowerShell processes (i.e. call ``.New()`` multiple times).
|
||||
|
||||
Also, note that all commands that you execute are wrapped in special echo
|
||||
statements to delimit the stdout/stderr streams. After ``.Execute()``ing a command,
|
||||
you can therefore not access ``$LastExitCode`` anymore and expect meaningful
|
||||
results.
|
||||
|
||||
## License
|
||||
|
||||
MIT, see LICENSE file.
|
38
pkg/powershell/backend/local.go
Normal file
38
pkg/powershell/backend/local.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
type Local struct{}
|
||||
|
||||
func (b *Local) StartProcess(cmd string, args ...string) (Waiter, io.Writer, io.Reader, io.Reader, error) {
|
||||
command := exec.Command(cmd, args...)
|
||||
|
||||
stdin, err := command.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the PowerShell's stdin stream")
|
||||
}
|
||||
|
||||
stdout, err := command.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the PowerShell's stdout stream")
|
||||
}
|
||||
|
||||
stderr, err := command.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the PowerShell's stderr stream")
|
||||
}
|
||||
|
||||
err = command.Start()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not spawn PowerShell process")
|
||||
}
|
||||
|
||||
return command, stdin, stdout, stderr, nil
|
||||
}
|
69
pkg/powershell/backend/ssh.go
Normal file
69
pkg/powershell/backend/ssh.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// sshSession exists so we don't create a hard dependency on crypto/ssh.
|
||||
type sshSession interface {
|
||||
Waiter
|
||||
|
||||
StdinPipe() (io.WriteCloser, error)
|
||||
StdoutPipe() (io.Reader, error)
|
||||
StderrPipe() (io.Reader, error)
|
||||
Start(string) error
|
||||
}
|
||||
|
||||
type SSH struct {
|
||||
Session sshSession
|
||||
}
|
||||
|
||||
func (b *SSH) StartProcess(cmd string, args ...string) (Waiter, io.Writer, io.Reader, io.Reader, error) {
|
||||
stdin, err := b.Session.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the SSH session's stdin stream")
|
||||
}
|
||||
|
||||
stdout, err := b.Session.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the SSH session's stdout stream")
|
||||
}
|
||||
|
||||
stderr, err := b.Session.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the SSH session's stderr stream")
|
||||
}
|
||||
|
||||
err = b.Session.Start(b.createCmd(cmd, args))
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, errors.Annotate(err, "Could not spawn process via SSH")
|
||||
}
|
||||
|
||||
return b.Session, stdin, stdout, stderr, nil
|
||||
}
|
||||
|
||||
func (b *SSH) createCmd(cmd string, args []string) string {
|
||||
parts := []string{cmd}
|
||||
simple := regexp.MustCompile(`^[a-z0-9_/.~+-]+$`)
|
||||
|
||||
for _, arg := range args {
|
||||
if !simple.MatchString(arg) {
|
||||
arg = b.quote(arg)
|
||||
}
|
||||
|
||||
parts = append(parts, arg)
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func (b *SSH) quote(s string) string {
|
||||
return fmt.Sprintf(`"%s"`, s)
|
||||
}
|
13
pkg/powershell/backend/types.go
Normal file
13
pkg/powershell/backend/types.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package backend
|
||||
|
||||
import "io"
|
||||
|
||||
type Waiter interface {
|
||||
Wait() error
|
||||
}
|
||||
|
||||
type Starter interface {
|
||||
StartProcess(cmd string, args ...string) (Waiter, io.Writer, io.Reader, io.Reader, error)
|
||||
}
|
47
pkg/powershell/middleware/session.go
Normal file
47
pkg/powershell/middleware/session.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
type session struct {
|
||||
upstream Middleware
|
||||
name string
|
||||
}
|
||||
|
||||
func NewSession(upstream Middleware, config *SessionConfig) (Middleware, error) {
|
||||
asserted, ok := config.Credential.(credential)
|
||||
if ok {
|
||||
credentialParamValue, err := asserted.prepare(upstream)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Could not setup credentials")
|
||||
}
|
||||
|
||||
config.Credential = credentialParamValue
|
||||
}
|
||||
|
||||
name := "goSess" + utils.CreateRandomString(8)
|
||||
args := strings.Join(config.ToArgs(), " ")
|
||||
|
||||
_, _, err := upstream.Execute(fmt.Sprintf("$%s = New-PSSession %s", name, args))
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Could not create new PSSession")
|
||||
}
|
||||
|
||||
return &session{upstream, name}, nil
|
||||
}
|
||||
|
||||
func (s *session) Execute(cmd string) (string, string, error) {
|
||||
return s.upstream.Execute(fmt.Sprintf("Invoke-Command -Session $%s -Script {%s}", s.name, cmd))
|
||||
}
|
||||
|
||||
func (s *session) Exit() {
|
||||
s.upstream.Execute(fmt.Sprintf("Disconnect-PSSession -Session $%s", s.name))
|
||||
s.upstream.Exit()
|
||||
}
|
95
pkg/powershell/middleware/session_config.go
Normal file
95
pkg/powershell/middleware/session_config.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
HTTPPort = 5985
|
||||
HTTPSPort = 5986
|
||||
)
|
||||
|
||||
type SessionConfig struct {
|
||||
ComputerName string
|
||||
AllowRedirection bool
|
||||
Authentication string
|
||||
CertificateThumbprint string
|
||||
Credential interface{}
|
||||
Port int
|
||||
UseSSL bool
|
||||
}
|
||||
|
||||
func NewSessionConfig() *SessionConfig {
|
||||
return &SessionConfig{}
|
||||
}
|
||||
|
||||
func (c *SessionConfig) ToArgs() []string {
|
||||
args := make([]string, 0)
|
||||
|
||||
if c.ComputerName != "" {
|
||||
args = append(args, "-ComputerName")
|
||||
args = append(args, utils.QuoteArg(c.ComputerName))
|
||||
}
|
||||
|
||||
if c.AllowRedirection {
|
||||
args = append(args, "-AllowRedirection")
|
||||
}
|
||||
|
||||
if c.Authentication != "" {
|
||||
args = append(args, "-Authentication")
|
||||
args = append(args, utils.QuoteArg(c.Authentication))
|
||||
}
|
||||
|
||||
if c.CertificateThumbprint != "" {
|
||||
args = append(args, "-CertificateThumbprint")
|
||||
args = append(args, utils.QuoteArg(c.CertificateThumbprint))
|
||||
}
|
||||
|
||||
if c.Port > 0 {
|
||||
args = append(args, "-Port")
|
||||
args = append(args, strconv.Itoa(c.Port))
|
||||
}
|
||||
|
||||
if asserted, ok := c.Credential.(string); ok {
|
||||
args = append(args, "-Credential")
|
||||
args = append(args, asserted) // do not quote, as it contains a variable name when using password auth
|
||||
}
|
||||
|
||||
if c.UseSSL {
|
||||
args = append(args, "-UseSSL")
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
type credential interface {
|
||||
prepare(Middleware) (interface{}, error)
|
||||
}
|
||||
|
||||
type UserPasswordCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (c *UserPasswordCredential) prepare(s Middleware) (interface{}, error) {
|
||||
name := "goCred" + utils.CreateRandomString(8)
|
||||
pwname := "goPass" + utils.CreateRandomString(8)
|
||||
|
||||
_, _, err := s.Execute(fmt.Sprintf("$%s = ConvertTo-SecureString -String %s -AsPlainText -Force", pwname, utils.QuoteArg(c.Password)))
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Could not convert password to secure string")
|
||||
}
|
||||
|
||||
_, _, err = s.Execute(fmt.Sprintf("$%s = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList %s, $%s", name, utils.QuoteArg(c.Username), pwname))
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Could not create PSCredential object")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("$%s", name), nil
|
||||
}
|
8
pkg/powershell/middleware/types.go
Normal file
8
pkg/powershell/middleware/types.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package middleware
|
||||
|
||||
type Middleware interface {
|
||||
Execute(cmd string) (string, string, error)
|
||||
Exit()
|
||||
}
|
49
pkg/powershell/middleware/utf8.go
Normal file
49
pkg/powershell/middleware/utf8.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils"
|
||||
)
|
||||
|
||||
// utf8 implements a primitive middleware that encodes all outputs
|
||||
// as base64 to prevent encoding issues between remote PowerShell
|
||||
// shells and the receiver. Just setting $OutputEncoding does not
|
||||
// work reliably enough, sadly.
|
||||
type utf8 struct {
|
||||
upstream Middleware
|
||||
wrapper string
|
||||
}
|
||||
|
||||
func NewUTF8(upstream Middleware) (Middleware, error) {
|
||||
wrapper := "goUTF8" + utils.CreateRandomString(8)
|
||||
|
||||
_, _, err := upstream.Execute(fmt.Sprintf(`function %s { process { if ($_) { [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($_)) } else { '' } } }`, wrapper))
|
||||
|
||||
return &utf8{upstream, wrapper}, err
|
||||
}
|
||||
|
||||
func (u *utf8) Execute(cmd string) (string, string, error) {
|
||||
// Out-String to concat all lines into a single line,
|
||||
// Write-Host to prevent line breaks at the "window width"
|
||||
cmd = fmt.Sprintf(`%s | Out-String | %s | Write-Host`, cmd, u.wrapper)
|
||||
|
||||
stdout, stderr, err := u.upstream.Execute(cmd)
|
||||
if err != nil {
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(stdout)
|
||||
if err != nil {
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
return string(decoded), stderr, nil
|
||||
}
|
||||
|
||||
func (u *utf8) Exit() {
|
||||
u.upstream.Exit()
|
||||
}
|
120
pkg/powershell/shell.go
Normal file
120
pkg/powershell/shell.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package powershell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/powershell/backend"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
const newline = "\r\n"
|
||||
|
||||
type Shell interface {
|
||||
Execute(cmd string) (string, string, error)
|
||||
Exit()
|
||||
}
|
||||
|
||||
type shell struct {
|
||||
handle backend.Waiter
|
||||
stdin io.Writer
|
||||
stdout io.Reader
|
||||
stderr io.Reader
|
||||
}
|
||||
|
||||
func New(backend backend.Starter) (Shell, error) {
|
||||
handle, stdin, stdout, stderr, err := backend.StartProcess("powershell.exe", "-NoExit", "-Command", "-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &shell{handle, stdin, stdout, stderr}, nil
|
||||
}
|
||||
|
||||
func (s *shell) Execute(cmd string) (string, string, error) {
|
||||
if s.handle == nil {
|
||||
return "", "", errors.Annotate(errors.New(cmd), "Cannot execute commands on closed shells.")
|
||||
}
|
||||
|
||||
outBoundary := createBoundary()
|
||||
errBoundary := createBoundary()
|
||||
|
||||
// wrap the command in special markers so we know when to stop reading from the pipes
|
||||
full := fmt.Sprintf("%s; echo '%s'; [Console]::Error.WriteLine('%s')%s", cmd, outBoundary, errBoundary, newline)
|
||||
|
||||
_, err := s.stdin.Write([]byte(full))
|
||||
if err != nil {
|
||||
return "", "", errors.Annotate(errors.Annotate(err, cmd), "Could not send PowerShell command")
|
||||
}
|
||||
|
||||
// read stdout and stderr
|
||||
sout := ""
|
||||
serr := ""
|
||||
|
||||
waiter := &sync.WaitGroup{}
|
||||
waiter.Add(2)
|
||||
|
||||
go streamReader(s.stdout, outBoundary, &sout, waiter)
|
||||
go streamReader(s.stderr, errBoundary, &serr, waiter)
|
||||
|
||||
waiter.Wait()
|
||||
|
||||
if len(serr) > 0 {
|
||||
return sout, serr, errors.Annotate(errors.New(cmd), serr)
|
||||
}
|
||||
|
||||
return sout, serr, nil
|
||||
}
|
||||
|
||||
func (s *shell) Exit() {
|
||||
s.stdin.Write([]byte("exit" + newline))
|
||||
|
||||
// if it's possible to close stdin, do so (some backends, like the local one,
|
||||
// do support it)
|
||||
closer, ok := s.stdin.(io.Closer)
|
||||
if ok {
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
s.handle.Wait()
|
||||
|
||||
s.handle = nil
|
||||
s.stdin = nil
|
||||
s.stdout = nil
|
||||
s.stderr = nil
|
||||
}
|
||||
|
||||
func streamReader(stream io.Reader, boundary string, buffer *string, signal *sync.WaitGroup) error {
|
||||
// read all output until we have found our boundary token
|
||||
output := ""
|
||||
bufsize := 64
|
||||
marker := boundary + newline
|
||||
|
||||
for {
|
||||
buf := make([]byte, bufsize)
|
||||
read, err := stream.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output = output + string(buf[:read])
|
||||
|
||||
if strings.HasSuffix(output, marker) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
*buffer = strings.TrimSuffix(output, marker)
|
||||
signal.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBoundary() string {
|
||||
return "$gorilla" + utils.CreateRandomString(12) + "$"
|
||||
}
|
9
pkg/powershell/utils/quote.go
Normal file
9
pkg/powershell/utils/quote.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
func QuoteArg(s string) string {
|
||||
return "'" + strings.Replace(s, "'", "\"", -1) + "'"
|
||||
}
|
28
pkg/powershell/utils/quote_test.go
Normal file
28
pkg/powershell/utils/quote_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQuotingArguments(t *testing.T) {
|
||||
testcases := [][]string{
|
||||
{"", "''"},
|
||||
{"test", "'test'"},
|
||||
{"two words", "'two words'"},
|
||||
{"quo\"ted", "'quo\"ted'"},
|
||||
{"quo'ted", "'quo\"ted'"},
|
||||
{"quo\\'ted", "'quo\\\"ted'"},
|
||||
{"quo\"t'ed", "'quo\"t\"ed'"},
|
||||
{"es\\caped", "'es\\caped'"},
|
||||
{"es`caped", "'es`caped'"},
|
||||
{"es\\`caped", "'es\\`caped'"},
|
||||
}
|
||||
|
||||
for i, testcase := range testcases {
|
||||
quoted := QuoteArg(testcase[0])
|
||||
|
||||
if quoted != testcase[1] {
|
||||
t.Errorf("test %02d failed: input '%s', expected %s, actual %s", i+1, testcase[0], testcase[1], quoted)
|
||||
}
|
||||
}
|
||||
}
|
20
pkg/powershell/utils/rand.go
Normal file
20
pkg/powershell/utils/rand.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func CreateRandomString(bytes int) string {
|
||||
c := bytes
|
||||
b := make([]byte, c)
|
||||
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(b)
|
||||
}
|
16
pkg/powershell/utils/rand_test.go
Normal file
16
pkg/powershell/utils/rand_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2017 Gorillalabs. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRandomStrings(t *testing.T) {
|
||||
r1 := CreateRandomString(8)
|
||||
r2 := CreateRandomString(8)
|
||||
|
||||
if r1 == r2 {
|
||||
t.Error("Failed to create random strings: The two generated strings are identical.")
|
||||
} else if len(r1) != 16 {
|
||||
t.Errorf("Expected the random string to contain 16 characters, but got %d.", len(r1))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user