Command-line tool and library for Windows remote command execution in Go

WinRM for Go

Note: if you're looking for the winrm command-line tool, this has been splitted from this project and is available at winrm-cli

This is a Go library to execute remote commands on Windows machines through the use of WinRM/WinRS.

Note: this library doesn't support domain users (it doesn't support GSSAPI nor Kerberos). It's primary target is to execute remote commands on EC2 windows machines.

Build Status Coverage Status

Contact

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).

Note: This library only supports Golang 1.7+

Preparing the remote Windows machine for Basic authentication

This project supports only basic authentication for local accounts (domain users are not supported). The remote windows system must be prepared for winrm:

For a PowerShell script to do what is described below in one go, check Richard Downer's blog

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/Auth '@{Basic="true"}'
	winrm set winrm/config/service '@{AllowUnencrypted="true"}'
	winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'

N.B.: The Windows Firewall needs to be running to run this command. See Microsoft Knowledge Base article #2004640.

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 if you need to run commands that uses more than 150MB of memory.

For more information on WinRM, please refer to the online documentation at Microsoft's DevCenter.

Building the winrm go and executable

You can build winrm from source:

git clone https://github.com/masterzen/winrm
cd winrm
make

Note: this winrm code doesn't depend anymore on Gokogiri which means it is now in pure Go.

Note: you need go 1.5+. Please check your installation with

go version

Command-line usage

For command-line usage check the winrm-cli project

Library Usage

Warning the API might be subject to change.

For the fast version (this doesn't allow to send input to the command) and it's using HTTP as the transport:

package main

import (
	"github.com/masterzen/winrm"
	"os"
)

endpoint := winrm.NewEndpoint(host, 5986, false, false, nil, nil, nil, 0)
client, err := winrm.NewClient(endpoint, "Administrator", "secret")
if err != nil {
	panic(err)
}
client.Run("ipconfig /all", os.Stdout, os.Stderr)

or

package main
import (
  "github.com/masterzen/winrm"
  "fmt"
  "os"
)

endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)
client, err := winrm.NewClient(endpoint,"Administrator", "secret")
if err != nil {
	panic(err)
}

_, err := client.RunWithInput("ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
	panic(err)
}

By passing a TransportDecorator in the Parameters struct it is possible to use different Transports (e.g. NTLM)

package main
import (
  "github.com/masterzen/winrm"
  "fmt"
  "os"
)

endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)

params := DefaultParameters
params.TransportDecorator = func() Transporter { return &ClientNTLM{} }

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)
}

By passing a Dial in the Parameters struct it is possible to use different dialer (e.g. tunnel through SSH)

package main
     
 import (
    "github.com/masterzen/winrm"
    "golang.org/x/crypto/ssh"
    "os"
 )
 
 func main() {
 
    sshClient, err := ssh.Dial("tcp","localhost:22", &ssh.ClientConfig{
        User:"ubuntu",
        Auth: []ssh.AuthMethod{ssh.Password("ubuntu")},
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    })
 
    endpoint := winrm.NewEndpoint("other-host", 5985, false, false, nil, nil, nil, 0)
 
    params := winrm.DefaultParameters
    params.Dial = sshClient.Dial
 
    client, err := winrm.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)
    }
 }

For a more complex example, it is possible to call the various functions directly:

package main

import (
  "github.com/masterzen/winrm"
  "fmt"
  "bytes"
  "os"
)

stdin := bytes.NewBufferString("ipconfig /all")
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.Execute("cmd.exe")
if err != nil {
  panic(err)
}

go io.Copy(cmd.Stdin, stdin)
go io.Copy(os.Stdout, cmd.Stdout)
go io.Copy(os.Stderr, cmd.Stderr)

cmd.Wait()
shell.Close()

For using HTTPS authentication with x 509 cert without checking the CA

package main

import (
    "github.com/masterzen/winrm"
    "io/ioutil"
    "log"
    "os"
)

