New Upstream Release - golang-github-masterzen-winrm

Ready changes

Summary

Merged new upstream version: 0.0~git20220917.b07f6cb (was: 0.0~git20200615.c42b513).

Resulting package

Built on 2023-08-10T01:23 (took 7m20s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-masterzen-winrm-dev

Lintian Result

Diff

diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..b122159
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,44 @@
+run:
+  go: "1.14"
+  deadline: 10m
+  allow-parallel-runners: true
+linters:
+  disable-all: true
+  enable:
+    # Default linters
+    - deadcode
+    - errcheck
+    - gosimple
+    - govet
+    - ineffassign
+    - staticcheck
+    - structcheck
+    - typecheck
+    - unused
+    - varcheck
+    # Optional linters
+    - asciicheck
+    - bidichk
+    - bodyclose
+    - containedctx
+    - contextcheck
+    - durationcheck
+    - errchkjson
+    - errname
+    - errorlint
+    - exportloopref
+    - gosec
+    - misspell
+    - noctx
+    - nolintlint
+    - revive
+    - stylecheck
+    - whitespace
+linters-settings:
+  revive:
+    ignore-generated-header: true
+    severity: error
+  staticcheck:
+    go: "1.14"
+  stylecheck:
+    go: "1.14"
diff --git a/.travis.yml b/.travis.yml
index 18c04d4..2edf6a3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,6 @@
+arch:
+- amd64
+- ppc64le
 sudo: false
 language: go
 go:
diff --git a/README.md b/README.md
index db84c8b..095f18e 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ _Note_: this library doesn't support domain users (it doesn't support GSSAPI nor
 
 
 ## Getting Started
-WinRM is available on Windows Server 2008 and up. This project natively supports basic authentication for local accounts, see the steps in the next section on how to prepare the remote Windows machine for this scenario. The authentication model is pluggable, see below for an example on using Negotiate/NTLM authentication (e.g. for connecting to vanilla Azure VMs).
+WinRM is available on Windows Server 2008 and up. This project natively supports basic authentication for local accounts, see the steps in the next section on how to prepare the remote Windows machine for this scenario. The authentication model is pluggable, see below for an example on using Negotiate/NTLM authentication (e.g. for connecting to vanilla Azure VMs) or Kerberos authentication (using domain accounts).
 
 _Note_: This library only supports Golang 1.7+
 
@@ -37,10 +37,23 @@ __N.B.:__ The Windows Firewall needs to be running to run this command. See [Mic
 
 __N.B.:__ Do not disable Negotiate authentication as the `winrm` command itself uses this for internal authentication, and you risk getting a system where `winrm` doesn't work anymore.
 
-__N.B.:__ The `MaxMemoryPerShellMB` option has no effects on some Windows 2008R2 systems because of a WinRM bug. Make sure to install the hotfix described [Microsoft Knowledge Base article #2842230](http://support.microsoft.com/kb/2842230) if you need to run commands that uses more than 150MB of memory.
+__N.B.:__ The `MaxMemoryPerShellMB` option has no effects on some Windows 2008R2 systems because of a WinRM bug. Make sure to install the hotfix described [Microsoft Knowledge Base article #2842230](http://support.microsoft.com/kb/2842230) if you need to run commands that use more than 150MB of memory.
 
 For more information on WinRM, please refer to <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa384426(v=vs.85).aspx">the online documentation at Microsoft's DevCenter</a>.
 
+### Preparing the remote Windows machine for kerberos authentication
+This project supports domain users via kerberos authentication. The remote windows system must be prepared for winrm:
+
+On the remote host, a PowerShell prompt, using the __Run as Administrator__ option and paste in the following lines:
+
+                winrm quickconfig
+                y
+                winrm set winrm/config/service '@{AllowUnencrypted="true"}'
+                winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
+
+All __N.B__ points of "Preparing the remote Windows machine for Basic authentication" also applies.
+
+
 ### Building the winrm go and executable
 
 You can build winrm from source:
@@ -82,7 +95,9 @@ client, err := winrm.NewClient(endpoint, "Administrator", "secret")
 if err != nil {
 	panic(err)
 }
-client.Run("ipconfig /all", os.Stdout, os.Stderr)
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
+client.RunWithContext(ctx, "ipconfig /all", os.Stdout, os.Stderr)
 ```
 
 or
@@ -100,7 +115,9 @@ if err != nil {
 	panic(err)
 }
 
-_, err := client.RunWithInput("ipconfig", os.Stdout, os.Stderr, os.Stdin)
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
+_, err := client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
 if err != nil {
 	panic(err)
 }
@@ -134,6 +151,47 @@ if err != nil {
 
 ```
 
+Passing a TransportDecorator also permit to use Kerberos authentication
+
+```go
+package main
+import (
+  "os"
+  "fmt"
+  "github.com/masterzen/winrm"
+)
+
+endpoint := winrm.NewEndpoint("srv-win", 5985, false, false, nil, nil, nil, 0)
+
+params := winrm.DefaultParameters
+params.TransportDecorator = func() Transporter {
+        return &winrm.ClientKerberos{
+		Username: "test",
+		Password: "s3cr3t",
+		Hostname: "srv-win",
+		Realm: "DOMAIN.LAN",
+		Port: 5985,
+		Proto: "http",
+		KrbConf: "/etc/krb5.conf",
+		SPN: fmt.Sprintf("HTTP/%s", hostname),
+	}
+}
+
+client, err := NewClientWithParameters(endpoint, "test", "s3cr3t", params)
+if err != nil {
+        panic(err)
+}
+
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
+_, err := client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
+if err != nil {
+        panic(err)
+}
+
+```
+
+
 By passing a Dial in the Parameters struct it is possible to use different dialer (e.g. tunnel through SSH)
 
 ```go
@@ -163,7 +221,9 @@ package main
         panic(err)
     }
  
-    _, err = client.RunWithInput("ipconfig", os.Stdout, os.Stderr, os.Stdin)
+    ctx, cancel := context.WithCancel(context.Background())
+    defer cancel()
+    _, err = client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
     if err != nil {
         panic(err)
     }
@@ -194,8 +254,10 @@ shell, err := client.CreateShell()
 if err != nil {
   panic(err)
 }
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
 var cmd *winrm.Command
-cmd, err = shell.Execute("cmd.exe")
+cmd, err = shell.ExecuteWithContext(ctx, "cmd.exe")
 if err != nil {
   panic(err)
 }
@@ -249,13 +311,20 @@ func main() {
     if err != nil {
         log.Fatalf("failed to create client: %q", err)
     }
-    _, err = client.Run("whoami", os.Stdout, os.Stderr)
+    ctx, cancel := context.WithCancel(context.Background())
+    defer cancel()
+    _, err = client.RunWithContext(ctx, "whoami", os.Stdout, os.Stderr)
     if err != nil {
         log.Fatalf("failed to run command: %q", err)
     }
 }
 ```
 
+Note: canceling the `context.Context` passed as first argument to the various
+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()`.
+
 ## Developing on WinRM
 
 If you wish to work on `winrm` itself, you'll first need [Go](http://golang.org)
diff --git a/auth.go b/auth.go
index 04e29c6..53f1975 100644
--- a/auth.go
+++ b/auth.go
@@ -12,11 +12,13 @@ import (
 	"github.com/masterzen/winrm/soap"
 )
 
+//ClientAuthRequest ClientAuthRequest
 type ClientAuthRequest struct {
 	transport http.RoundTripper
 	dial      func(network, addr string) (net.Conn, error)
 }
 
+//Transport Transport
 func (c *ClientAuthRequest) Transport(endpoint *Endpoint) error {
 	cert, err := tls.X509KeyPair(endpoint.Cert, endpoint.Key)
 	if err != nil {
@@ -32,6 +34,7 @@ func (c *ClientAuthRequest) Transport(endpoint *Endpoint) error {
 		dial = c.dial
 	}
 
+	//nolint:gosec
 	transport := &http.Transport{
 		Proxy: http.ProxyFromEnvironment,
 		TLSClientConfig: &tls.Config{
@@ -59,8 +62,7 @@ func (c *ClientAuthRequest) Transport(endpoint *Endpoint) error {
 
 // parse func reads the response body and return it as a string
 func parse(response *http.Response) (string, error) {
-
-	// if we recived the content we expected
+	// if we received the content we expected
 	if strings.Contains(response.Header.Get("Content-Type"), "application/soap+xml") {
 		body, err := ioutil.ReadAll(response.Body)
 		defer func() {
@@ -71,7 +73,7 @@ func parse(response *http.Response) (string, error) {
 			}
 		}()
 		if err != nil {
-			return "", fmt.Errorf("error while reading request body %s", err)
+			return "", fmt.Errorf("error while reading request body %w", err)
 		}
 
 		return string(body), nil
@@ -80,12 +82,13 @@ func parse(response *http.Response) (string, error) {
 	return "", fmt.Errorf("invalid content type")
 }
 
+//Post Post
 func (c ClientAuthRequest) Post(client *Client, request *soap.SoapMessage) (string, error) {
 	httpClient := &http.Client{Transport: c.transport}
 
 	req, err := http.NewRequest("POST", client.url, strings.NewReader(request.String()))
 	if err != nil {
-		return "", fmt.Errorf("impossible to create http request %s", err)
+		return "", fmt.Errorf("impossible to create http request %w", err)
 	}
 
 	req.Header.Set("Content-Type", soapXML+";charset=UTF-8")
@@ -93,12 +96,12 @@ func (c ClientAuthRequest) Post(client *Client, request *soap.SoapMessage) (stri
 
 	resp, err := httpClient.Do(req)
 	if err != nil {
-		return "", fmt.Errorf("unknown error %s", err)
+		return "", fmt.Errorf("unknown error %w", err)
 	}
 
 	body, err := parse(resp)
 	if err != nil {
-		return "", fmt.Errorf("http response error: %d - %s", resp.StatusCode, err.Error())
+		return "", fmt.Errorf("http response error: %d - %w", resp.StatusCode, err)
 	}
 
 	// if we have different 200 http status code
@@ -112,6 +115,7 @@ func (c ClientAuthRequest) Post(client *Client, request *soap.SoapMessage) (stri
 	return body, err
 }
 
+//NewClientAuthRequestWithDial NewClientAuthRequestWithDial
 func NewClientAuthRequestWithDial(dial func(network, addr string) (net.Conn, error)) *ClientAuthRequest {
 	return &ClientAuthRequest{
 		dial: dial,
diff --git a/client.go b/client.go
index bc4197f..a38a1e7 100644
--- a/client.go
+++ b/client.go
@@ -2,9 +2,12 @@ package winrm
 
 import (
 	"bytes"
+	"context"
 	"crypto/x509"
+	"errors"
 	"fmt"
 	"io"
+	"strings"
 	"sync"
 
 	"github.com/masterzen/winrm/soap"
@@ -37,7 +40,6 @@ func NewClient(endpoint *Endpoint, user, password string) (*Client, error) {
 // NewClientWithParameters will create a new remote client on url, connecting with user and password
 // This function doesn't connect (connection happens only when CreateShell is called)
 func NewClientWithParameters(endpoint *Endpoint, user, password string, params *Parameters) (*Client, error) {
-
 	// alloc a new client
 	client := &Client{
 		Parameters: *params,
@@ -56,7 +58,7 @@ func NewClientWithParameters(endpoint *Endpoint, user, password string, params *
 
 	// set the transport to some endpoint configuration
 	if err := client.http.Transport(endpoint); err != nil {
-		return nil, fmt.Errorf("Can't parse this key and certs: %s", err)
+		return nil, fmt.Errorf("can't parse this key and certs: %w", err)
 	}
 
 	return client, nil
@@ -66,7 +68,7 @@ func readCACerts(certs []byte) (*x509.CertPool, error) {
 	certPool := x509.NewCertPool()
 
 	if !certPool.AppendCertsFromPEM(certs) {
-		return nil, fmt.Errorf("Unable to read certificates")
+		return nil, errors.New("unable to read certificates")
 	}
 
 	return certPool, nil
@@ -89,7 +91,6 @@ func (c *Client) CreateShell() (*Shell, error) {
 	}
 
 	return c.NewShell(shellID), nil
-
 }
 
 // NewShell will create a new WinRM Shell for the given shellID
@@ -104,77 +105,56 @@ func (c *Client) sendRequest(request *soap.SoapMessage) (string, error) {
 
 // Run will run command on the the remote host, writing the process stdout and stderr to
 // the given writers. Note with this method it isn't possible to inject stdin.
+//
+// Deprecated: use RunWithContext()
 func (c *Client) Run(command string, stdout io.Writer, stderr io.Writer) (int, error) {
-	shell, err := c.CreateShell()
-	if err != nil {
-		return 1, err
-	}
-	defer shell.Close()
-	cmd, err := shell.Execute(command)
-	if err != nil {
-		return 1, err
-	}
-
-	var wg sync.WaitGroup
-	wg.Add(2)
-
-	go func() {
-		defer wg.Done()
-		io.Copy(stdout, cmd.Stdout)
-	}()
-
-	go func() {
-		defer wg.Done()
-		io.Copy(stderr, cmd.Stderr)
-	}()
-
-	cmd.Wait()
-	wg.Wait()
-	cmd.Close()
+	return c.RunWithContext(context.Background(), command, stdout, stderr)
+}
 
-	return cmd.ExitCode(), cmd.err
+// RunWithContext will run command on the the remote host, writing the process stdout and stderr to
+// the given writers. Note with this method it isn't possible to inject stdin.
+// If the context is canceled, the remote command is canceled.
+func (c *Client) RunWithContext(ctx context.Context, command string, stdout io.Writer, stderr io.Writer) (int, error) {
+	return c.RunWithContextWithInput(ctx, command, stdout, stderr, nil)
 }
 
 // RunWithString will run command on the the remote host, returning the process stdout and stderr
 // as strings, and using the input stdin string as the process input
+//
+// Deprecated: use RunWithContextWithString()
 func (c *Client) RunWithString(command string, stdin string) (string, string, int, error) {
-	shell, err := c.CreateShell()
-	if err != nil {
-		return "", "", 1, err
-	}
-	defer shell.Close()
-
-	cmd, err := shell.Execute(command)
-	if err != nil {
-		return "", "", 1, err
-	}
-
-	if len(stdin) > 0 {
-		defer cmd.Stdin.Close()
-		_, err := cmd.Stdin.Write([]byte(stdin))
-		if err != nil {
-			return "", "", -1, err
-		}
-	}
+	return c.RunWithContextWithString(context.Background(), command, stdin)
+}
 
+// RunWithContextWithString will run command on the the remote host, returning the process stdout and stderr
+// as strings, and using the input stdin string as the process input
+// If the context is canceled, the remote command is canceled.
+func (c *Client) RunWithContextWithString(ctx context.Context, command string, stdin string) (string, string, int, error) {
 	var outWriter, errWriter bytes.Buffer
-	var wg sync.WaitGroup
-	wg.Add(2)
-	go func() {
-		defer wg.Done()
-		io.Copy(&outWriter, cmd.Stdout)
-	}()
+	exitCode, err := c.RunWithContextWithInput(ctx, command, &outWriter, &errWriter, strings.NewReader(stdin))
+	return outWriter.String(), errWriter.String(), exitCode, err
+}
 
-	go func() {
-		defer wg.Done()
-		io.Copy(&errWriter, cmd.Stderr)
-	}()
+// RunPSWithString will basically wrap your code to execute commands in powershell.exe. Default RunWithString
+// runs commands in cmd.exe
+//
+// Deprecated: use RunPSWithContextWithString()
+func (c *Client) RunPSWithString(command string, stdin string) (string, string, int, error) {
+	return c.RunPSWithContextWithString(context.Background(), command, stdin)
+}
 
-	cmd.Wait()
-	wg.Wait()
-	cmd.Close()
+// RunPSWithContextWithString will basically wrap your code to execute commands in powershell.exe. Default RunWithString
+// runs commands in cmd.exe
+func (c *Client) RunPSWithContextWithString(ctx context.Context, command string, stdin string) (string, string, int, error) {
+	command = Powershell(command)
+
+	// Let's check if we actually created a command
+	if command == "" {
+		return "", "", 1, errors.New("cannot encode the given command")
+	}
 
-	return outWriter.String(), errWriter.String(), cmd.ExitCode(), cmd.err
+	// Specify powershell.exe to run encoded command
+	return c.RunWithContextWithString(ctx, command, stdin)
 }
 
 // RunWithInput will run command on the the remote host, writing the process stdout and stderr to
@@ -182,13 +162,27 @@ func (c *Client) RunWithString(command string, stdin string) (string, string, in
 // Warning stdin (not stdout/stderr) are bufferized, which means reading only one byte in stdin will
 // send a winrm http packet to the remote host. If stdin is a pipe, it might be better for
 // performance reasons to buffer it.
-func (c Client) RunWithInput(command string, stdout, stderr io.Writer, stdin io.Reader) (int, error) {
+// If stdin is nil, this is equivalent to c.Run()
+//
+// Deprecated: use RunWithContextWithInput()
+func (c *Client) RunWithInput(command string, stdout, stderr io.Writer, stdin io.Reader) (int, error) {
+	return c.RunWithContextWithInput(context.Background(), command, stdout, stderr, stdin)
+}
+
+// RunWithContextWithInput will run command on the the remote host, writing the process stdout and stderr to
+// the given writers, and injecting the process stdin with the stdin reader.
+// If the context is canceled, the command on the remote machine is canceled.
+// Warning stdin (not stdout/stderr) are bufferized, which means reading only one byte in stdin will
+// send a winrm http packet to the remote host. If stdin is a pipe, it might be better for
+// performance reasons to buffer it.
+// If stdin is nil, this is equivalent to c.RunWithContext()
+func (c *Client) RunWithContextWithInput(ctx context.Context, command string, stdout, stderr io.Writer, stdin io.Reader) (int, error) {
 	shell, err := c.CreateShell()
 	if err != nil {
 		return 1, err
 	}
 	defer shell.Close()
-	cmd, err := shell.Execute(command)
+	cmd, err := shell.ExecuteWithContext(ctx, command)
 	if err != nil {
 		return 1, err
 	}
@@ -198,18 +192,23 @@ func (c Client) RunWithInput(command string, stdout, stderr io.Writer, stdin io.
 
 	go func() {
 		defer func() {
-			cmd.Stdin.Close()
 			wg.Done()
 		}()
-		io.Copy(cmd.Stdin, stdin)
+		if stdin == nil {
+			return
+		}
+		defer func() {
+			cmd.Stdin.Close()
+		}()
+		_, _ = io.Copy(cmd.Stdin, stdin)
 	}()
 	go func() {
 		defer wg.Done()
-		io.Copy(stdout, cmd.Stdout)
+		_, _ = io.Copy(stdout, cmd.Stdout)
 	}()
 	go func() {
 		defer wg.Done()
-		io.Copy(stderr, cmd.Stderr)
+		_, _ = io.Copy(stderr, cmd.Stderr)
 	}()
 
 	cmd.Wait()
@@ -217,5 +216,4 @@ func (c Client) RunWithInput(command string, stdout, stderr io.Writer, stdin io.
 	cmd.Close()
 
 	return cmd.ExitCode(), cmd.err
-
 }
diff --git a/client_test.go b/client_test.go
index 4d7dc4d..48af06b 100644
--- a/client_test.go
+++ b/client_test.go
@@ -8,28 +8,30 @@ import (
 
 	"github.com/masterzen/winrm/soap"
 
-	. "gopkg.in/check.v1"
 	"net"
 	"time"
+
+	. "gopkg.in/check.v1"
 )
 
 type Requester struct {
 	http      func(*Client, *soap.SoapMessage) (string, error)
 	transport http.RoundTripper
-	dial func(network, addr string) (net.Conn, error)
+	dial      func(network, addr string) (net.Conn, error)
 }
 
-func (r Requester) Post(client *Client, request *soap.SoapMessage) (string, error) {
+func (r *Requester) Post(client *Client, request *soap.SoapMessage) (string, error) {
 	return r.http(client, request)
 }
 
-func (r Requester) Transport(endpoint *Endpoint) error {
+func (r *Requester) Transport(endpoint *Endpoint) error {
+	//nolint:gosec
 	transport := &http.Transport{
 		TLSClientConfig: &tls.Config{
 			InsecureSkipVerify: endpoint.Insecure,
 		},
 		ResponseHeaderTimeout: endpoint.Timeout,
-		Dial: r.dial,
+		Dial:                  r.dial,
 	}
 
 	if endpoint.CACert != nil && len(endpoint.CACert) > 0 {
@@ -42,9 +44,7 @@ func (r Requester) Transport(endpoint *Endpoint) error {
 	}
 
 	r.transport = transport
-
 	return nil
-
 }
 
 func (s *WinRMSuite) TestNewClient(c *C) {
@@ -58,7 +58,6 @@ func (s *WinRMSuite) TestNewClient(c *C) {
 }
 
 func (s *WinRMSuite) TestClientCreateShell(c *C) {
-
 	endpoint := NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)
 	client, err := NewClient(endpoint, "Administrator", "v3r1S3cre7")
 	c.Assert(err, IsNil)
@@ -67,7 +66,7 @@ func (s *WinRMSuite) TestClientCreateShell(c *C) {
 		c.Assert(message.String(), Contains, "http://schemas.xmlsoap.org/ws/2004/09/transfer/Create")
 		return createShellResponse, nil
 	}
-	client.http = r
+	client.http = &r
 
 	shell, _ := client.CreateShell()
 	c.Assert(shell.id, Equals, "67A74734-DD32-4F10-89DE-49A060483810")
@@ -105,6 +104,21 @@ func (s *WinRMSuite) TestRunWithString(c *C) {
 	c.Assert(stderr, Equals, "This is stderr, I'm pretty sure!")
 }
 
+func (s *WinRMSuite) TestRunPSWithString(c *C) {
+	ts, host, port, err := runWinRMFakeServer(c, "this is the input")
+	c.Assert(err, IsNil)
+	defer ts.Close()
+	endpoint := NewEndpoint(host, port, false, false, nil, nil, nil, 0)
+	client, err := NewClient(endpoint, "Administrator", "v3r1S3cre7")
+	c.Assert(err, IsNil)
+
+	stdout, stderr, code, err := client.RunPSWithString("ipconfig /all", "this is the input")
+	c.Assert(err, IsNil)
+	c.Assert(code, Equals, 123)
+	c.Assert(stdout, Equals, "That's all folks!!!")
+	c.Assert(stderr, Equals, "This is stderr, I'm pretty sure!")
+}
+
 func (s *WinRMSuite) TestRunWithInput(c *C) {
 	ts, host, port, err := runWinRMFakeServer(c, "this is the input")
 	c.Assert(err, IsNil)
@@ -221,7 +235,6 @@ func (s *WinRMSuite) TestReplaceTransportWithDecorator(c *C) {
 	c.Assert(ok, Equals, true)
 }
 
-
 func (s *WinRMSuite) TestReplaceDial(c *C) {
 	ts, host, port, err := runWinRMFakeServer(c, "this is the input")
 	c.Assert(err, IsNil)
@@ -241,6 +254,7 @@ func (s *WinRMSuite) TestReplaceDial(c *C) {
 
 	endpoint := NewEndpoint(host, port, false, false, nil, nil, nil, 0)
 	client, err := NewClientWithParameters(endpoint, "Administrator", "v3r1S3cre7", params)
+	c.Assert(err, IsNil)
 	var stdout, stderr bytes.Buffer
 	_, err = client.Run("ipconfig /all", &stdout, &stderr)
 	c.Assert(err, IsNil)
diff --git a/command.go b/command.go
index 1f57d34..80afd95 100644
--- a/command.go
+++ b/command.go
@@ -2,6 +2,7 @@ package winrm
 
 import (
 	"bytes"
+	"context"
 	"errors"
 	"io"
 	"strings"
@@ -28,7 +29,6 @@ type Command struct {
 	shell    *Shell
 	id       string
 	exitCode int
-	finished bool
 	err      error
 
 	Stdin  *commandWriter
@@ -39,7 +39,7 @@ type Command struct {
 	cancel chan struct{}
 }
 
-func newCommand(shell *Shell, ids string) *Command {
+func newCommand(ctx context.Context, shell *Shell, ids string) *Command {
 	command := &Command{
 		shell:    shell,
 		client:   shell.client,
@@ -57,7 +57,7 @@ func newCommand(shell *Shell, ids string) *Command {
 	}
 	command.Stderr = newCommandReader("stderr", command)
 
-	go fetchOutput(command)
+	go fetchOutput(ctx, command)
 
 	return command
 }
@@ -72,12 +72,21 @@ func newCommandReader(stream string, command *Command) *commandReader {
 	}
 }
 
-func fetchOutput(command *Command) {
+func fetchOutput(ctx context.Context, command *Command) {
+	ctxDone := ctx.Done()
 	for {
 		select {
 		case <-command.cancel:
+			_, _ = command.slurpAllOutput()
+			err := errors.New("canceled")
+			command.Stderr.write.CloseWithError(err)
+			command.Stdout.write.CloseWithError(err)
 			close(command.done)
 			return
+		case <-ctxDone:
+			command.err = ctx.Err()
+			ctxDone = nil
+			command.Close()
 		default:
 			finished, err := command.slurpAllOutput()
 			if finished {
@@ -155,15 +164,15 @@ func (c *Command) slurpAllOutput() (bool, error) {
 		return true, err
 	}
 	if stdout.Len() > 0 {
-		c.Stdout.write.Write(stdout.Bytes())
+		_, _ = c.Stdout.write.Write(stdout.Bytes())
 	}
 	if stderr.Len() > 0 {
-		c.Stderr.write.Write(stderr.Bytes())
+		_, _ = c.Stderr.write.Write(stderr.Bytes())
 	}
 	if finished {
 		c.exitCode = exitCode
-		c.Stderr.write.Close()
-		c.Stdout.write.Close()
+		_ = c.Stderr.write.Close()
+		_ = c.Stdout.write.Close()
 	}
 
 	return finished, nil
@@ -254,7 +263,7 @@ func (w *commandWriter) Close() error {
 // Read data from this Pipe
 func (r *commandReader) Read(buf []byte) (int, error) {
 	n, err := r.read.Read(buf)
-	if err != nil && err != io.EOF {
+	if err != nil && errors.Is(err, io.EOF) {
 		return 0, err
 	}
 	return n, err
diff --git a/command_test.go b/command_test.go
index 79943c0..09100e5 100644
--- a/command_test.go
+++ b/command_test.go
@@ -42,14 +42,14 @@ func (s *WinRMSuite) TestExecuteCommand(c *C) {
 			}
 		}
 	}
-	client.http = r
+	client.http = &r
 	command, _ := shell.Execute("ipconfig /all")
 	var stdout, stderr bytes.Buffer
 	var wg sync.WaitGroup
 	f := func(b *bytes.Buffer, r *commandReader) {
 		wg.Add(1)
 		defer wg.Done()
-		io.Copy(b, r)
+		_, _ = io.Copy(b, r)
 	}
 	go f(&stdout, command.Stdout)
 	go f(&stderr, command.Stderr)
@@ -75,24 +75,23 @@ func (s *WinRMSuite) TestStdinCommand(c *C) {
 		if strings.Contains(message.String(), "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send") {
 			c.Assert(message.String(), Contains, "c3RhbmRhcmQgaW5wdXQ=")
 			return "", nil
+		}
+		if strings.Contains(message.String(), "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command") {
+			return executeCommandResponse, nil
+		} else if count != 1 && strings.Contains(message.String(), "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive") {
+			count = 1
+			return outputResponse, nil
 		} else {
-			if strings.Contains(message.String(), "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command") {
-				return executeCommandResponse, nil
-			} else if count != 1 && strings.Contains(message.String(), "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive") {
-				count = 1
-				return outputResponse, nil
-			} else {
-				return doneCommandResponse, nil
-			}
+			return doneCommandResponse, nil
 		}
 	}
-	client.http = r
+	client.http = &r
 	command, _ := shell.Execute("ipconfig /all")
-	command.Stdin.Write([]byte("standard input"))
+	_, _ = command.Stdin.Write([]byte("standard input"))
 	// slurp output from command
 	var outWriter, errWriter bytes.Buffer
-	go io.Copy(&outWriter, command.Stdout)
-	go io.Copy(&errWriter, command.Stderr)
+	go func() { _, _ = io.Copy(&outWriter, command.Stdout) }()
+	go func() { _, _ = io.Copy(&errWriter, command.Stderr) }()
 	command.Wait()
 }
 
@@ -120,7 +119,7 @@ func (s *WinRMSuite) TestCommandExitCode(c *C) {
 			return doneCommandExitCode0Response, nil
 		}
 	}
-	client.http = r
+	client.http = &r
 	command, _ := shell.Execute("ipconfig /all")
 
 	command.Wait()
@@ -155,11 +154,11 @@ func (s *WinRMSuite) TestCloseCommandStopsFetch(c *C) {
 			return "", nil
 		}
 	}
-	client.http = r
+	client.http = &r
 	command, _ := shell.Execute("ipconfig /all")
 	// need to be reading Stdout/Stderr, otherwise, the writes to these are blocking...
-	go ioutil.ReadAll(command.Stdout)
-	go ioutil.ReadAll(command.Stderr)
+	go func() { _, _ = ioutil.ReadAll(command.Stdout) }()
+	go func() { _, _ = ioutil.ReadAll(command.Stderr) }()
 
 	httpChan <- outputResponse // wait for command to enter fetch/slurp
 
@@ -167,7 +166,7 @@ func (s *WinRMSuite) TestCloseCommandStopsFetch(c *C) {
 
 	select {
 	case httpChan <- outputResponse: // return to fetch from slurp
-		c.Log("Fetch loop 'drained' one last reponse before realizing that the command is now closed")
+		c.Log("Fetch loop 'drained' one last response before realizing that the command is now closed")
 	case <-time.After(1 * time.Second):
 		c.Log("no poll within one second, fetch may have stopped")
 	}
@@ -203,11 +202,10 @@ func (s *WinRMSuite) TestConnectionTimeout(c *C) {
 			}
 		}
 	}))
-	defer ts.Close()
-
 	if err != nil {
 		c.Error(err)
 	}
+	defer ts.Close()
 
 	endpoint := NewEndpoint(host, port, false, false, nil, nil, nil, 1*time.Second)
 	client, err := NewClient(endpoint, "Administrator", "v3r1S3cre7")
@@ -245,11 +243,10 @@ func (s *WinRMSuite) TestOperationTimeoutSupport(c *C) {
 			}
 		}
 	}))
-	defer ts.Close()
-
 	if err != nil {
 		c.Error(err)
 	}
+	defer ts.Close()
 
 	endpoint := NewEndpoint(host, port, false, false, nil, nil, nil, 0)
 	client, err := NewClient(endpoint, "Administrator", "v3r1S3cre7")
@@ -262,7 +259,7 @@ func (s *WinRMSuite) TestOperationTimeoutSupport(c *C) {
 	f := func(b *bytes.Buffer, r *commandReader) {
 		wg.Add(1)
 		defer wg.Done()
-		io.Copy(b, r)
+		_, _ = io.Copy(b, r)
 	}
 	go f(&stdout, command.Stdout)
 	go f(&stderr, command.Stderr)
@@ -290,7 +287,7 @@ func (s *WinRMSuite) TestEOFError(c *C) {
 			return doneCommandExitCode0Response, nil
 		}
 	}
-	client.http = r
+	client.http = &r
 	shell := &Shell{client: client, id: "67A74734-DD32-4F10-89DE-49A060483810"}
 	command, _ := shell.Execute("ipconfig /all")
 
diff --git a/debian/changelog b/debian/changelog
index 289ae24..2d412de 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-masterzen-winrm (0.0~git20220917.b07f6cb-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 10 Aug 2023 01:16:36 -0000
+
 golang-github-masterzen-winrm (0.0~git20200615.c42b513-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/error.go b/error.go
index aaa50dc..f2df4e6 100644
--- a/error.go
+++ b/error.go
@@ -1,13 +1,11 @@
 package winrm
 
-import "fmt"
-
-// errWinrm generic error struct
-type errWinrm struct {
+// winrmError generic error struct
+type winrmError struct {
 	message string
 }
 
 // ErrWinrm implements the Error type interface
-func (e errWinrm) Error() string {
-	return fmt.Sprintf("%s", e.message)
+func (e winrmError) Error() string {
+	return e.message
 }
diff --git a/error_test.go b/error_test.go
index e30ef1f..a4f33ca 100644
--- a/error_test.go
+++ b/error_test.go
@@ -7,14 +7,13 @@ import (
 )
 
 func (s *WinRMSuite) TestError(c *C) {
-
-	err := errWinrm{
+	err := winrmError{
 		message: "Some test error",
 	}
 	same := errors.New("Some test error")
 	func(err, same error) {
-		t, ok := err.(errWinrm)
-		c.Assert(ok, Equals, true)
-		c.Assert(t.Error(), Equals, same.Error())
+		var wErr winrmError
+		c.Assert(errors.As(err, &wErr), Equals, true)
+		c.Assert(wErr.Error(), Equals, same.Error())
 	}(err, same)
 }
diff --git a/fixture_test.go b/fixture_test.go
index bbaafea..a4bf47c 100644
--- a/fixture_test.go
+++ b/fixture_test.go
@@ -118,8 +118,7 @@ func FindHostAndPortFromURL(rawurl string) (string, int, error) {
 // StartTestServer will start an httptest server on a random port with the given handler
 // and then return the host and port on which this server is listening
 func StartTestServer(handler http.Handler) (*httptest.Server, string, int, error) {
-	var ts *httptest.Server
-	ts = httptest.NewServer(handler)
+	ts := httptest.NewServer(handler)
 	host, port, err := FindHostAndPortFromURL(ts.URL)
 	return ts, host, port, err
 }
diff --git a/go.mod b/go.mod
index d8d814b..002a338 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,21 @@ module github.com/masterzen/winrm
 go 1.14
 
 require (
-	github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4
-	github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022
-	github.com/gofrs/uuid v3.2.0+incompatible
+	github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e
+	github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6
+	github.com/gofrs/uuid v4.2.0+incompatible
+	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/hashicorp/go-uuid v1.0.2 // indirect
+	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
+	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
+	github.com/jcmturner/gofork v1.0.0 // indirect
+	github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
+	github.com/jcmturner/gokrb5/v8 v8.4.2
+	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
 	github.com/kr/pretty v0.1.0 // indirect
-	github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9
-	golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f // indirect
-	golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect
-	golang.org/x/text v0.3.0 // indirect
+	github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786
+	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
+	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
+	golang.org/x/text v0.3.7
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
 )
diff --git a/go.sum b/go.sum
index 5c3cc36..897cf0b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,21 +1,88 @@
-github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
-github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
-github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022 h1:y8Gs8CzNfDF5AZvjr+5UyGQvQEBL7pwo+v+wX6q9JI8=
-github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
-github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
-github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ=
+github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
+github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns=
+github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
+github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
+github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
+github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
+github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9 h1:SmVbOZFWAlyQshuMfOkiAx1f5oUTsOGG5IXplAEYeeM=
-github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
-golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f h1:qWFY9ZxP3tfI37wYIs/MnIAqK0vlXp1xnYEa5HxFSSY=
-golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg=
+github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/revel/config v1.0.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI=
+github.com/revel/log15 v2.11.20+incompatible/go.mod h1:l0WmLRs+IM1hBl4noJiBc2tZQiOgZyXzS1mdmFt+5Gc=
+github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9/go.mod h1:TmlwoRLDvgRjoTe6rbsxIaka/CulzYrgfef7iNJcEWY=
+github.com/revel/revel v1.0.0/go.mod h1:VZWJnHjpDEtuGUuZJ2NO42XryitrtwsdVaJxfDeo5yc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
+github.com/xeonx/timeago v1.0.0-rc4/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0/go.mod h1:kl/bNzW/jgTgUOCGDj3XPn9/Hbfhw6pjfBRUnaTioFQ=
+gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/http.go b/http.go
index ebc3f9d..72d7688 100644
--- a/http.go
+++ b/http.go
@@ -17,8 +17,7 @@ var soapXML = "application/soap+xml"
 
 // body func reads the response body and return it as a string
 func body(response *http.Response) (string, error) {
-
-	// if we recived the content we expected
+	// if we received the content we expected
 	if strings.Contains(response.Header.Get("Content-Type"), "application/soap+xml") {
 		body, err := ioutil.ReadAll(response.Body)
 		defer func() {
@@ -29,7 +28,7 @@ func body(response *http.Response) (string, error) {
 			}
 		}()
 		if err != nil {
-			return "", fmt.Errorf("error while reading request body %s", err)
+			return "", fmt.Errorf("error while reading request body %w", err)
 		}
 
 		return string(body), nil
@@ -45,7 +44,6 @@ type clientRequest struct {
 }
 
 func (c *clientRequest) Transport(endpoint *Endpoint) error {
-
 	dial := (&net.Dialer{
 		Timeout:   30 * time.Second,
 		KeepAlive: 30 * time.Second,
@@ -60,6 +58,7 @@ func (c *clientRequest) Transport(endpoint *Endpoint) error {
 		proxyfunc = c.proxyfunc
 	}
 
+	//nolint:gosec
 	transport := &http.Transport{
 		Proxy: proxyfunc,
 		TLSClientConfig: &tls.Config{
@@ -88,20 +87,21 @@ func (c *clientRequest) Transport(endpoint *Endpoint) error {
 func (c clientRequest) Post(client *Client, request *soap.SoapMessage) (string, error) {
 	httpClient := &http.Client{Transport: c.transport}
 
+	//nolint:noctx
 	req, err := http.NewRequest("POST", client.url, strings.NewReader(request.String()))
 	if err != nil {
-		return "", fmt.Errorf("impossible to create http request %s", err)
+		return "", fmt.Errorf("impossible to create http request %w", err)
 	}
 	req.Header.Set("Content-Type", soapXML+";charset=UTF-8")
 	req.SetBasicAuth(client.username, client.password)
 	resp, err := httpClient.Do(req)
 	if err != nil {
-		return "", fmt.Errorf("unknown error %s", err)
+		return "", fmt.Errorf("unknown error %w", err)
 	}
 
 	body, err := body(resp)
 	if err != nil {
-		return "", fmt.Errorf("http response error: %d - %s", resp.StatusCode, err.Error())
+		return "", fmt.Errorf("http response error: %d - %w", resp.StatusCode, err)
 	}
 
 	// if we have different 200 http status code
@@ -115,12 +115,14 @@ func (c clientRequest) Post(client *Client, request *soap.SoapMessage) (string,
 	return body, err
 }
 
+//NewClientWithDial NewClientWithDial
 func NewClientWithDial(dial func(network, addr string) (net.Conn, error)) *clientRequest {
 	return &clientRequest{
 		dial: dial,
 	}
 }
 
+//NewClientWithProxyFunc NewClientWithProxyFunc
 func NewClientWithProxyFunc(proxyfunc func(req *http.Request) (*url.URL, error)) *clientRequest {
 	return &clientRequest{
 		proxyfunc: proxyfunc,
diff --git a/http_test.go b/http_test.go
index 0d7f4e7..43ad263 100644
--- a/http_test.go
+++ b/http_test.go
@@ -3,9 +3,10 @@ package winrm
 import (
 	"net/http"
 
-	. "gopkg.in/check.v1"
 	"net"
 	"time"
+
+	. "gopkg.in/check.v1"
 )
 
 var response = `<s:Envelope xml:lang="en-US" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd">
@@ -43,7 +44,7 @@ stderr</rsp:OutputStreams>
 func (s *WinRMSuite) TestHttpRequest(c *C) {
 	ts, host, port, err := StartTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/soap+xml")
-		w.Write([]byte(response))
+		_, _ = w.Write([]byte(response))
 	}))
 	c.Assert(err, IsNil)
 	defer ts.Close()
@@ -55,7 +56,6 @@ func (s *WinRMSuite) TestHttpRequest(c *C) {
 	c.Assert(shell.id, Equals, "67A74734-DD32-4F10-89DE-49A060483810")
 }
 
-
 func (s *WinRMSuite) TestHttpViaCustomDialerRequest(c *C) {
 	normalDialer := (&net.Dialer{
 		Timeout:   30 * time.Second,
@@ -69,7 +69,7 @@ func (s *WinRMSuite) TestHttpViaCustomDialerRequest(c *C) {
 
 	ts, host, port, err := StartTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/soap+xml")
-		w.Write([]byte(response))
+		_, _ = w.Write([]byte(response))
 	}))
 	c.Assert(err, IsNil)
 	defer ts.Close()
diff --git a/kerberos.go b/kerberos.go
new file mode 100644
index 0000000..8d6607c
--- /dev/null
+++ b/kerberos.go
@@ -0,0 +1,128 @@
+package winrm
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/masterzen/winrm/soap"
+
+	"github.com/jcmturner/gokrb5/v8/client"
+	"github.com/jcmturner/gokrb5/v8/config"
+	"github.com/jcmturner/gokrb5/v8/credentials"
+	"github.com/jcmturner/gokrb5/v8/spnego"
+)
+
+// Settings holds all the information necessary to configure the provider
+type Settings struct {
+	WinRMUsername        string
+	WinRMPassword        string
+	WinRMHost            string
+	WinRMPort            int
+	WinRMProto           string
+	WinRMInsecure        bool
+	KrbRealm             string
+	KrbConfig            string
+	KrbSpn               string
+	KrbCCache            string
+	WinRMUseNTLM         bool
+	WinRMPassCredentials bool
+}
+
+type ClientKerberos struct {
+	clientRequest
+	Username  string
+	Password  string
+	Realm     string
+	Hostname  string
+	Port      int
+	Proto     string
+	SPN       string
+	KrbConf   string
+	KrbCCache string
+}
+
+func NewClientKerberos(settings *Settings) *ClientKerberos {
+	return &ClientKerberos{
+		Username:  settings.WinRMUsername,
+		Password:  settings.WinRMPassword,
+		Realm:     settings.KrbRealm,
+		Hostname:  settings.WinRMHost,
+		Port:      settings.WinRMPort,
+		Proto:     settings.WinRMProto,
+		KrbConf:   settings.KrbConfig,
+		KrbCCache: settings.KrbCCache,
+		SPN:       settings.KrbSpn,
+	}
+}
+
+func (c *ClientKerberos) Transport(endpoint *Endpoint) error {
+	return c.clientRequest.Transport(endpoint)
+}
+
+func (c *ClientKerberos) Post(clt *Client, request *soap.SoapMessage) (string, error) {
+	cfg, err := config.Load(c.KrbConf)
+	if err != nil {
+		return "", err
+	}
+
+	// setup the kerberos client
+	var kerberosClient *client.Client
+	if len(c.KrbCCache) > 0 {
+		b, err := ioutil.ReadFile(c.KrbCCache)
+		if err != nil {
+			return "", fmt.Errorf("unable to read ccache file %s: %w", c.KrbCCache, err)
+		}
+
+		cc := new(credentials.CCache)
+		err = cc.Unmarshal(b)
+		if err != nil {
+			return "", fmt.Errorf("unable to parse ccache file %s: %w", c.KrbCCache, err)
+		}
+		kerberosClient, err = client.NewFromCCache(cc, cfg, client.DisablePAFXFAST(true))
+		if err != nil {
+			return "", fmt.Errorf("unable to create kerberos client from ccache: %w", err)
+		}
+	} else {
+		kerberosClient = client.NewWithPassword(c.Username, c.Realm, c.Password, cfg,
+			client.DisablePAFXFAST(true), client.AssumePreAuthentication(true))
+	}
+
+	//create an http request
+	winrmURL := fmt.Sprintf("%s://%s:%d/wsman", c.Proto, c.Hostname, c.Port)
+	//nolint:noctx
+	winRMRequest, _ := http.NewRequest("POST", winrmURL, strings.NewReader(request.String()))
+	winRMRequest.Header.Add("Content-Type", "application/soap+xml;charset=UTF-8")
+
+	err = spnego.SetSPNEGOHeader(kerberosClient, winRMRequest, c.SPN)
+	if err != nil {
+		return "", fmt.Errorf("unable to set SPNego Header: %w", err)
+	}
+
+	httpClient := &http.Client{Transport: c.transport}
+
+	resp, err := httpClient.Do(winRMRequest)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		var bodyMsg string
+		respBody, err := io.ReadAll(resp.Body)
+		if err != nil {
+			bodyMsg = fmt.Sprintf("Error retrieving the response's body: %s", err)
+		} else {
+			bodyMsg = fmt.Sprintf("Response body:\n%s", string(respBody))
+		}
+		return "", fmt.Errorf("request returned: %d - %s. %s", resp.StatusCode, resp.Status, bodyMsg)
+	}
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	return string(body), err
+}
diff --git a/ntlm.go b/ntlm.go
index 08de8a5..219c54d 100644
--- a/ntlm.go
+++ b/ntlm.go
@@ -1,9 +1,12 @@
 package winrm
 
 import (
+	"net"
+	"net/http"
+	"net/url"
+
 	"github.com/Azure/go-ntlmssp"
 	"github.com/masterzen/winrm/soap"
-	"net"
 )
 
 // ClientNTLM provides a transport via NTLMv2
@@ -13,7 +16,9 @@ type ClientNTLM struct {
 
 // Transport creates the wrapped NTLM transport
 func (c *ClientNTLM) Transport(endpoint *Endpoint) error {
-	c.clientRequest.Transport(endpoint)
+	if err := c.clientRequest.Transport(endpoint); err != nil {
+		return err
+	}
 	c.clientRequest.transport = &ntlmssp.Negotiator{RoundTripper: c.clientRequest.transport}
 	return nil
 }
@@ -23,11 +28,20 @@ func (c ClientNTLM) Post(client *Client, request *soap.SoapMessage) (string, err
 	return c.clientRequest.Post(client, request)
 }
 
-
+//NewClientNTLMWithDial NewClientNTLMWithDial
 func NewClientNTLMWithDial(dial func(network, addr string) (net.Conn, error)) *ClientNTLM {
 	return &ClientNTLM{
 		clientRequest{
-			dial:dial,
+			dial: dial,
+		},
+	}
+}
+
+//NewClientNTLMWithProxyFunc NewClientNTLMWithProxyFunc
+func NewClientNTLMWithProxyFunc(proxyfunc func(req *http.Request) (*url.URL, error)) *ClientNTLM {
+	return &ClientNTLM{
+		clientRequest{
+			proxyfunc: proxyfunc,
 		},
 	}
-}
\ No newline at end of file
+}
diff --git a/ntlm_test.go b/ntlm_test.go
index 42baec1..48d3167 100644
--- a/ntlm_test.go
+++ b/ntlm_test.go
@@ -3,15 +3,16 @@ package winrm
 import (
 	"net/http"
 
-	. "gopkg.in/check.v1"
 	"net"
 	"time"
+
+	. "gopkg.in/check.v1"
 )
 
 func (s *WinRMSuite) TestHttpNTLMRequest(c *C) {
 	ts, host, port, err := StartTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/soap+xml")
-		w.Write([]byte(response))
+		_, _ = w.Write([]byte(response))
 	}))
 	c.Assert(err, IsNil)
 	defer ts.Close()
