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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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