func main() {
    clientCert, err := ioutil.ReadFile("/home/example/winrm_client_cert.pem")
    if err != nil {
        log.Fatalf("failed to read client certificate: %q", err)
    }

    clientKey, err := ioutil.ReadFile("/home/example/winrm_client_key.pem")
    if err != nil {
        log.Fatalf("failed to read client key: %q", err)
    }

    winrm.DefaultParameters.TransportDecorator = func() winrm.Transporter {
        // winrm https module
        return &winrm.ClientAuthRequest{}
    }

    endpoint := winrm.NewEndpoint(
        "192.168.100.2", // host to connect to
        5986,            // winrm port
        true,            // use TLS
        true,            // Allow insecure connection
        nil,             // CA certificate
        clientCert,      // Client Certificate
        clientKey,       // Client Key
        0,               // Timeout
    )
    client, err := winrm.NewClient(endpoint, "Administrator", "")
    if err != nil {
        log.Fatalf("failed to create client: %q", err)
    }
    _, err = client.Run("whoami", os.Stdout, os.Stderr)
    if err != nil {
        log.Fatalf("failed to run command: %q", err)
    }
}

Developing on WinRM

If you wish to work on winrm itself, you'll first need Go installed (version 1.5+ is required). Make sure you have Go properly installed, including setting up your GOPATH.

For some additional dependencies, Go needs Mercurial and Bazaar to be installed. Winrm itself doesn't require these, but a dependency of a dependency does.

Next, clone this repository into $GOPATH/src/github.com/masterzen/winrm and then just type make.

You can run tests by typing make test.

If you make any changes to the code, run make format in order to automatically format the code according to Go standards.

When new dependencies are added to winrm you can use make updatedeps to get the latest and subsequently use make to compile.

