winrm 1.3.0.dev.2 → 1.3.0.dev.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd79ebba5bae6d5d3a3149b46ec99c8bef9ddfac
4
- data.tar.gz: 080075faa797bbfacd52383b8f5c3847ce1f6ee4
3
+ metadata.gz: 19e0ee9ca403a8fe7b5385edd2a055e040aa69fe
4
+ data.tar.gz: 7c66ac5aa2b9b02fa2245f52f1f61f770f929287
5
5
  SHA512:
6
- metadata.gz: 37c993dab7b631fd83e13eb9dffcd0aec1c9769c9ff21af1bbe1ba8e8db88a2e7b94c2174096e591268525f9ff942be8a4dc8b47a57e9f3fb77f47bb4403cd1b
7
- data.tar.gz: 68589e411195257d6380409938e5ee311d5001f747d5727f96f95df4c0a044c4d975b4fdfcc4ece08d8e0fd28359b6a01adb0a1efa19aacf5dc7aecefb0fd47d
6
+ metadata.gz: d2e8e8dddd4eb89fb2a19ff72ee2413cd39599c6c7198e1b8d3761f095739631ab73bb11467073d555a9fa059a723c081ce0d3d91188f2176143c65461770801
7
+ data.tar.gz: dcbd77c85f639859fdfbd60ba9a26b82ef7676139f9878853b8c4d64a85f8a1bff5e6051acb6f96d6c268025ed901ee7e7a054f4197886893e95954ed07de55a
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ spec/spec/creds*.json
8
8
  spec/config.yml
9
9
  traces/
10
10
  Gemfile.lock
11
+ .vagrant
data/README.md CHANGED
@@ -73,21 +73,29 @@ WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => 'MYREALM.COM')
73
73
  ```
74
74
 
75
75
  ### Uploading files
76
- Files may be copied from the local machine to the winrm endpoint. Individual files or directories may be specified:
76
+ Files may be copied from the local machine to the winrm endpoint. Individual
77
+ files or directories may be specified:
77
78
  ```ruby
