diff --git a/README.md b/README.md index f3f13ee..48c96e1 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,85 @@ functions of the API will not cancel the HTTP requests themselves, it will rather cause a running command to be aborted on the remote machine via a call to `command.Stop()`. +For setting winrm protocol options ([Remote Shell Options](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/593f3ed0-0c7a-4158-a4be-0b429b597e31#Appendix_A_Target_110) and [Command Options](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/c793e333-c409-43c6-a2eb-6ae2489c7ef4#Appendix_A_Target_117)) + +```go +package main +import ( + "github.com/masterzen/winrm" + "fmt" + "os" +) + +endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0) + +params := DefaultParameters +params.RequestOptions["WINRS_NOPROFILE"] = "TRUE" +params.RequestOptions["WINRS_CONSOLEMODE_STDIN"] = "FALSE" +params.RequestOptions["WINRS_SKIP_CMD_SHELL"] = "TRUE" + +client, err := NewClientWithParameters(endpoint, "test", "test", params) +if err != nil { + panic(err) +} + +_, err := client.RunWithInput("ipconfig", os.Stdout, os.Stderr, os.Stdin) +if err != nil { + panic(err) +} + +``` + +To get command output as a byte array, use ExecuteDirect on a shell +NB: This also has a convenience function to write command strings + +```go +package main + +import ( + "github.com/masterzen/winrm" + "fmt" + "bytes" + "log" + "os" +) + +endpoint := winrm.NewEndpoint("localhost", 5985, false, false,nil, nil, nil, 0) +client , err := winrm.NewClient(endpoint, "Administrator", "secret") +if err != nil { + panic(err) +} +shell, err := client.CreateShell() +if err != nil { + panic(err) +} +var cmd *winrm.Command +cmd, err = shell.ExecuteDirect("powershell.exe", "-NonInteractive", "-NoProfile", "-Command", "-") +if err != nil { + panic(err) +} +err = cmd.SendCommand("$PSVersionTable.PSVersion | ConvertTo-Json") +if err != nil { + panic(err) +} +stdin, stderr, finished, exitcode, err := cmd.ReadOutput() +if err != nil { + panic(err) +} +log.Printf("Stdout: '%v'", string(stdout)) +log.Printf("Stderr: '%v'", string(stderr)) + +var result map[string]int +err = json.Unmarshal(stdout, &result) +if err != nil { + panic(err) +} +log.Printf("Version: %d.%d-%d", result["Major"], result["Minor"], result["Build"]) + + +shell.Close() +``` + ## Developing on WinRM If you wish to work on `winrm` itself, you'll first need [Go](http://golang.org) diff --git a/directcommand.go b/directcommand.go new file mode 100644 index 0000000..6b92f6c --- /dev/null +++ b/directcommand.go @@ -0,0 +1,73 @@ +package winrm + +import ( + "bytes" + "fmt" + "strings" +) + +type DirectCommand struct { + client *Client + shell *Shell + id string +} + +func (c *DirectCommand) SendCommand(format string, arguments ...interface{}) error { + return c.SendInput([]byte(fmt.Sprintf(format, arguments...)+"\n"), false) +} + +func (c *DirectCommand) SendInput(data []byte, eof bool) error { + request := NewSendInputRequest(c.client.url, c.shell.id, c.id, data, eof, &c.client.Parameters) + defer request.Free() + + _, err := c.client.sendRequest(request) + return err +} + +func (c *DirectCommand) ReadOutput() ([]byte, []byte, bool, int, error) { + request := NewGetOutputRequest(c.client.url, c.shell.id, c.id, "stdout stderr", &c.client.Parameters) + defer request.Free() + response, err := c.client.sendRequest(request) + if err != nil { + if strings.Contains(err.Error(), "OperationTimeout") { + return nil, nil, false, 0, err + } + return nil, nil, true, -1, err + } + var exitCode int + var stdout, stderr bytes.Buffer + finished, exitCode, err := ParseSlurpOutputErrResponse(response, &stdout, &stderr) + return stdout.Bytes(), stderr.Bytes(), finished, exitCode, nil +} + +func (c *DirectCommand) Close() error { + if c.shell == nil { + return nil + } + defer c.shell.Close() + if c.id != "" || c.client != nil { + return nil + } + request := NewSignalRequest(c.client.url, c.shell.id, c.id, &c.client.Parameters) + defer request.Free() + + _, err := c.client.sendRequest(request) + return err +} + +func (s *Shell) ExecuteDirect(command string, arguments ...string) (*DirectCommand, error) { + request := NewExecuteCommandRequest(s.client.url, s.id, command, arguments, &s.client.Parameters) + defer request.Free() + + response, err := s.client.sendRequest(request) + if err != nil { + return nil, err + } + + commandID, err := ParseExecuteCommandResponse(response) + if err != nil { + return nil, err + } + + return &DirectCommand{s.client, s, commandID}, nil +} diff --git a/parameters.go b/parameters.go index 08adcc9..acae99a 100644 --- a/parameters.go +++ b/parameters.go @@ -8,6 +8,7 @@ type Parameters struct { Timeout string Locale string EnvelopeSize int + RequestOptions map[string]string TransportDecorator func() Transporter Dial func(network, addr string) (net.Conn, error) } @@ -23,5 +24,11 @@ func NewParameters(timeout, locale string, envelopeSize int) *Parameters { Timeout: timeout, Locale: locale, EnvelopeSize: envelopeSize, + RequestOptions: map[string]string{ + "WINRS_NOPROFILE": "FALSE", + "WINRS_CODEPAGE": "65001", + "WINRS_CONSOLEMODE_STDIN": "TRUE", + "WINRS_SKIP_CMD_SHELL": "FALSE", + }, } } diff --git a/request.go b/request.go index e66bfd7..267f8f3 100644 --- a/request.go +++ b/request.go @@ -33,8 +33,8 @@ func NewOpenShellRequest(uri string, params *Parameters) *soap.SoapMessage { defaultHeaders(message, uri, params). Action("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create"). ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"). - AddOption(soap.NewHeaderOption("WINRS_NOPROFILE", "FALSE")). - AddOption(soap.NewHeaderOption("WINRS_CODEPAGE", "65001")). + AddOption(soap.NewHeaderOption("WINRS_NOPROFILE", params.RequestOptions["WINRS_NOPROFILE"])). + AddOption(soap.NewHeaderOption("WINRS_CODEPAGE", params.RequestOptions["WINRS_CODEPAGE"])). Build() body := message.CreateBodyElement("Shell", soap.DOM_NS_WIN_SHELL) @@ -73,8 +73,8 @@ func NewExecuteCommandRequest(uri, shellID, command string, arguments []string, Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command"). ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"). ShellId(shellID). - AddOption(soap.NewHeaderOption("WINRS_CONSOLEMODE_STDIN", "TRUE")). - AddOption(soap.NewHeaderOption("WINRS_SKIP_CMD_SHELL", "FALSE")). + AddOption(soap.NewHeaderOption("WINRS_CONSOLEMODE_STDIN", params.RequestOptions["WINRS_CONSOLEMODE_STDIN"])). + AddOption(soap.NewHeaderOption("WINRS_SKIP_CMD_SHELL", params.RequestOptions["WINRS_SKIP_CMD_SHELL"])). Build() body := message.CreateBodyElement("CommandLine", soap.DOM_NS_WIN_SHELL)