Owner
Brice Figureau
Code craftsman, Sysadmin, DevOps in a man :) Working with @daysofwonder
Brice Figureau
Comments
  • Fix winrm handling of early connection termination or timeout

    Fix winrm handling of early connection termination or timeout

    PR https://github.com/masterzen/winrm/pull/25 introduced a bug where winrm will exit with error code 1 even when err is empty and a lot of error codes from further on the line are omitted (shell.Close()). This is affecting remote commands as net stop winrm or Stop-Service winrm needed when rebooting a host to ensure winrm does not reconnect too soon on a subsecuent command while rebooting.

    This PR is not complete yet, the intention is to get some help on this code as i add to it, since go is not my primary lang.

    Reference: https://github.com/mitchellh/packer/pull/2243 https://github.com/packer-community/packer-windows-plugins/issues/54

  • CertAuth support

    CertAuth support

    • Added CertAuth support
    • Added tests for CertAuth and some tests for BasicAuth
    • Modified README.md with an example using CertAuth and how to use it on the command-line

    Note: When the SSL certificate is generated for winRM the UPN extension must not be marked as critical because it can not be validated by GO.

  • Remove LGPL dependencies

    Remove LGPL dependencies

    Replaces the LGPL dependency masterzen/xmlpath with ChrisTrenkamp/goxpath, which is under the MIT license.

    Underlying API's should be the same, and there aren't any expected breaking changes to any public functions exposed via this WinRM library.

    This change is solely to modify dependencies away from an LGPL license.

    $ make test
    ==> Installing dependencies
    ==> Testing...
    go test ./...
    ok      github.com/grubernaut/winrm     5.050s
    ok      github.com/grubernaut/winrm/soap        0.003s
    
  • "http error: 500" on latest master

    As part of our deployment process, we build and deploy the latest winrm version. We've noticed that master now seems to error with "http error: 500" when we're doing long running commands (usually after about 70 seconds or so).

    This previously didn't happen, and it doesn't happen on versions of WinRM that only have these options (not sure which commit this was built from):

    Usage of winrm:
      -hostname="localhost": winrm host
      -password="vagrant": winrm admin password
      -port=5985: winrm port
      -username="vagrant": winrm admin username
    

    I'm sure we've been running newer versions than this, but this was the version of WinRM I could find since I needed to restore service quickly.

  • Can specify alternate port

    Can specify alternate port

    This allows us to

    • Use port forwarding to communicate with multiple Windows VMs
    • Develop a testing framework similar to net/httptest

    I avoided changing the signature of winrm.NewParameters but I think you should consider it if you are willing to break any other existing users of that function. :)

  • Fix #49 split the library and the command-line tool

    Fix #49 split the library and the command-line tool

    This is a breaking change. The tool has been moved to the winrm-cli project (https://github.com/masterzen/winrm-cli). This commit removes the tool, and moves back all the library code to the github.com/masterzen/winrm package.

    Dependent libraries or tool might need to be modified to take the new package path into account.

  • Fix #48 - OperationTimeout aborts the current command

    Fix #48 - OperationTimeout aborts the current command

    Since PR #25 winrm is now unfortunately reporting up OperationTimeout errors when the command doesn't output anything, and aborting processing.

    This changeset is PR #26 by @pecigonzalo which I rebased in order to be able to merge it on top of current master.

  • Fix race condition between command.Wait() and fetchOutput

    Fix race condition between command.Wait() and fetchOutput

    I've observed the exitcode sometimes coming back as 0 if it should have been non-zero. It turns out that both command.Wait() and fetchOutput(* command) were racing to receive command.done. The break statements were only breaking out of the select, not the for loop, so the exit code kept being updated forever.

    Apparently at least one implementation of WSMAN returns ExitCode 0 on subsequent Receive messages, even though it was non-zero on the first Receive response with CommandState/@State=done.

    I changed fetchOutput/Wait to the canonical 'done channel' pattern to terminate on the first encounter of a CommandState/@State=done fragment.

    Not sure why fetchOutput was trying to receive command.done, since it's only sent by the very same function?

  • CommandWriter Close() infinite loop

    CommandWriter Close() infinite loop

    https://github.com/masterzen/winrm/blob/master/command.go#L227-L230

    // Close method wrapper
    // commandWriter implements io.Closer interface
    func (w *commandWriter) Close() error {
    	w.eof = true
    	return w.Close()
    }
    

    I'm not a go-wizard, but doesn't this create an infinite loop and cause a stack overflow panic? I got this:

    runtime: goroutine stack exceeds 1000000000-byte limit
    fatal error: stack overflow
    
    runtime stack:
    runtime.throw(0x1611bb0, 0xe)
    	/usr/local/Cellar/go/1.13.7/libexec/src/runtime/panic.go:774 +0x72
    runtime.newstack()
    	/usr/local/Cellar/go/1.13.7/libexec/src/runtime/stack.go:1046 +0x6e9
    runtime.morestack()
    	/usr/local/Cellar/go/1.13.7/libexec/src/runtime/asm_amd64.s:449 +0x8f
    
    goroutine 173 [running]:
    github.com/masterzen/winrm.(*commandWriter).Close(0xc00023ecc0, 0x0, 0x0)
    	/Users/kimmo/Projects/go/pkg/mod/github.com/masterzen/[email protected]/command.go:227 +0x52 fp=0xc02085a360 sp=0xc02085a358 pc=0x1404082
    github.com/masterzen/winrm.(*commandWriter).Close(0xc00023ecc0, 0x0, 0x0)
    	/Users/kimmo/Projects/go/pkg/mod/github.com/masterzen/[email protected]/command.go:229 +0x2f fp=0xc02085a388 sp=0xc02085a360 pc=0x140405f
    github.com/masterzen/winrm.(*commandWriter).Close(0xc00023ecc0, 0x0, 0x0)
    	/Users/kimmo/Projects/go/pkg/mod/github.com/masterzen/[email protected]/command.go:229 +0x2f fp=0xc02085a3b0 sp=0xc02085a388 pc=0x140405f
    github.com/masterzen/winrm.(*commandWriter).Close(0xc00023ecc0, 0x0, 0x0)
    	/Users/kimmo/Projects/go/pkg/mod/github.com/masterzen/[email protected]/command.go:229 +0x2f fp=0xc02085a3d8 sp=0xc02085a3b0 pc=0x140405f
    github.com/masterzen/winrm.(*commandWriter).Close(0xc00023ecc0, 0x0, 0x0)
    
    ...and so on
    
  • Vendoring this library causes build failures

    Vendoring this library causes build failures

    Hi,

    We have a project using this library which won't build if we vendor this library (using dep).

    vendor/github.com/masterzen/winrm/auth.go:28:18: cannot use "[snip]/vendor/github.com/masterzen/azure-sdk-for-go/core/tls".Config literal (type *"[snip]/vendor/github.com/masterzen/azure-sdk-for-go/core/tls".Config) as type *"[snip]/vendor/github.com/MSOpenTech/azure-sdk-for-go/core/tls".Config in field value
    

    Without vendoring, the everything builds fine.

    I can see that you're using your own fork of azure-sdk-for-go, but dep seems to be pulling in the MSOpenTech one as well for some reason - I'm not entirely sure why it's doing this as all we're directly importing into our project is this winrm library.

    Output from dep status, relevant packages only:

    PROJECT                                             CONSTRAINT     VERSION        REVISION  LATEST       PKGS USED
    github.com/Azure/go-ntlmssp                         branch master  branch master  c92175d   c92175d      1   
    github.com/MSOpenTech/azure-sdk-for-go              ^1.2.0         v1.2           0fbd371   v1.2         2   
    github.com/masterzen/azure-sdk-for-go               ^1.2.0         v1.2           0fbd371   v1.2         2   
    github.com/masterzen/simplexml                      branch master  branch master  4572e39   4572e39      1   
    github.com/masterzen/winrm                          branch master  branch master  a2df6b1   a2df6b1      2   
    github.com/nu7hatch/gouuid                          branch master  branch master  179d4d0   179d4d0      1   
    

    Some of the files in your azure-sdk-for-go fork appear to be referencing the MSOpenTech files for imports, so I think it might be this causing the problem.

  • winrm building error (empty Private Key)

    winrm building error (empty Private Key)

    I got the following error while building winrm. Any ideas what I did wrong? I`ve already done:

    • export $GOPATH=/tmp/gotmp
    • installed GO with: apt-get install golang*
    • exported http_proxy and https_proxy for downloading the src.
    • OS: Linux Mint 17.3

    Detailed Error Message

    mintlab/winrm # make
    ==> Installing dependencies
    ==> Building
    # github.com/masterzen/winrm/winrm
    /tmp/gotmp/src/github.com/masterzen/winrm/winrm/certgen.go:191: t.Public undefined (type *rsa.PrivateKey has no field or method Public)
    /tmp/gotmp/src/github.com/masterzen/winrm/winrm/certgen.go:193: t.Public undefined (type *ecdsa.PrivateKey has no field or method Public)
    make: *** [all] Error 2
    
  • No error if quota is exceeded

    No error if quota is exceeded

    Found that if I'm using quite a number of parallel operations - winrm client starts to just skip the executions and there is no way to get that the operation failed...

    The error message is:

    The WS-Management service cannot process the request. This user is allowed a maximum number of 1500 concurrent operations, which has been exceeded. Close existing operations for this user, or raise the quota for this user.
    

    Workaround: winrm set winrm/config/service @{MaxConcurrentOperationsPerUser="4294967295"} helps, but overall it will be great if winrm client will be able to figure out the error from the response.

    Attached the tcpdump packet with envelope: tcpdump_packet.txt

  • Direct command, doesn't use pipes

    Direct command, doesn't use pipes

    New directcommand.go with object that sends commands directly to the remote. Noprofile and consolemode_stdin off, so remote powershell doesn't think it's interactive

    This can be used to run a shell on the remote side, and then repeatedly send commands to that shell and receive their output.

    I use this (in https://github.com/willemm/cluster-api-provider-scvmm) to open a powershell remotely and then run a number of cmdlets on it, which is much cleaner and faster than sending each statement as a commandline-option to powershell.exe

  • Support canceling remote processes via `context.Context`

    Support canceling remote processes via `context.Context`

    Hi Brice, I would like to have public access to the err member of the Command struct. This would make it possible to reimplement the Client.Run* methods out of package in a way that supports canceling via context.Context. Would you consider a PR to this effect?

  • panic: close of closed channel

    panic: close of closed channel

    goroutine 1709 [running]:
    github.com/masterzen/winrm.(*Command).Close(0xc00017e700, 0x0, 0x0)
        github.com/masterzen/[email protected]/command.go:114
    

    https://github.com/masterzen/winrm/blob/master/command.go#L111-L115

    	select { // close cancel channel if it's still open
    	case <-c.cancel:
    	default:
    		close(c.cancel)
    	}
    
  • Allow callers to control powershell.exe parameters

    Allow callers to control powershell.exe parameters

    Sometimes we need to call powershell.exe with switches such as -NoProfile. This PR allows us to make such calls but also maintains backwards compatibility.

Related tags
Trace Go program execution with uprobes and eBPF
Trace Go program execution with uprobes and eBPF

Weaver PLEASE READ! - I am currently refactoring Weaver to use libbpf instead of bcc which would include various other major improvements. If you're c

Dec 28, 2022
A simple Go library to toggle on and off pac(proxy auto configuration) for Windows, MacOS and Linux

pac pac is a simple Go library to toggle on and off pac(proxy auto configuration

Dec 26, 2021
ZheTian Powerful remote load and execute ShellCode tool
 ZheTian Powerful remote load and execute ShellCode tool

ZheTian ZheTian Powerful remote load and execute ShellCode tool 免杀shellcode加载框架 命令详解 -u:从远程服务器加载base64混淆后的字节码。 -r:从本地文件内读。 -s:读取无修改的原始文件,只能从本地加载 -o:参数

Jan 9, 2023
Hostkeydns - Library for verifying remote ssh keys using DNS and SSHFP resource records

hostkeydns import "suah.dev/hostkeydns" Package hostkeydns facilitates verifying

Feb 11, 2022
Bell is the simplest event system written in Go (Golang) which is based on the execution of handlers independent of the main channel.

Bell Bell is the simplest event system written in Go (Golang) which is based on the execution of handlers independent of the main channel. Written in

Nov 17, 2022
A major platform Remote Access Terminal Tool based by Blockchain/P2P.
A major platform Remote Access Terminal Tool based by Blockchain/P2P.

NGLite A major platform Remote Access Terminal Tool based by Blockchain/P2P. No public IP address required.More anonymity Example Detection Warning!!!

Jan 2, 2023
A remote access tool & CNC
A remote access tool & CNC

⚠️ ⚠️ Disclaimer just use this with good intentions ⚠️ ⚠️ An useless rat (remote acces tool in develop) web client you want to use it? download pairat

Dec 14, 2022
A remote access tool & CNC
A remote access tool & CNC

⚠️ ⚠️ Disclaimer just use this with good intentions ⚠️ ⚠️ An useless rat (remote acces tool in develop) web client you want to use it? download pairat

Dec 14, 2022
Simple tool for connecting to remote hosts via ssh written on GO.

sshmenu is a simple tool for connecting to remote hosts via ssh written on GO. Great if you have trouble remembering IP addresses, hostnames, usernames or path to a key file.

Jul 21, 2022
Go wrapper around Device Console Windows tool.

go-devcon Go wrapper around the Windows Device Console (devcon.exe). go install github.com/mikerourke/go-devcon Introduction Here's a brief overview

Nov 4, 2021
oniongrok forwards ports on the local host to remote Onion addresses as Tor hidden services and vice-versa

oniongrok Onion addresses for anything. oniongrok forwards ports on the local host to remote Onion addresses as Tor hidden services and vice-versa. Wh

Jan 1, 2023
📦 Command line peer-to-peer data transfer tool based on libp2p.

pcp - Peer Copy Command line peer-to-peer data transfer tool based on libp2p. Table of Contents Motivation Project Status How does it work? Usage Inst

Jan 5, 2023
A simple command-line tool to manage ADRs in markdown format

Architecture Decision Records A simple command-line tool to manage ADRs in markdown format. Usage adr init [path] Initialize the ADR path and create a

Feb 10, 2022
Radical - A command-line tool facilitating development of radiant-based application

radical radical is a command-line tool facilitating development of radiant-based

Mar 22, 2022
protoCURL is cURL for Protobuf: The command-line tool for interacting with Protobuf over HTTP REST endpoints using human-readable text formats

protoCURL protoCURL is cURL for Protobuf: The command-line tool for interacting with Protobuf over HTTP REST endpoints using human-readable text forma

Jan 6, 2023
Simple reverse shell to avoid Windows defender and kaspersky detection
Simple reverse shell to avoid Windows defender and kaspersky detection

Windows-ReverseShell Simple reverse shell to avoid Windows defender, kaspersky d

Oct 19, 2022
Local development against a remote Kubernetes or OpenShift cluster
Local development against a remote Kubernetes or OpenShift cluster

Documentation - start here! ** Note: Telepresence 1 is being replaced by our even better Telepresence 2. Please try Telepresence 2 first and report an

Jan 8, 2023
Simple HTTP tunnel using SSH remote port forwarding

Simple HTTP tunnel using SSH remote port forwarding

Nov 18, 2022
EasyAgent is an infrastructure component, applied to manage the life-cycle of services on the remote host.
EasyAgent is an infrastructure component, applied to manage the life-cycle of services on the remote host.

Easyagent English | 中文 介绍 easyagent是在袋鼠云内部广泛使用的基础架构组件,最佳应用场景包括ELK体系beats等数据采集器的管控和配置管理、数栈体系自动化部署等 基本原理 easyagent主要有sidecar和server两个组件,sidecar部署在主机端,si

Nov 24, 2022