Skip to content

Commit

Permalink
CHORE: Vendor go-powershell (StackExchange#2837)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlimoncelli authored Feb 15, 2024
1 parent dbbc9e5 commit bb3d191
Show file tree
Hide file tree
Showing 17 changed files with 647 additions and 7 deletions.
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.1
github.com/aws/aws-sdk-go-v2/service/route53domains v1.20.6
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e
github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v3 v3.5.6
github.com/cloudflare/cloudflare-go v0.87.0
Expand Down Expand Up @@ -66,6 +65,7 @@ require (
github.com/fbiville/markdown-table-formatter v0.3.0
github.com/google/go-cmp v0.6.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-isatty v0.0.20
github.com/vultr/govultr/v2 v2.17.2
Expand Down Expand Up @@ -124,7 +124,6 @@ require (
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f // indirect
github.com/juju/testing v0.0.0-20210324180055-18c50b0c2098 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e h1:KCjb01YiNoRaJ5c+SbnPLWjVzU9vqRYHg3e5JcN50nM=
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e/go.mod h1:f7vw6ObmmNcyFQLhZX9eUGBJGpnwTJFDvVjqZxIxHWY=
github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 h1:2vQTbEJvFsyd1VefzZ34GUkUD6TkJleYYJh9/25WBE4=
github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9/go.mod h1:bqqNsI2akL+lLWyApkYY0cxquWPKwEBU0Wd3chi3TEg=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
Expand Down
21 changes: 21 additions & 0 deletions pkg/powershell/LICENSE
Original file line number Diff line number Diff line change
@@ -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 changes: 110 additions & 0 deletions pkg/powershell/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 38 additions & 0 deletions pkg/powershell/backend/local.go
Original file line number Diff line number Diff line change
@@ -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 changes: 69 additions & 0 deletions pkg/powershell/backend/ssh.go
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions pkg/powershell/backend/types.go
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions pkg/powershell/middleware/session.go
Original file line number Diff line number Diff line change
@@ -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()
}
Loading

0 comments on commit bb3d191

Please sign in to comment.