78
- WinRM::FileTransfer.upload(client, 'c:/dev/my_dir', '$env:AppData')
79
+ service = WinRM::WinRMWebService.new(...
80
+ file_manager = WinRM::FileManager.new(service)
81
+ file_manager.upload('c:/dev/my_dir', '$env:AppData')
79
82
  ```
80
83
  Or an array of several files and/or directories can be included:
81
84
  ```ruby
82
- WinRM::FileTransfer.upload(client, ['c:/dev/file1.txt','c:/dev/dir1'], '$env:AppData')
85
+ file_manager.upload(['c:/dev/file1.txt','c:/dev/dir1'], '$env:AppData')
83
86
  ```
84
- **Note**:The winrm protocol poseses some limitations that can adversely affect the performance of large file transfers. By default, the above upload call will display a progress bar to indicate the progress of a file transfer currently underway. This progress bar can be suppressed by setting the `:quiet` option to `true`:
87
+
88
+ #### Handling progress events
89
+ If you want to implemnt your own custom progress handling, you can pass a code
90
+ block and use the proggress data that `upload` yields to this block:
85
91
  ```ruby
86
- WinRM::FileTransfer.upload(client, 'c:/dev/my_dir', '$env:AppData', :quiet => true)
92
+ file_manager.upload('c:/dev/my_dir', '$env:AppData') do |bytes_copied, total_bytes, local_path, remote_path|
93
+ puts "#{bytes_copied}bytes of #{total_bytes}bytes copied"
94
+ end
87
95
  ```
88
96
 
89
97
  ## Troubleshooting
90
- You may have some errors like ```WinRM::WinRMHTTPTransportError: Bad HTTP response returned from server (401).```.
98
+ You may have some errors like ```WinRM::WinRMAuthorizationError```.
91
99
  You can run the following commands on the server to try to solve the problem:
92
100
  ```
93
101
  winrm set winrm/config/client/auth @{Basic="true"}
data/Rakefile CHANGED
@@ -21,4 +21,6 @@ RSpec::Core::RakeTask.new(:integration) do |task|
21
21
  task.rspec_opts << '-tintegration'
22
22
  end
23
23
 
24
- task :default => "spec"
24
+ task :default => :spec
25
+
26
+ task :all => [:spec, :integration]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.0.dev.2
1
+ 1.3.0.dev.3
data/Vagrantfile ADDED
@@ -0,0 +1,8 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ VAGRANTFILE_API_VERSION = "2"
5
+
6
+ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
7
+ config.vm.box = "mwrock/Windows2012R2"
8
+ end
data/bin/rwinrm CHANGED
@@ -17,82 +17,48 @@
17
17
  $:.push File.expand_path('../../lib', __FILE__)
18
18
 
19
19
  require 'readline'
20
- require 'optparse'
20
+ require 'io/console'
21
21
  require 'winrm'
22
22
 
23
+ def help_msg
24
+ puts "Usage: rwinrm user@host"
25
+ puts ""
26
+ end
27
+
23
28
  def parse_options
24
29
  options = {}
30
+ raise "Missing required options" unless ARGV.length == 1
31
+
32
+ m = /^(?<user>\w+)@{1}(?<host>[a-zA-Z0-9\.]+)(?<port>:[0-9]+)?/.match(ARGV[0])
33
+ raise "#{ARGV[0]} is an invalid host" unless m
34
+ options[:user] = m[:user]
35
+ options[:endpoint] = "http://#{m[:host]}#{m[:port] || ':5985'}/wsman"
36
+
37
+ # Get the password
38
+ print "Password: "
39
+ options[:pass] = STDIN.noecho(&:gets).chomp
40
+ puts
41
+
42
+ # Set some defaults required by WinRM WS
25
43
  options[:auth_type] = :plaintext
26
44
  options[:basic_auth_only] = true
27
- options[:mode] = :repl
28
-
29
- optparse = OptionParser.new do |opts|
30
- opts.banner = "Usage: rwinrm [command] [local_path] [remote_path] endpoint [options]"
31
- opts.separator ""
32
- opts.separator "Commands"
33
- opts.separator " repl(default)"
34
- opts.separator " upload"
35
- opts.separator ""
36
- opts.separator "Options"
37
-
38
- opts.on('-u', '--user username', String, 'WinRM user name') do |v|
39
- options[:user] = v
40
- end
41
-
42
- opts.on('-p', '--pass password', String, 'WinRM user password') do |v|
43
- options[:pass] = v
44
- end
45
-
46
- opts.on('-h', '--help', 'Display this screen') do
47
- puts optparse
48
- exit
49
- end
50
- end
51
-
52
- optparse.parse!
53
-
54
- if ARGV[0] && ARGV[0].downcase !~ /\A#{URI::regexp(['http', 'https'])}\z/
55
- options[:mode] = ARGV.shift.downcase.to_sym
56
-
57
- case options[:mode]
58
- when :upload
59
- options[:local_path] = ARGV.shift
60
- options[:remote_path] = ARGV.shift
61
-
62
- raise OptionParser::MissingArgument.new(:local_path) if options[:local_path].nil?
63
- raise OptionParser::MissingArgument.new(:remote_path) if options[:remote_path].nil?
64
- when :repl
65
- else
66
- raise OptionParser::InvalidArgument.new(options[:mode])
67
- end
68
- end
69
- options[:endpoint] = ARGV.shift
70
-
71
- raise OptionParser::MissingArgument.new(:endpoint) if options[:endpoint].nil?
72
45
 
73
46
  options
74
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::InvalidArgument
75
- puts $!.message
76
- puts optparse
47
+ rescue StandardError => e
48
+ puts e.message
49
+ help_msg
77
50
  exit 1
78
51
  end
79
52
 
80
- def winrm_client(options)
81
- WinRM::WinRMWebService.new(
82
- options[:endpoint],
83
- options[:auth_type].to_sym,
84
- options)
85
- end
53
+ def repl(options)
54
+ client = WinRM::WinRMWebService.new(
55
+ options[:endpoint],
56
+ options[:auth_type].to_sym,
57
+ options)
86
58
 
87
- def upload(client, options)
88
- bytes = WinRM::FileTransfer.upload(client, options[:local_path], options[:remote_path])
59
+ client.set_timeout(3600)
89
60
  shell_id = client.open_shell()
90
- puts "#{bytes} total bytes transfered"
91
- end
92
-
93
- def repl(client, options)
94
- shell_id = client.open_shell()
95
- command_id = client.run_command(shell_id, 'cmd')
61
+ command_id = client.run_command(shell_id, 'cmd', "/K prompt [#{ARGV[0]}]$P$G")
96
62
 
97
63
  read_thread = Thread.new do
98
64
  client.get_command_output(shell_id, command_id) do |stdout, stderr|
@@ -100,6 +66,7 @@ def repl(client, options)
100
66
  STDERR.write stderr
101
67
  end
102
68
  end
69
+ read_thread.abort_on_exception = true
103
70
 
104
71
  while buf = Readline.readline('', true)
105
72
  if buf =~ /^exit/
@@ -111,17 +78,14 @@ def repl(client, options)
111
78
  client.write_stdin(shell_id, command_id, "#{buf}\r\n")
112
79
  end
113
80
  end
114
- end
115
-
116
- def run(options)
117
- client = winrm_client(options)
118
- method(options[:mode]).call(client, options)
119
- exit 0
120
81
  rescue Interrupt
121
82
  # ctrl-c
83
+ rescue WinRM::WinRMAuthorizationError
84
+ puts "Authentication failed, bad user name or password"
85
+ exit 1
122
86
  rescue StandardError => e
123
87
  puts e.message
124
88
  exit 1
125
89
  end
126
90
 
127
- run(parse_options())
91
+ repl(parse_options())
@@ -20,7 +20,11 @@ module WinRM
20
20
  # Authorization Error
21
21
  class WinRMAuthorizationError < WinRMError; end
22
22
 
23
- class WinRMUploadFailed < WinRMError; end
23
+ # Error that occurs when a file upload fails
24
+ class WinRMUploadError < WinRMError; end
25
+
26
+ # Error that occurs when a file download fails
27
+ class WinRMDownloadError < WinRMError; end
24
28
 
25
29
  # A Fault returned in the SOAP response. The XML node is a WSManFault
26
30
  class WinRMWSManFault < WinRMError
@@ -34,6 +38,18 @@ module WinRM
34
38
  end
35
39
  end
36
40
 
41
+ # A Fault returned in the SOAP response. The XML node is a MSFT_WmiError
42
+ class WinRMWMIError < WinRMError
43
+ attr_reader :error_code
44
+ attr_reader :error
45
+
46
+ def initialize(error, error_code)
47
+ @error = error
48
+ @error_code = error_code
49
+ super("[WMI ERROR CODE: #{error_code}]: #{error}")
50
+ end
51
+ end
52
+
37
53
  # non-200 response without a SOAP fault
38
54
  class WinRMHTTPTransportError < WinRMError
39
55
  attr_reader :status_code
@@ -0,0 +1,22 @@
1
+ module WinRM
2
+ class PowershellScript
3
+
4
+ attr_reader :text
5
+
6
+ # Creates a new PowershellScript object which can be used to encode
7
+ # PS scripts for safe transport over WinRM.
8
+ # @param [String] The PS script text content
9
+ def initialize(script)
10
+ @text = script
11
+ end
12
+
13
+ # Encodes the script so that it can be passed to the PowerShell
14
+ # --EncodedCommand argument.
15
+ # @return [String] The UTF-16LE base64 encoded script
16
+ def encoded()
17
+ encoded_script = text.encode('UTF-16LE', 'UTF-8')
18
+ Base64.strict_encode64(encoded_script)
19
+ end
20
+
21
+ end
22
+ end
@@ -46,7 +46,8 @@ module WinRM
46
46
  def raise_if_error()
47
47
  return if @status_code == 200
48
48
  raise_if_auth_error()
49
- raise_if_soap_fault()
49
+ raise_if_wsman_fault()
50
+ raise_if_wmi_error()
50
51
  raise_transport_error()
51
52
  end
52
53
 
@@ -54,11 +55,22 @@ module WinRM
54
55
  raise WinRMAuthorizationError.new if @status_code == 401
55
56
  end
56
57
 
57
- def raise_if_soap_fault()
58
+ def raise_if_wsman_fault()
58
59
  soap_errors = REXML::XPath.match(response_xml, "//#{NS_SOAP_ENV}:Body/#{NS_SOAP_ENV}:Fault/*")
59
60
  if !soap_errors.empty?
60
61
  fault = REXML::XPath.first(soap_errors, "//#{NS_WSMAN_FAULT}:WSManFault")
61
- raise WinRMWSManFault.new(fault.to_s, fault.attributes['Code'])
62
+ raise WinRMWSManFault.new(fault.to_s, fault.attributes['Code']) unless fault.nil?
63
+ end
64
+ end
65
+
66
+ def raise_if_wmi_error()
67
+ soap_errors = REXML::XPath.match(response_xml, "//#{NS_SOAP_ENV}:Body/#{NS_SOAP_ENV}:Fault/*")
68
+ if !soap_errors.empty?
69
+ error = REXML::XPath.first(soap_errors, "//#{NS_WSMAN_MSFT}:MSFT_WmiError")
70
+ if !error.nil?
71
+ error_code = REXML::XPath.first(error, "//#{NS_WSMAN_MSFT}:error_Code").text
72
+ raise WinRMWMIError.new(error.to_s, error_code)
73
+ end
62
74
  end
63
75
  end
64
76
 
@@ -22,12 +22,15 @@ module WinRM
22
22
  # is possible to use GSSAPI with Keep-Alive.
23
23
  class HttpTransport
24
24
 
25
+ # Set this to an unreasonable amount because WinRM has its own timeouts
26
+ DEFAULT_RECEIVE_TIMEOUT = 3600
27
+
25
28
  attr_reader :endpoint
26
29
 
27
30
  def initialize(endpoint)
28
31
  @endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
29
32
  @httpcli = HTTPClient.new(:agent_name => 'Ruby WinRM Client')
30
- @httpcli.receive_timeout = 3600 # Set this to an unreasonable amount for now because WinRM has timeouts
33
+ @httpcli.receive_timeout = DEFAULT_RECEIVE_TIMEOUT
31
34
  @logger = Logging.logger[self]
32
35
  end
33
36
 
@@ -62,6 +65,16 @@ module WinRM
62
65
  @httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
63
66
  end
64
67
 
68
+ # HTTP Client receive timeout. How long should a remote call wait for a
69
+ # for a response from WinRM?
70
+ def receive_timeout=(sec)
71
+ @httpcli.receive_timeout = sec
72
+ end
73
+
74
+ def receive_timeout()
75
+ @httpcli.receive_timeout
76
+ end
77
+
65
78
  protected
66
79
 
67
80
  def log_soap_message(message)
@@ -0,0 +1,27 @@
1
+ module WinRM
2
+ # This class holds raw output as a hash, and has convenience methods to parse.
3
+ class Output < Hash
4
+ def initialize
5
+ super
6
+ self[:data] = []
7
+ end
8
+
9
+ def output
10
+ self[:data].flat_map do | line |
11
+ [line[:stdout], line[:stderr]]
12
+ end.compact.join
13
+ end
14
+
15
+ def stdout
16
+ self[:data].map do | line |
17
+ line[:stdout]
18
+ end.compact.join
19
+ end
20
+
21
+ def stderr
22
+ self[:data].map do | line |
23
+ line[:stderr]
24
+ end.compact.join
25
+ end
26
+ end
27
+ end
@@ -14,6 +14,7 @@
14
14
 
15
15
  require 'nori'
16
16
  require 'rexml/document'
17
+ require_relative 'helpers/powershell_script'
17
18
 
18
19
  module WinRM
19
20
  # This is the main class that does the SOAP request/response logic. There are a few helper classes, but pretty
@@ -41,6 +42,7 @@ module WinRM
41
42
  case transport
42
43
  when :kerberos
43
44
  require 'gssapi'
45
+ require 'gssapi/extensions'
44
46
  @xfer = HTTP::HttpGSSAPI.new(endpoint, opts[:realm], opts[:service], opts[:keytab], opts)
45
47
  when :plaintext
46
48
  @xfer = HTTP::HttpPlaintext.new(endpoint, opts[:user], opts[:pass], opts)
@@ -51,11 +53,20 @@ module WinRM
51
53
  end
52
54
  end
53
55
 
54
- # Operation timeout
56
+ # Operation timeout.
57
+ #
58
+ # Unless specified the client receive timeout will be 10s + the operation
59
+ # timeout.
60
+ #
55
61
  # @see http://msdn.microsoft.com/en-us/library/ee916629(v=PROT.13).aspx
56
- # @param [Fixnum] sec the number of seconds to set the timeout to. It will be converted to an ISO8601 format.
57
- def set_timeout(sec)
58
- @timeout = Iso8601Duration.sec_to_dur(sec)
62
+ #
63
+ # @param [Fixnum] The number of seconds to set the WinRM operation timeout
64
+ # @param [Fixnum] The number of seconds to set the Ruby receive timeout
65
+ # @return [String] The ISO 8601 formatted operation timeout
66
+ def set_timeout(op_timeout_sec, receive_timeout_sec=nil)
67
+ @timeout = Iso8601Duration.sec_to_dur(op_timeout_sec)
68
+ @xfer.receive_timeout = receive_timeout_sec || op_timeout_sec + 10
69
+ @timeout
59
70
  end
60
71
  alias :op_timeout :set_timeout
61
72
 
@@ -151,12 +162,12 @@ module WinRM
151
162
 
152
163
  # Grab the command element and unescape any single quotes - issue 69
153
164
  xml = builder.target!
154
- escaped_cmd = /<#{NS_WIN_SHELL}:Command>(.+)<\/#{NS_WIN_SHELL}:Command>/.match(xml)[1]
165
+ escaped_cmd = /<#{NS_WIN_SHELL}:Command>(.+)<\/#{NS_WIN_SHELL}:Command>/m.match(xml)[1]
155
166
  xml.sub!(escaped_cmd, escaped_cmd.gsub(/&#39;/, "'"))
156
167
 
157
168
  resp_doc = send_message(xml)
158
169
  command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
159
-
170
+
160
171
  if block_given?
161
172
  begin
162
173
  yield command_id
@@ -211,7 +222,7 @@ module WinRM
211
222
  end
212
223
 
213
224
  resp_doc = send_message(builder.target!)
214
- output = {:data => []}
225
+ output = Output.new
215
226
  REXML::XPath.match(resp_doc, "//#{NS_WIN_SHELL}:Stream").each do |n|
216
227
  next if n.text.nil? || n.text.empty?
217
228
  stream = { n.attributes['Name'].to_sym => Base64.decode64(n.text) }
@@ -299,10 +310,9 @@ module WinRM
299
310
  # @return [Hash] :stdout and :stderr
300
311
  def run_powershell_script(script_file, &block)
301
312
  # if an IO object is passed read it..otherwise assume the contents of the file were passed
302
- script = script_file.kind_of?(IO) ? script_file.read : script_file
303
- script = script.encode('UTF-16LE', 'UTF-8')
304
- script = Base64.strict_encode64(script)
305
- run_cmd("powershell -encodedCommand #{script}", &block)
313
+ script_text = script_file.kind_of?(IO) ? script_file.read : script_file
314
+ script = WinRM::PowershellScript.new(script_text)
315
+ run_cmd("powershell -encodedCommand #{script.encoded()}", &block)
306
316
  end
307
317
  alias :powershell :run_powershell_script
308
318
 
data/lib/winrm.rb CHANGED
@@ -33,6 +33,6 @@ module WinRM
33
33
  end
34
34
  end
35
35
 
36
+ require 'winrm/output'
36
37
  require 'winrm/helpers/iso8601_duration'
37
38
  require 'winrm/soap_provider'
38
- require 'winrm/file_transfer'
data/spec/cmd_spec.rb CHANGED
@@ -24,6 +24,38 @@ describe 'winrm client cmd', :integration => true do
24
24
  it { should have_no_stderr }
25
25
  end
26
26
 
27
+ describe 'capturing output from stdout and stderr' do
28
+ subject(:output) do
29
+ # Note: Multiple lines doesn't work:
30
+ # script = <<-eos
31
+ # echo Hello
32
+ # echo , world! 1>&2
33
+ # eos
34
+
35
+ script = "echo Hello & echo , world! 1>&2"
36
+
37
+ @captured_stdout, @captured_stderr = "", ""
38
+ @winrm.cmd(script) do |stdout, stderr|
39
+ @captured_stdout << stdout if stdout
40
+ @captured_stderr << stderr if stderr
41
+ end
42
+ end
43
+
44
+ it 'should have stdout' do
45
+ expect(output.stdout).to eq("Hello \r\n")
46
+ expect(output.stdout).to eq(@captured_stdout)
47
+ end
48
+
49
+ it 'should have stderr' do
50
+ expect(output.stderr).to eq(", world! \r\n")
51
+ expect(output.stderr).to eq(@captured_stderr)
52
+ end
53
+
54
+ it 'should have output' do
55
+ expect(output.output).to eq("Hello \r\n, world! \r\n")
56
+ end
57
+ end
58
+
27
59
  describe 'ipconfig with /all argument' do
28
60
  subject(:output) { @winrm.cmd('ipconfig', %w{/all}) }
29
61
  it { should have_exit_code 0 }
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Plain Text
8
8
  auth_type: plaintext
9
- endpoint: "http://localhost:5985/wsman"
9
+ endpoint: "http://localhost:55985/wsman"
10
10
  options:
11
11
  user: vagrant
12
12
  pass: vagrant
@@ -32,4 +32,21 @@ describe "Exceptions", :unit => true do
32
32
  expect(error).to be_kind_of(WinRM::WinRMError)
33
33
  end
34
34
  end
35
+
36
+ describe WinRM::WinRMWMIError do
37
+
38
+ let (:error) { WinRM::WinRMWMIError.new("message text", 77777) }
39
+
40
+ it 'exposes the error text as an attribute' do
41
+ expect(error.error).to eq('message text')
42
+ end
43
+
44
+ it 'exposes the error code as an attribute' do
45
+ expect(error.error_code).to eq 77777
46
+ end
47
+
48
+ it 'is a winrm error' do
49
+ expect(error).to be_kind_of(WinRM::WinRMError)
50
+ end
51
+ end
35
52
  end
@@ -0,0 +1,106 @@
1
+ describe WinRM::Output, :unit => true do
2
+ subject { WinRM::Output.new() }
3
+
4
+ context "when there is no output" do
5
+ describe "#stdout" do
6
+ it "is empty" do
7
+ expect(subject.stdout).to be_empty
8
+ end
9
+ end
10
+
11
+ describe "#stderr" do
12
+ it "is empty" do
13
+ expect(subject.stderr).to be_empty
14
+ end
15
+ end
16
+
17
+ describe "#output" do
18
+ it "is empty" do
19
+ expect(subject.output).to be_empty
20
+ end
21
+ end
22
+ end
23
+
24
+ context "when there is only one line" do
25
+ describe "#stdout" do
26
+ it "is equal to that line" do
27
+ subject[:data] << { :stdout => "foo" }
28
+ expect(subject.stdout).to eq("foo")
29
+ end
30
+ end
31
+
32
+ describe "#stderr" do
33
+ it "is equal to that line" do
34
+ subject[:data] << { :stderr => "foo" }
35
+ expect(subject.stderr).to eq("foo")
36
+ end
37
+ end
38
+
39
+ describe "#output" do
40
+ it "is equal to stdout" do
41
+ subject[:data] << { :stdout => "foo" }
42
+ expect(subject.output).to eq("foo")
43
+ end
44
+
45
+ it "is equal to stderr" do
46
+ subject[:data] << { :stderr => "foo" }
47
+ expect(subject.output).to eq("foo")
48
+ end
49
+ end
50
+ end
51
+
52
+ context "when there is one line of each type" do
53
+ before(:each) do
54
+ subject[:data] << { :stdout => "foo" }
55
+ subject[:data] << { :stderr => "bar" }
56
+ end
57
+
58
+ describe "#stdout" do
59
+ it "is equal to that line" do
60
+ expect(subject.stdout).to eq("foo")
61
+ end
62
+ end
63
+
64
+ describe "#stderr" do
65
+ it "is equal to that line" do
66
+ expect(subject.stderr).to eq("bar")
67
+ end
68
+ end
69
+
70
+ describe "#output" do
71
+ it "is equal to stdout + stderr" do
72
+ expect(subject.output).to eq("foobar")
73
+ end
74
+ end
75
+ end
76
+
77
+ context "when there are multiple lines" do
78
+ before(:each) do
79
+ subject[:data] << { :stdout => "I can have a newline\nanywhere, " }
80
+ subject[:data] << { :stderr => "I can also have stderr" }
81
+ subject[:data] << { :stdout => "or stdout", :stderr => " and stderr" }
82
+ subject[:data] << { }
83
+ subject[:data] << { :stdout => " or nothing! (above)" }
84
+ end
85
+
86
+ describe "#stdout" do
87
+ it "is equal to that line" do
88
+ expect(subject.stdout).to eq("I can have a newline\nanywhere, or stdout or nothing! (above)")
89
+ end
90
+ end
91
+
92
+ describe "#stderr" do
93
+ it "is equal to that line" do
94
+ expect(subject.stderr).to eq("I can also have stderr and stderr")
95
+ end
96
+ end
97
+
98
+ describe "#output" do
99
+ it "is equal to stdout + stderr" do
100
+ expect(subject.output).to eq("I can have a newline\nanywhere, I can also have stderror stdout and stderr or nothing! (above)")
101
+ end
102
+ end
103
+ end
104
+
105
+ pending "parse CLIXML errors and convert to Strings and/or Exceptions"
106
+ end
@@ -55,6 +55,40 @@ describe 'winrm client powershell', :integration => true do
55
55
  end
56
56
  it { should match(/Windows IP Configuration/) }
57
57
  end
58
+
59
+ describe 'capturing output from Write-Host and Write-Error' do
60
+ subject(:output) do
61
+ script = <<-eos
62
+ Write-Host 'Hello'
63
+ $host.ui.WriteErrorLine(', world!')
64
+ eos
65
+
66
+ @captured_stdout, @captured_stderr = "", ""
67
+ @winrm.powershell(script) do |stdout, stderr|
68
+ @captured_stdout << stdout if stdout
69
+ @captured_stderr << stderr if stderr
70
+ end
71
+ end
72
+
73
+ it 'should have stdout' do
74
+ expect(output.stdout).to eq("Hello\n")
75
+ expect(output.stdout).to eq(@captured_stdout)
76
+ end
77
+
78
+ it 'should have stderr' do
79
+ # TODO: Option to parse CLIXML
80
+ # expect(output.output).to eq("Hello\n, world!")
81
+ # expect(output.stderr).to eq(", world!")
82
+ expect(output.stderr).to eq("#< CLIXML\r\n<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Error\">, world!_x000D__x000A_</S></Objs>")
83
+ expect(output.stderr).to eq(@captured_stderr)
84
+ end
85
+
86
+ it 'should have output' do
87
+ # TODO: Option to parse CLIXML
88
+ # expect(output.output).to eq("Hello\n, world!")
89
+ expect(output.output).to eq("Hello\n#< CLIXML\r\n<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Error\">, world!_x000D__x000A_</S></Objs>")
90
+ end
91
+ end
58
92
  end
59
93
 
60
94