@@ -27,9 +28,7 @@ func (s *WinRMSuite) TestHttpNTLMRequest(c *C) {
 	c.Assert(shell.id, Equals, "67A74734-DD32-4F10-89DE-49A060483810")
 }
 
-
 func (s *WinRMSuite) TestHttpNTLMViaCustomDialerRequest(c *C) {
-
 	normalDialer := (&net.Dialer{
 		Timeout:   30 * time.Second,
 		KeepAlive: 30 * time.Second,
@@ -42,7 +41,7 @@ func (s *WinRMSuite) TestHttpNTLMViaCustomDialerRequest(c *C) {
 
 	ts, host, port, err := StartTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/soap+xml")
-		w.Write([]byte(response))
+		_, _ = w.Write([]byte(response))
 	}))
 	c.Assert(err, IsNil)
 	defer ts.Close()
@@ -56,4 +55,3 @@ func (s *WinRMSuite) TestHttpNTLMViaCustomDialerRequest(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(usedCustomDialer, Equals, true)
 }
-
diff --git a/powershell.go b/powershell.go
index 423f038..fd19639 100644
--- a/powershell.go
+++ b/powershell.go
@@ -2,22 +2,26 @@ package winrm
 
 import (
 	"encoding/base64"
-	"fmt"
+
+	"golang.org/x/text/encoding/unicode"
 )
 
 // Powershell wraps a PowerShell script
 // and prepares it for execution by the winrm client
 func Powershell(psCmd string) string {
-	// 2 byte chars to make PowerShell happy
-	wideCmd := ""
-	for _, b := range []byte(psCmd) {
-		wideCmd += string(b) + "\x00"
+	// Disable unnecessary progress bars which considered as stderr.
+	psCmd = "$ProgressPreference = 'SilentlyContinue';" + psCmd
+
+	// Encode string to UTF16-LE
+	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
+	encoded, err := encoder.String(psCmd)
+	if err != nil {
+		return ""
 	}
 
-	// Base64 encode the command
-	input := []uint8(wideCmd)
-	encodedCmd := base64.StdEncoding.EncodeToString(input)
+	// Finally make it base64 encoded which is required for powershell.
+	psCmd = base64.StdEncoding.EncodeToString([]byte(encoded))
 
-	// Create the powershell.exe command line to execute the script
-	return fmt.Sprintf("powershell.exe -EncodedCommand %s", encodedCmd)
+	// Specify powershell.exe to run encoded command
+	return "powershell.exe -EncodedCommand " + psCmd
 }
diff --git a/powershell_test.go b/powershell_test.go
index cb98b93..e387fe2 100644
--- a/powershell_test.go
+++ b/powershell_test.go
@@ -6,5 +6,5 @@ import (
 
 func (s *WinRMSuite) TestPowershell(c *C) {
 	psCmd := Powershell("dir")
-	c.Assert(psCmd, Equals, "powershell.exe -EncodedCommand ZABpAHIA")
+	c.Assert(psCmd, Equals, "powershell.exe -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwA7AGQAaQByAA==")
 }
diff --git a/request.go b/request.go
index 4c82733..e66bfd7 100644
--- a/request.go
+++ b/request.go
@@ -47,14 +47,14 @@ func NewOpenShellRequest(uri string, params *Parameters) *soap.SoapMessage {
 }
 
 // NewDeleteShellRequest ...
-func NewDeleteShellRequest(uri, shellId string, params *Parameters) *soap.SoapMessage {
+func NewDeleteShellRequest(uri, shellID string, params *Parameters) *soap.SoapMessage {
 	if params == nil {
 		params = DefaultParameters
 	}
 	message := soap.NewMessage()
 	defaultHeaders(message, uri, params).
 		Action("http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete").
-		ShellId(shellId).
+		ShellId(shellID).
 		ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").
 		Build()
 
@@ -64,7 +64,7 @@ func NewDeleteShellRequest(uri, shellId string, params *Parameters) *soap.SoapMe
 }
 
 // NewExecuteCommandRequest exec command on specific shellID
-func NewExecuteCommandRequest(uri, shellId, command string, arguments []string, params *Parameters) *soap.SoapMessage {
+func NewExecuteCommandRequest(uri, shellID, command string, arguments []string, params *Parameters) *soap.SoapMessage {
 	if params == nil {
 		params = DefaultParameters
 	}
@@ -72,7 +72,7 @@ func NewExecuteCommandRequest(uri, shellId, command string, arguments []string,
 	defaultHeaders(message, uri, params).
 		Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command").
 		ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").
-		ShellId(shellId).
+		ShellId(shellID).
 		AddOption(soap.NewHeaderOption("WINRS_CONSOLEMODE_STDIN", "TRUE")).
 		AddOption(soap.NewHeaderOption("WINRS_SKIP_CMD_SHELL", "FALSE")).
 		Build()
@@ -93,7 +93,8 @@ func NewExecuteCommandRequest(uri, shellId, command string, arguments []string,
 	return message
 }
 
-func NewGetOutputRequest(uri, shellId, commandId, streams string, params *Parameters) *soap.SoapMessage {
+//NewGetOutputRequest NewGetOutputRequest
+func NewGetOutputRequest(uri, shellID, commandID, streams string, params *Parameters) *soap.SoapMessage {
 	if params == nil {
 		params = DefaultParameters
 	}
@@ -101,18 +102,19 @@ func NewGetOutputRequest(uri, shellId, commandId, streams string, params *Parame
 	defaultHeaders(message, uri, params).
 		Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive").
 		ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").
-		ShellId(shellId).
+		ShellId(shellID).
 		Build()
 
 	receive := message.CreateBodyElement("Receive", soap.DOM_NS_WIN_SHELL)
 	desiredStreams := message.CreateElement(receive, "DesiredStream", soap.DOM_NS_WIN_SHELL)
-	desiredStreams.SetAttr("CommandId", commandId)
+	desiredStreams.SetAttr("CommandId", commandID)
 	desiredStreams.SetContent(streams)
 
 	return message
 }
 
-func NewSendInputRequest(uri, shellId, commandId string, input []byte, eof bool, params *Parameters) *soap.SoapMessage {
+//NewSendInputRequest NewSendInputRequest
+func NewSendInputRequest(uri, shellID, commandID string, input []byte, eof bool, params *Parameters) *soap.SoapMessage {
 	if params == nil {
 		params = DefaultParameters
 	}
@@ -121,7 +123,7 @@ func NewSendInputRequest(uri, shellId, commandId string, input []byte, eof bool,
 	defaultHeaders(message, uri, params).
 		Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send").
 		ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").
-		ShellId(shellId).
+		ShellId(shellID).
 		Build()
 
 	content := base64.StdEncoding.EncodeToString(input)
@@ -129,7 +131,7 @@ func NewSendInputRequest(uri, shellId, commandId string, input []byte, eof bool,
 	send := message.CreateBodyElement("Send", soap.DOM_NS_WIN_SHELL)
 	streams := message.CreateElement(send, "Stream", soap.DOM_NS_WIN_SHELL)
 	streams.SetAttr("Name", "stdin")
-	streams.SetAttr("CommandId", commandId)
+	streams.SetAttr("CommandId", commandID)
 	streams.SetContent(content)
 	if eof {
 		streams.SetAttr("End", "true")
@@ -137,7 +139,8 @@ func NewSendInputRequest(uri, shellId, commandId string, input []byte, eof bool,
 	return message
 }
 
-func NewSignalRequest(uri string, shellId string, commandId string, params *Parameters) *soap.SoapMessage {
+//NewSignalRequest NewSignalRequest
+func NewSignalRequest(uri string, shellID string, commandID string, params *Parameters) *soap.SoapMessage {
 	if params == nil {
 		params = DefaultParameters
 	}
@@ -146,11 +149,11 @@ func NewSignalRequest(uri string, shellId string, commandId string, params *Para
 	defaultHeaders(message, uri, params).
 		Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal").
 		ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").
-		ShellId(shellId).
+		ShellId(shellID).
 		Build()
 
 	signal := message.CreateBodyElement("Signal", soap.DOM_NS_WIN_SHELL)
-	signal.SetAttr("CommandId", commandId)
+	signal.SetAttr("CommandId", commandID)
 	code := message.CreateElement(signal, "Code", soap.DOM_NS_WIN_SHELL)
 	code.SetContent("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate")
 
diff --git a/response.go b/response.go
index b5ca8cc..bf877f7 100644
--- a/response.go
+++ b/response.go
@@ -44,6 +44,7 @@ func xPath(node tree.Node, xpath string) (tree.NodeSet, error) {
 	return nodes, nil
 }
 
+//ParseOpenShellResponse ParseOpenShellResponse
 func ParseOpenShellResponse(response string) (string, error) {
 	doc, err := xmltree.ParseXML(strings.NewReader(response))
 	if err != nil {
@@ -52,6 +53,7 @@ func ParseOpenShellResponse(response string) (string, error) {
 	return first(doc, "//w:Selector[@Name='ShellId']")
 }
 
+//ParseExecuteCommandResponse ParseExecuteCommandResponse
 func ParseExecuteCommandResponse(response string) (string, error) {
 	doc, err := xmltree.ParseXML(strings.NewReader(response))
 	if err != nil {
@@ -60,6 +62,7 @@ func ParseExecuteCommandResponse(response string) (string, error) {
 	return first(doc, "//rsp:CommandId")
 }
 
+//ParseSlurpOutputErrResponse ParseSlurpOutputErrResponse
 func ParseSlurpOutputErrResponse(response string, stdout, stderr io.Writer) (bool, int, error) {
 	var (
 		finished bool
@@ -94,6 +97,7 @@ func ParseSlurpOutputErrResponse(response string, stdout, stderr io.Writer) (boo
 	return finished, exitCode, err
 }
 
+//ParseSlurpOutputResponse ParseSlurpOutputResponse
 func ParseSlurpOutputResponse(response string, stream io.Writer, streamType string) (bool, int, error) {
 	var (
 		finished bool
@@ -105,7 +109,7 @@ func ParseSlurpOutputResponse(response string, stream io.Writer, streamType stri
 	nodes, _ := xPath(doc, fmt.Sprintf("//rsp:Stream[@Name='%s']", streamType))
 	for _, node := range nodes {
 		content, _ := base64.StdEncoding.DecodeString(node.ResValue())
-		stream.Write(content)
+		_, _ = stream.Write(content)
 	}
 
 	ended, _ := any(doc, "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']")
diff --git a/response_test.go b/response_test.go
index f9e3890..692c0ce 100644
--- a/response_test.go
+++ b/response_test.go
@@ -8,24 +8,23 @@ import (
 
 func (s *WinRMSuite) TestOpenShellResponse(c *C) {
 	response := createShellResponse
-	shellId, err := ParseOpenShellResponse(response)
+	shellID, err := ParseOpenShellResponse(response)
 	if err != nil {
 		c.Fatalf("response didn't parse: %s", err)
 	}
 
-	c.Assert("67A74734-DD32-4F10-89DE-49A060483810", Equals, shellId)
+	c.Assert("67A74734-DD32-4F10-89DE-49A060483810", Equals, shellID)
 }
 
 func (s *WinRMSuite) TestExecuteCommandResponse(c *C) {
 	response := executeCommandResponse
 
-	commandId, err := ParseExecuteCommandResponse(response)
+	commandID, err := ParseExecuteCommandResponse(response)
 	if err != nil {
 		c.Fatalf("response didn't parse: %s", err)
 	}
 
-	c.Assert("1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4", Equals, commandId)
-
+	c.Assert("1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4", Equals, commandID)
 }
 
 func (s *WinRMSuite) TestSlurpOutputResponse(c *C) {
diff --git a/shell.go b/shell.go
index 0f33348..79c6bd2 100644
--- a/shell.go
+++ b/shell.go
@@ -1,5 +1,7 @@
 package winrm
 
+import "context"
+
 // Shell is the local view of a WinRM Shell of a given Client
 type Shell struct {
 	client *Client
@@ -7,7 +9,14 @@ type Shell struct {
 }
 
 // Execute command on the given Shell, returning either an error or a Command
+//
+// Deprecated: user ExecuteWithContext
 func (s *Shell) Execute(command string, arguments ...string) (*Command, error) {
+	return s.ExecuteWithContext(context.Background(), command, arguments...)
+}
+
+// ExecuteWithContext command on the given Shell, returning either an error or a Command
+func (s *Shell) ExecuteWithContext(ctx context.Context, command string, arguments ...string) (*Command, error) {
 	request := NewExecuteCommandRequest(s.client.url, s.id, command, arguments, &s.client.Parameters)
 	defer request.Free()
 
@@ -21,7 +30,7 @@ func (s *Shell) Execute(command string, arguments ...string) (*Command, error) {
 		return nil, err
 	}
 
-	cmd := newCommand(s, commandID)
+	cmd := newCommand(ctx, s, commandID)
 
 	return cmd, nil
 }
diff --git a/shell_test.go b/shell_test.go
index e4da1de..eb988a4 100644
--- a/shell_test.go
+++ b/shell_test.go
@@ -18,12 +18,11 @@ func (s *WinRMSuite) TestShellExecuteResponse(c *C) {
 			c.Assert(message.String(), Contains, "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")
 			first = false
 			return executeCommandResponse, nil
-		} else {
-			c.Assert(message.String(), Contains, "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive")
-			return outputResponse, nil
 		}
+		c.Assert(message.String(), Contains, "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive")
+		return outputResponse, nil
 	}
-	client.http = r
+	client.http = &r
 	command, _ := shell.Execute("ipconfig /all")
 	c.Assert(command.id, Equals, "1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4")
 }
@@ -39,7 +38,7 @@ func (s *WinRMSuite) TestShellCloseResponse(c *C) {
 		c.Assert(message.String(), Contains, "http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete")
 		return "", nil
 	}
-	client.http = r
+	client.http = &r
 
 	shell.Close()
 }
diff --git a/soap/header.go b/soap/header.go
index 6aeaebe..d9f5dd3 100644
--- a/soap/header.go
+++ b/soap/header.go
@@ -23,7 +23,7 @@ type SoapHeader struct {
 	locale          string
 	id              string
 	action          string
-	shellId         string
+	shellID         string
 	resourceURI     string
 	options         []HeaderOption
 	message         *SoapMessage
@@ -44,135 +44,137 @@ type HeaderBuilder interface {
 	Build(*SoapMessage) *SoapMessage
 }
 
-func (self *SoapHeader) To(uri string) *SoapHeader {
-	self.to = uri
-	return self
+func (sh *SoapHeader) To(uri string) *SoapHeader {
+	sh.to = uri
+	return sh
 }
 
-func (self *SoapHeader) ReplyTo(uri string) *SoapHeader {
-	self.replyTo = uri
-	return self
+func (sh *SoapHeader) ReplyTo(uri string) *SoapHeader {
+	sh.replyTo = uri
+	return sh
 }
 
-func (self *SoapHeader) MaxEnvelopeSize(size int) *SoapHeader {
-	self.maxEnvelopeSize = strconv.Itoa(size)
-	return self
+func (sh *SoapHeader) MaxEnvelopeSize(size int) *SoapHeader {
+	sh.maxEnvelopeSize = strconv.Itoa(size)
+	return sh
 }
 
-func (self *SoapHeader) Timeout(timeout string) *SoapHeader {
-	self.timeout = timeout
-	return self
+func (sh *SoapHeader) Timeout(timeout string) *SoapHeader {
+	sh.timeout = timeout
+	return sh
 }
 
-func (self *SoapHeader) Id(id string) *SoapHeader {
-	self.id = id
-	return self
+//nolint:stylecheck // Should be ShellID, but we stay compatible
+func (sh *SoapHeader) Id(id string) *SoapHeader {
+	sh.id = id
+	return sh
 }
 
-func (self *SoapHeader) Action(action string) *SoapHeader {
-	self.action = action
-	return self
+func (sh *SoapHeader) Action(action string) *SoapHeader {
+	sh.action = action
+	return sh
 }
 
-func (self *SoapHeader) Locale(locale string) *SoapHeader {
-	self.locale = locale
-	return self
+func (sh *SoapHeader) Locale(locale string) *SoapHeader {
+	sh.locale = locale
+	return sh
 }
 
-func (self *SoapHeader) ShellId(shellId string) *SoapHeader {
-	self.shellId = shellId
-	return self
+//nolint:stylecheck // Should be ShellID, but we stay compatible
+func (sh *SoapHeader) ShellId(shellId string) *SoapHeader {
+	sh.shellID = shellId
+	return sh
 }
 
-func (self *SoapHeader) ResourceURI(resourceURI string) *SoapHeader {
-	self.resourceURI = resourceURI
-	return self
+func (sh *SoapHeader) ResourceURI(resourceURI string) *SoapHeader {
+	sh.resourceURI = resourceURI
+	return sh
 }
 
-func (self *SoapHeader) AddOption(option *HeaderOption) *SoapHeader {
-	self.options = append(self.options, *option)
-	return self
+func (sh *SoapHeader) AddOption(option *HeaderOption) *SoapHeader {
+	sh.options = append(sh.options, *option)
+	return sh
 }
 
-func (self *SoapHeader) Options(options []HeaderOption) *SoapHeader {
-	self.options = options
-	return self
+func (sh *SoapHeader) Options(options []HeaderOption) *SoapHeader {
+	sh.options = options
+	return sh
 }
 
-func (self *SoapHeader) Build() *SoapMessage {
-	header := self.createElement(self.message.envelope, "Header", DOM_NS_SOAP_ENV)
+func (sh *SoapHeader) Build() *SoapMessage {
+	header := sh.createElement(sh.message.envelope, "Header", DOM_NS_SOAP_ENV)
 
-	if self.to != "" {
-		to := self.createElement(header, "To", DOM_NS_ADDRESSING)
-		to.SetContent(self.to)
+	if sh.to != "" {
+		to := sh.createElement(header, "To", DOM_NS_ADDRESSING)
+		to.SetContent(sh.to)
 	}
 
-	if self.replyTo != "" {
-		replyTo := self.createElement(header, "ReplyTo", DOM_NS_ADDRESSING)
-		a := self.createMUElement(replyTo, "Address", DOM_NS_ADDRESSING, true)
-		a.SetContent(self.replyTo)
+	if sh.replyTo != "" {
+		replyTo := sh.createElement(header, "ReplyTo", DOM_NS_ADDRESSING)
+		a := sh.createMUElement(replyTo, "Address", DOM_NS_ADDRESSING, true)
+		a.SetContent(sh.replyTo)
 	}
 
-	if self.maxEnvelopeSize != "" {
-		envelope := self.createMUElement(header, "MaxEnvelopeSize", DOM_NS_WSMAN_DMTF, true)
-		envelope.SetContent(self.maxEnvelopeSize)
+	if sh.maxEnvelopeSize != "" {
+		envelope := sh.createMUElement(header, "MaxEnvelopeSize", DOM_NS_WSMAN_DMTF, true)
+		envelope.SetContent(sh.maxEnvelopeSize)
 	}
 
-	if self.timeout != "" {
-		timeout := self.createElement(header, "OperationTimeout", DOM_NS_WSMAN_DMTF)
-		timeout.SetContent(self.timeout)
+	if sh.timeout != "" {
+		timeout := sh.createElement(header, "OperationTimeout", DOM_NS_WSMAN_DMTF)
+		timeout.SetContent(sh.timeout)
 	}
 
-	if self.id != "" {
-		id := self.createElement(header, "MessageID", DOM_NS_ADDRESSING)
-		id.SetContent(self.id)
+	if sh.id != "" {
+		id := sh.createElement(header, "MessageID", DOM_NS_ADDRESSING)
+		id.SetContent(sh.id)
 	}
 
-	if self.locale != "" {
-		locale := self.createMUElement(header, "Locale", DOM_NS_WSMAN_DMTF, false)
-		locale.SetAttr("xml:lang", self.locale)
-		datalocale := self.createMUElement(header, "DataLocale", DOM_NS_WSMAN_MSFT, false)
-		datalocale.SetAttr("xml:lang", self.locale)
+	if sh.locale != "" {
+		locale := sh.createMUElement(header, "Locale", DOM_NS_WSMAN_DMTF, false)
+		locale.SetAttr("xml:lang", sh.locale)
+		datalocale := sh.createMUElement(header, "DataLocale", DOM_NS_WSMAN_MSFT, false)
+		datalocale.SetAttr("xml:lang", sh.locale)
 	}
 
-	if self.action != "" {
-		action := self.createMUElement(header, "Action", DOM_NS_ADDRESSING, true)
-		action.SetContent(self.action)
+	if sh.action != "" {
+		action := sh.createMUElement(header, "Action", DOM_NS_ADDRESSING, true)
+		action.SetContent(sh.action)
 	}
 
-	if self.shellId != "" {
-		selectorSet := self.createElement(header, "SelectorSet", DOM_NS_WSMAN_DMTF)
-		selector := self.createElement(selectorSet, "Selector", DOM_NS_WSMAN_DMTF)
+	if sh.shellID != "" {
+		selectorSet := sh.createElement(header, "SelectorSet", DOM_NS_WSMAN_DMTF)
+		selector := sh.createElement(selectorSet, "Selector", DOM_NS_WSMAN_DMTF)
 		selector.SetAttr("Name", "ShellId")
-		selector.SetContent(self.shellId)
+		selector.SetContent(sh.shellID)
 	}
 
-	if self.resourceURI != "" {
-		resource := self.createMUElement(header, "ResourceURI", DOM_NS_WSMAN_DMTF, true)
-		resource.SetContent(self.resourceURI)
+	if sh.resourceURI != "" {
+		resource := sh.createMUElement(header, "ResourceURI", DOM_NS_WSMAN_DMTF, true)
+		resource.SetContent(sh.resourceURI)
 	}
 
-	if len(self.options) > 0 {
-		set := self.createElement(header, "OptionSet", DOM_NS_WSMAN_DMTF)
-		for _, option := range self.options {
-			e := self.createElement(set, "Option", DOM_NS_WSMAN_DMTF)
+	if len(sh.options) > 0 {
+		set := sh.createElement(header, "OptionSet", DOM_NS_WSMAN_DMTF)
+		for _, option := range sh.options {
+			e := sh.createElement(set, "Option", DOM_NS_WSMAN_DMTF)
 			e.SetAttr("Name", option.key)
 			e.SetContent(option.value)
 		}
 	}
 
-	return self.message
+	return sh.message
 }
 
-func (self *SoapHeader) createElement(parent *dom.Element, name string, ns dom.Namespace) (element *dom.Element) {
+func (sh *SoapHeader) createElement(parent *dom.Element, name string, ns dom.Namespace) (element *dom.Element) {
 	element = dom.CreateElement(name)
 	parent.AddChild(element)
 	ns.SetTo(element)
 	return
 }
 
-func (self *SoapHeader) createMUElement(parent *dom.Element, name string, ns dom.Namespace, mustUnderstand bool) (element *dom.Element) {
-	element = self.createElement(parent, name, ns)
+func (sh *SoapHeader) createMUElement(parent *dom.Element, name string, ns dom.Namespace, mustUnderstand bool) (element *dom.Element) {
+	element = sh.createElement(parent, name, ns)
 	value := "false"
 	if mustUnderstand {
 		value = "true"
diff --git a/soap/namespaces.go b/soap/namespaces.go
index e7f5e22..5c541f1 100644
--- a/soap/namespaces.go
+++ b/soap/namespaces.go
@@ -6,6 +6,7 @@ import (
 )
 
 // Namespaces
+//nolint:stylecheck // we keep the ALL_CAPS names
 const (
 	NS_SOAP_ENV    = "http://www.w3.org/2003/05/soap-envelope"
 	NS_ADDRESSING  = "http://schemas.xmlsoap.org/ws/2004/08/addressing"
@@ -20,6 +21,7 @@ const (
 )
 
 // Namespace Prefixes
+//nolint:stylecheck // we keep the ALL_CAPS names
 const (
 	NSP_SOAP_ENV    = "env"
 	NSP_ADDRESSING  = "a"
@@ -34,17 +36,18 @@ const (
 )
 
 // DOM Namespaces
+//nolint:stylecheck
 var (
-	DOM_NS_SOAP_ENV    = dom.Namespace{"env", "http://www.w3.org/2003/05/soap-envelope"}
-	DOM_NS_ADDRESSING  = dom.Namespace{"a", "http://schemas.xmlsoap.org/ws/2004/08/addressing"}
-	DOM_NS_CIMBINDING  = dom.Namespace{"b", "http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd"}
-	DOM_NS_ENUM        = dom.Namespace{"n", "http://schemas.xmlsoap.org/ws/2004/09/enumeration"}
-	DOM_NS_TRANSFER    = dom.Namespace{"x", "http://schemas.xmlsoap.org/ws/2004/09/transfer"}
-	DOM_NS_WSMAN_DMTF  = dom.Namespace{"w", "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"}
-	DOM_NS_WSMAN_MSFT  = dom.Namespace{"p", "http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"}
-	DOM_NS_SCHEMA_INST = dom.Namespace{"xsi", "http://www.w3.org/2001/XMLSchema-instance"}
-	DOM_NS_WIN_SHELL   = dom.Namespace{"rsp", "http://schemas.microsoft.com/wbem/wsman/1/windows/shell"}
-	DOM_NS_WSMAN_FAULT = dom.Namespace{"f", "http://schemas.microsoft.com/wbem/wsman/1/wsmanfault"}
+	DOM_NS_SOAP_ENV    = dom.Namespace{Prefix: "env", Uri: "http://www.w3.org/2003/05/soap-envelope"}
+	DOM_NS_ADDRESSING  = dom.Namespace{Prefix: "a", Uri: "http://schemas.xmlsoap.org/ws/2004/08/addressing"}
+	DOM_NS_CIMBINDING  = dom.Namespace{Prefix: "b", Uri: "http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd"}
+	DOM_NS_ENUM        = dom.Namespace{Prefix: "n", Uri: "http://schemas.xmlsoap.org/ws/2004/09/enumeration"}
+	DOM_NS_TRANSFER    = dom.Namespace{Prefix: "x", Uri: "http://schemas.xmlsoap.org/ws/2004/09/transfer"}
+	DOM_NS_WSMAN_DMTF  = dom.Namespace{Prefix: "w", Uri: "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"}
+	DOM_NS_WSMAN_MSFT  = dom.Namespace{Prefix: "p", Uri: "http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"}
+	DOM_NS_SCHEMA_INST = dom.Namespace{Prefix: "xsi", Uri: "http://www.w3.org/2001/XMLSchema-instance"}
+	DOM_NS_WIN_SHELL   = dom.Namespace{Prefix: "rsp", Uri: "http://schemas.microsoft.com/wbem/wsman/1/windows/shell"}
+	DOM_NS_WSMAN_FAULT = dom.Namespace{Prefix: "f", Uri: "http://schemas.microsoft.com/wbem/wsman/1/wsmanfault"}
 )
 
 var MostUsed = [...]dom.Namespace{

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/masterzen/winrm/kerberos.go

No differences were encountered in the control files

More details

Full run details