forked from StackExchange/dnscontrol
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CHORE: Vendor go-powershell (StackExchange#2837)
- Loading branch information
1 parent
dbbc9e5
commit bb3d191
Showing
17 changed files
with
647 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.