winrm 1.2.0 → 1.3.0.dev.1

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.
@@ -1,20 +1,19 @@
1
- =begin
2
- This file is part of WinRM; the Ruby library for Microsoft WinRM.
3
-
4
- Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
5
-
6
- Licensed under the Apache License, Version 2.0 (the "License");
7
- you may not use this file except in compliance with the License.
8
- You may obtain a copy of the License at
9
-
10
- http://www.apache.org/licenses/LICENSE-2.0
11
-
12
- Unless required by applicable law or agreed to in writing, software
13
- distributed under the License is distributed on an "AS IS" BASIS,
14
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- See the License for the specific language governing permissions and
16
- limitations under the License.
17
- =end
1
+ # Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'nori'
16
+ require 'rexml/document'
18
17
 
19
18
  module WinRM
20
19
  # This is the main class that does the SOAP request/response logic. There are a few helper classes, but pretty
@@ -36,17 +35,19 @@ module WinRM
36
35
  def initialize(endpoint, transport = :kerberos, opts = {})
37
36
  @endpoint = endpoint
38
37
  @timeout = DEFAULT_TIMEOUT
39
- @max_env_sz = DEFAULT_MAX_ENV_SIZE
38
+ @max_env_sz = DEFAULT_MAX_ENV_SIZE
40
39
  @locale = DEFAULT_LOCALE
40
+ @logger = Logging.logger[self]
41
41
  case transport
42
42
  when :kerberos
43
43
  require 'gssapi'
44
- # TODO: check fo keys and throw error if missing
45
44
  @xfer = HTTP::HttpGSSAPI.new(endpoint, opts[:realm], opts[:service], opts[:keytab], opts)
46
45
  when :plaintext
47
46
  @xfer = HTTP::HttpPlaintext.new(endpoint, opts[:user], opts[:pass], opts)
48
47
  when :ssl
49
48
  @xfer = HTTP::HttpSSL.new(endpoint, opts[:user], opts[:pass], opts[:ca_trust_path], opts)
49
+ else
50
+ raise "Invalid transport '#{transport}' specified, expected: kerberos, plaintext, ssl."
50
51
  end
51
52
  end
52
53
 
@@ -85,33 +86,34 @@ module WinRM
85
86
  o_stream = shell_opts.has_key?(:o_stream) ? shell_opts[:o_stream] : 'stdout stderr'
86
87
  codepage = shell_opts.has_key?(:codepage) ? shell_opts[:codepage] : 437
87
88
  noprofile = shell_opts.has_key?(:noprofile) ? shell_opts[:noprofile] : 'FALSE'
88
- s = Savon::SOAP::XML.new
89
- s.version = 2
90
- s.namespaces.merge!(namespaces)
91
89
  h_opts = { "#{NS_WSMAN_DMTF}:OptionSet" => { "#{NS_WSMAN_DMTF}:Option" => [noprofile, codepage],
92
90
  :attributes! => {"#{NS_WSMAN_DMTF}:Option" => {'Name' => ['WINRS_NOPROFILE','WINRS_CODEPAGE']}}}}
93
- s.header.merge!(merge_headers(header,resource_uri_cmd,action_create,h_opts))
94
- s.input = "#{NS_WIN_SHELL}:Shell"
95
- s.body = {
91
+ shell_body = {
96
92
  "#{NS_WIN_SHELL}:InputStreams" => i_stream,
97
93
  "#{NS_WIN_SHELL}:OutputStreams" => o_stream
98
94
  }
99
- s.body["#{NS_WIN_SHELL}:WorkingDirectory"] = shell_opts[:working_directory] if shell_opts.has_key?(:working_directory)
100
- # TODO: research Lifetime a bit more: http://msdn.microsoft.com/en-us/library/cc251546(v=PROT.13).aspx
101
- #s.body["#{NS_WIN_SHELL}:Lifetime"] = Iso8601Duration.sec_to_dur(shell_opts[:lifetime]) if(shell_opts.has_key?(:lifetime) && shell_opts[:lifetime].is_a?(Fixnum))
102
- # @todo make it so the input is given in milliseconds and converted to xs:duration
103
- s.body["#{NS_WIN_SHELL}:IdleTimeOut"] = shell_opts[:idle_timeout] if(shell_opts.has_key?(:idle_timeout) && shell_opts[:idle_timeout].is_a?(String))
95
+ shell_body["#{NS_WIN_SHELL}:WorkingDirectory"] = shell_opts[:working_directory] if shell_opts.has_key?(:working_directory)
96
+ shell_body["#{NS_WIN_SHELL}:IdleTimeOut"] = shell_opts[:idle_timeout] if(shell_opts.has_key?(:idle_timeout) && shell_opts[:idle_timeout].is_a?(String))
104
97
  if(shell_opts.has_key?(:env_vars) && shell_opts[:env_vars].is_a?(Hash))
105
98
  keys = shell_opts[:env_vars].keys
106
99
  vals = shell_opts[:env_vars].values
107
- s.body["#{NS_WIN_SHELL}:Environment"] = {
100
+ shell_body["#{NS_WIN_SHELL}:Environment"] = {
108
101
  "#{NS_WIN_SHELL}:Variable" => vals,
109
102
  :attributes! => {"#{NS_WIN_SHELL}:Variable" => {'Name' => keys}}
110
103
  }
111
104
  end
105
+ builder = Builder::XmlMarkup.new
106
+ builder.instruct!(:xml, :encoding => 'UTF-8')
107
+ builder.tag! :env, :Envelope, namespaces do |env|
108
+ env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_create,h_opts)) }
109
+ env.tag! :env, :Body do |body|
110
+ body.tag!("#{NS_WIN_SHELL}:Shell") { |s| s << Gyoku.xml(shell_body)}
111
+ end
112
+ end
112
113
 
113
- resp = send_message(s.to_xml)
114
- (resp/"//*[@Name='ShellId']").text
114
+ resp_doc = send_message(builder.target!)
115
+ shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
116
+ shell_id
115
117
  end
116
118
 
117
119
  # Run a command on a machine with an open shell
@@ -122,24 +124,53 @@ module WinRM
122
124
  def run_command(shell_id, command, arguments = [], cmd_opts = {})
123
125
  consolemode = cmd_opts.has_key?(:console_mode_stdin) ? cmd_opts[:console_mode_stdin] : 'TRUE'
124
126
  skipcmd = cmd_opts.has_key?(:skip_cmd_shell) ? cmd_opts[:skip_cmd_shell] : 'FALSE'
125
- s = Savon::SOAP::XML.new
126
- s.version = 2
127
- s.namespaces.merge!(namespaces)
127
+
128
128
  h_opts = { "#{NS_WSMAN_DMTF}:OptionSet" => {
129
129
  "#{NS_WSMAN_DMTF}:Option" => [consolemode, skipcmd],
130
130
  :attributes! => {"#{NS_WSMAN_DMTF}:Option" => {'Name' => ['WINRS_CONSOLEMODE_STDIN','WINRS_SKIP_CMD_SHELL']}}}
131
131
  }
132
- s.header.merge!(merge_headers(header,resource_uri_cmd,action_command,h_opts,selector_shell_id(shell_id)))
133
- s.input = "#{NS_WIN_SHELL}:CommandLine"
134
- s.body = { "#{NS_WIN_SHELL}:Command" => "\"#{command}\"", "#{NS_WIN_SHELL}:Arguments" => arguments}
132
+ body = { "#{NS_WIN_SHELL}:Command" => "\"#{command}\"", "#{NS_WIN_SHELL}:Arguments" => arguments}
133
+
134
+ builder = Builder::XmlMarkup.new
135
+ builder.instruct!(:xml, :encoding => 'UTF-8')
136
+ builder.tag! :env, :Envelope, namespaces do |env|
137
+ env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_command,h_opts,selector_shell_id(shell_id))) }
138
+ env.tag!(:env, :Body) do |env_body|
139
+ env_body.tag!("#{NS_WIN_SHELL}:CommandLine") { |cl| cl << Gyoku.xml(body) }
140
+ end
141
+ end
135
142
 
136
143
  # Grab the command element and unescape any single quotes - issue 69
137
- xml = s.to_xml
144
+ xml = builder.target!
138
145
  escaped_cmd = /<#{NS_WIN_SHELL}:Command>(.+)<\/#{NS_WIN_SHELL}:Command>/.match(xml)[1]
139
146
  xml.sub!(escaped_cmd, escaped_cmd.gsub(/&#39;/, "'"))
140
147
 
141
- resp = send_message(xml)
142
- (resp/"//#{NS_WIN_SHELL}:CommandId").text
148
+ resp_doc = send_message(xml)
149
+ command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
150
+ command_id
151
+ end
152
+
153
+ def write_stdin(shell_id, command_id, stdin)
154
+ # Signal the Command references to terminate (close stdout/stderr)
155
+ body = {
156
+ "#{NS_WIN_SHELL}:Send" => {
157
+ "#{NS_WIN_SHELL}:Stream" => {
158
+ "@Name" => 'stdin',
159
+ "@CommandId" => command_id,
160
+ :content! => Base64.encode64(stdin)
161
+ }
162
+ }
163
+ }
164
+ builder = Builder::XmlMarkup.new
165
+ builder.instruct!(:xml, :encoding => 'UTF-8')
166
+ builder.tag! :env, :Envelope, namespaces do |env|
167
+ env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_send,selector_shell_id(shell_id))) }
168
+ env.tag!(:env, :Body) do |env_body|
169
+ env_body << Gyoku.xml(body)
170
+ end
171
+ end
172
+ resp = send_message(builder.target!)
173
+ true
143
174
  end
144
175
 
145
176
  # Get the Output of the given shell and command
@@ -149,19 +180,23 @@ module WinRM
149
180
  # is either :stdout or :stderr. The reason it is in an Array so so we can get the output in the order it ocurrs on
150
181
  # the console.
151
182
  def get_command_output(shell_id, command_id, &block)
152
- s = Savon::SOAP::XML.new
153
- s.version = 2
154
- s.namespaces.merge!(namespaces)
155
- s.header.merge!(merge_headers(header,resource_uri_cmd,action_receive,selector_shell_id(shell_id)))
156
- s.input = "#{NS_WIN_SHELL}:Receive"
157
- s.body = { "#{NS_WIN_SHELL}:DesiredStream" => 'stdout stderr',
183
+ body = { "#{NS_WIN_SHELL}:DesiredStream" => 'stdout stderr',
158
184
  :attributes! => {"#{NS_WIN_SHELL}:DesiredStream" => {'CommandId' => command_id}}}
159
185
 
160
- resp = send_message(s.to_xml)
186
+ builder = Builder::XmlMarkup.new
187
+ builder.instruct!(:xml, :encoding => 'UTF-8')
188
+ builder.tag! :env, :Envelope, namespaces do |env|
189
+ env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_receive,selector_shell_id(shell_id))) }
190
+ env.tag!(:env, :Body) do |env_body|
191
+ env_body.tag!("#{NS_WIN_SHELL}:Receive") { |cl| cl << Gyoku.xml(body) }
192
+ end
193
+ end
194
+
195
+ resp_doc = send_message(builder.target!)
161
196
  output = {:data => []}
162
- (resp/"//#{NS_WIN_SHELL}:Stream").each do |n|
197
+ REXML::XPath.match(resp_doc, "//#{NS_WIN_SHELL}:Stream").each do |n|
163
198
  next if n.text.nil? || n.text.empty?
164
- stream = {n['Name'].to_sym => Base64.decode64(n.text)}
199
+ stream = { n.attributes['Name'].to_sym => Base64.decode64(n.text) }
165
200
  output[:data] << stream
166
201
  yield stream[:stdout], stream[:stderr] if block_given?
167
202
  end
@@ -175,12 +210,14 @@ module WinRM
175
210
  # <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
176
211
  # <rsp:ExitCode>0</rsp:ExitCode>
177
212
  # </rsp:CommandState>
178
- if((resp/"//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']").empty?)
179
- output.merge!(get_command_output(shell_id,command_id,&block)) do |key, old_data, new_data|
213
+ #if ((resp/"//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']").empty?)
214
+ done_elems = REXML::XPath.match(resp_doc, "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']")
215
+ if done_elems.empty?
216
+ output.merge!(get_command_output(shell_id, command_id, &block)) do |key, old_data, new_data|
180
217
  old_data += new_data
181
218
  end
182
219
  else
183
- output[:exitcode] = (resp/"//#{NS_WIN_SHELL}:ExitCode").text.to_i
220
+ output[:exitcode] = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:ExitCode").text.to_i
184
221
  end
185
222
  output
186
223
  end
@@ -191,16 +228,17 @@ module WinRM
191
228
  # @param [String] command_id The command id on the remote machine. See #run_command
192
229
  # @return [true] This should have more error checking but it just returns true for now.
193
230
  def cleanup_command(shell_id, command_id)
194
- s = Savon::SOAP::XML.new
195
- s.version = 2
196
- s.namespaces.merge!(namespaces)
197
- s.header.merge!(merge_headers(header,resource_uri_cmd,action_signal,selector_shell_id(shell_id)))
198
-
199
231
  # Signal the Command references to terminate (close stdout/stderr)
200
- s.input = ["#{NS_WIN_SHELL}:Signal", {'CommandId' => command_id}]
201
-
202
- s.body = { "#{NS_WIN_SHELL}:Code" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate' }
203
- resp = send_message(s.to_xml)
232
+ body = { "#{NS_WIN_SHELL}:Code" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate' }
233
+ builder = Builder::XmlMarkup.new
234
+ builder.instruct!(:xml, :encoding => 'UTF-8')
235
+ builder.tag! :env, :Envelope, namespaces do |env|
236
+ env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_signal,selector_shell_id(shell_id))) }
237
+ env.tag!(:env, :Body) do |env_body|
238
+ env_body.tag!("#{NS_WIN_SHELL}:Signal", {'CommandId' => command_id}) { |cl| cl << Gyoku.xml(body) }
239
+ end
240
+ end
241
+ resp = send_message(builder.target!)
204
242
  true
205
243
  end
206
244
 
@@ -208,31 +246,15 @@ module WinRM
208
246
  # @param [String] shell_id The shell id on the remote machine. See #open_shell
209
247
  # @return [true] This should have more error checking but it just returns true for now.
210
248
  def close_shell(shell_id)
211
- s = Savon::SOAP::XML.new
212
- s.version = 2
213
- s.namespaces.merge!(namespaces)
214
- s.namespaces.merge!(Savon::SOAP::XML::SchemaTypes)
215
- s.header.merge!(merge_headers(header,resource_uri_cmd,action_delete,selector_shell_id(shell_id)))
216
-
217
- # Because Savon does not support a nil Body we have to build it ourselves.
218
- s.xml do |b|
219
- b.tag!('env:Envelope', s.namespaces) do
220
- b.tag!('env:Header') do |bh|
221
- bh << Gyoku.xml(s.header) unless s.header.empty?
222
- end
223
- if(s.input.nil?)
224
- b.tag! 'env:Body'
225
- else
226
- b.tag! 'env:Body' do |bb|
227
- bb.tag! s.input do |bbb|
228
- bbb << Gyoku.xml(s.body) if s.body
229
- end
230
- end
231
- end
232
- end
249
+ builder = Builder::XmlMarkup.new
250
+ builder.instruct!(:xml, :encoding => 'UTF-8')
251
+
252
+ builder.tag!('env:Envelope', namespaces) do |env|
253
+ env.tag!('env:Header') { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_delete,selector_shell_id(shell_id))) }
254
+ env.tag!('env:Body')
233
255
  end
234
256
 
235
- resp = send_message(s.to_xml)
257
+ resp = send_message(builder.target!)
236
258
  true
237
259
  end
238
260
 
@@ -274,28 +296,35 @@ module WinRM
274
296
  # @param [String] wql The WQL query
275
297
  # @return [Hash] Returns a Hash that contain the key/value pairs returned from the query.
276
298
  def run_wql(wql)
277
- s = Savon::SOAP::XML.new
278
- s.version = 2
279
- s.namespaces.merge!(namespaces)
280
- s.header.merge!(merge_headers(header,resource_uri_wmi,action_enumerate))
281
- s.input = "#{NS_ENUM}:Enumerate"
282
- s.body = { "#{NS_WSMAN_DMTF}:OptimizeEnumeration" => nil,
299
+
300
+ body = { "#{NS_WSMAN_DMTF}:OptimizeEnumeration" => nil,
283
301
  "#{NS_WSMAN_DMTF}:MaxElements" => '32000',
284
302
  "#{NS_WSMAN_DMTF}:Filter" => wql,
285
303
  :attributes! => { "#{NS_WSMAN_DMTF}:Filter" => {'Dialect' => 'http://schemas.microsoft.com/wbem/wsman/1/WQL'}}
286
304
  }
287
305
 
288
- resp = send_message(s.to_xml)
289
- toggle_nori_type_casting :off
290
- hresp = Nori.parse(resp.to_xml)[:envelope][:body]
291
- toggle_nori_type_casting :original
306
+ builder = Builder::XmlMarkup.new
307
+ builder.instruct!(:xml, :encoding => 'UTF-8')
308
+ builder.tag! :env, :Envelope, namespaces do |env|
309
+ env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_wmi,action_enumerate)) }
310
+ env.tag!(:env, :Body) do |env_body|
311
+ env_body.tag!("#{NS_ENUM}:Enumerate") { |en| en << Gyoku.xml(body) }
312
+ end
313
+ end
314
+
315
+ resp = send_message(builder.target!)
316
+ parser = Nori.new(:parser => :rexml, :advanced_typecasting => false, :convert_tags_to => lambda { |tag| tag.snakecase.to_sym }, :strip_namespaces => true)
317
+ hresp = parser.parse(resp.to_s)[:envelope][:body]
318
+
292
319
  # Normalize items so the type always has an array even if it's just a single item.
293
320
  items = {}
294
- hresp[:enumerate_response][:items].each_pair do |k,v|
295
- if v.is_a?(Array)
296
- items[k] = v
297
- else
298
- items[k] = [v]
321
+ if hresp[:enumerate_response][:items]
322
+ hresp[:enumerate_response][:items].each_pair do |k,v|
323
+ if v.is_a?(Array)
324
+ items[k] = v
325
+ else
326
+ items[k] = [v]
327
+ end
299
328
  end
300
329
  end
301
330
  items
@@ -303,24 +332,18 @@ module WinRM
303
332
  alias :wql :run_wql
304
333
 
305
334
  def toggle_nori_type_casting(to)
306
- @nori_type_casting ||= Nori.advanced_typecasting?
307
- case to.to_sym
308
- when :original
309
- Nori.advanced_typecasting = @nori_type_casting
310
- when :on
311
- Nori.advanced_typecasting = true
312
- when :off
313
- Nori.advanced_typecasting = false
314
- else
315
- raise ArgumentError, "Cannot toggle type casting to '#{to}', it is not a valid argument"
316
- end
317
-
335
+ @logger.warn('toggle_nori_type_casting is deprecated and has no effect, ' +
336
+ 'please remove calls to it')
318
337
  end
319
338
 
320
339
  private
321
340
 
322
341
  def namespaces
323
- {'xmlns:a' => 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
342
+ {
343
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
344
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
345
+ 'xmlns:env' => 'http://www.w3.org/2003/05/soap-envelope',
346
+ 'xmlns:a' => 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
324
347
  'xmlns:b' => 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd',
325
348
  'xmlns:n' => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration',
326
349
  'xmlns:x' => 'http://schemas.xmlsoap.org/ws/2004/09/transfer',
@@ -340,7 +363,6 @@ module WinRM
340
363
  "#{NS_ADDRESSING}:MessageID" => "uuid:#{UUIDTools::UUID.random_create.to_s.upcase}",
341
364
  "#{NS_WSMAN_DMTF}:Locale/" => '',
342
365
  "#{NS_WSMAN_MSFT}:DataLocale/" => '',
343
- #"#{NS_WSMAN_CONF}:MaxTimeoutms" => 600, #TODO: research this a bit http://msdn.microsoft.com/en-us/library/cc251561(v=PROT.13).aspx
344
366
  "#{NS_WSMAN_DMTF}:OperationTimeout" => @timeout,
345
367
  :attributes! => {
346
368
  "#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => {'mustUnderstand' => true},
@@ -362,23 +384,9 @@ module WinRM
362
384
  end
363
385
 
364
386
  def send_message(message)
365
- resp = @xfer.send_request(message)
366
-
367
- begin
368
- errors = resp/"//#{NS_SOAP_ENV}:Body/#{NS_SOAP_ENV}:Fault/*"
369
- if errors.empty?
370
- return resp
371
- else
372
- resp.root.add_namespace(NS_WSMAN_FAULT,'http://schemas.microsoft.com/wbem/wsman/1/wsmanfault')
373
- fault = (errors/"//#{NS_WSMAN_FAULT}:WSManFault").first
374
- raise WinRMWSManFault, "[WSMAN ERROR CODE: #{fault['Code']}]: #{fault.text}"
375
- end
376
- # Sometimes a blank response is returned and it will throw this error when the XPath is evaluated for Fault
377
- # The returned string will be '<?xml version="1.0"?>\n' in these cases
378
- rescue Nokogiri::XML::XPath::SyntaxError => e
379
- raise WinRMWebServiceError, "Bad SOAP Packet returned. Sometimes a retry will solve this error."
380
- end
387
+ @xfer.send_request(message)
381
388
  end
389
+
382
390
 
383
391
  # Helper methods for SOAP Headers
384
392
 
@@ -417,13 +425,18 @@ module WinRM
417
425
  :attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
418
426
  end
419
427
 
428
+ def action_send
429
+ {"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send',
430
+ :attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
431
+ end
432
+
420
433
  def action_enumerate
421
434
  {"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate',
422
435
  :attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
423
436
  end
424
437
 
425
438
  def selector_shell_id(shell_id)
426
- {"#{NS_WSMAN_DMTF}:SelectorSet" =>
439
+ {"#{NS_WSMAN_DMTF}:SelectorSet" =>
427
440
  {"#{NS_WSMAN_DMTF}:Selector" => shell_id, :attributes! => {"#{NS_WSMAN_DMTF}:Selector" => {'Name' => 'ShellId'}}}
428
441
  }
429
442
  end
data/lib/winrm.rb CHANGED
@@ -1,28 +1,36 @@
1
- =begin
2
- This file is part of WinRM; the Ruby library for Microsoft WinRM.
3
-
4
- Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
5
-
6
- Licensed under the Apache License, Version 2.0 (the "License");
7
- you may not use this file except in compliance with the License.
8
- You may obtain a copy of the License at
9
-
10
- http://www.apache.org/licenses/LICENSE-2.0
11
-
12
- Unless required by applicable law or agreed to in writing, software
13
- distributed under the License is distributed on an "AS IS" BASIS,
14
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- See the License for the specific language governing permissions and
16
- limitations under the License.
17
- =end
1
+ # Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
18
14
 
19
15
  require 'date'
20
16
  require 'kconv' # rubyntlm .0.1.1 doesn't require kconv, workaround for issue #65
21
17
  require 'logging'
22
18
 
23
19
  module WinRM
24
- Logging.logger.root.level = :info
25
- Logging.logger.root.appenders = Logging.appenders.stdout
20
+ # Enable logging if it is requested. We do this before
21
+ # anything else so that we can setup the output before
22
+ # any logging occurs.
23
+ if ENV["WINRM_LOG"] && ENV["WINRM_LOG"] != ""
24
+ begin
25
+ Logging.logger.root.level = ENV["WINRM_LOG"]
26
+ Logging.logger.root.appenders = Logging.appenders.stderr
27
+ rescue ArgumentError
28
+ # This means that the logging level wasn't valid
29
+ $stderr.puts "Invalid WINRM_LOG level is set: #{ENV["WINRM_LOG"]}"
30
+ $stderr.puts ""
31
+ $stderr.puts "Please use one of the standard log levels: debug, info, warn, or error"
32
+ end
33
+ end
26
34
  end
27
35
 
28
36
  require 'winrm/helpers/iso8601_duration'
@@ -0,0 +1,15 @@
1
+ # This test may only be meaningful with kerberos auth
2
+ # Against server 2012, a kerberos connection will require reauth (get a 401)
3
+ # if there are no requests for >= 15 seconds
4
+
5
+ describe "Verify kerberos will reauth when necessary", :kerberos => true do
6
+ before(:all) do
7
+ @winrm = winrm_connection
8
+ end
9
+
10
+ it 'work with a 18 second sleep' do
11
+ ps_command = 'Start-Sleep -s 18'
12
+ output = @winrm.run_powershell_script(ps_command)
13
+ output[:exitcode].should == 0
14
+ end
15
+ end
data/spec/cmd_spec.rb CHANGED
@@ -1,36 +1,58 @@
1
- describe "Test remote WQL features via WinRM", :integration => true do
1
+ describe 'winrm client cmd', :integration => true do
2
2
  before(:all) do
3
3
  @winrm = winrm_connection
4
4
  end
5
5
 
6
- it 'should run a CMD command string' do
7
- output = @winrm.run_cmd('ipconfig /all')
8
- expect(output[:exitcode]).to eq(0)
9
- expect(output[:data]).to_not be_empty
6
+ describe 'empty string' do
7
+ subject(:output) { @winrm.cmd('') }
8
+ it { should have_exit_code 0 }
9
+ it { should have_no_stdout }
10
+ it { should have_no_stderr }
10
11
  end
11
12
 
12
- it 'should run a CMD command with proper arguments' do
13
- output = @winrm.run_cmd('ipconfig', %w{/all})
14
- expect(output[:exitcode]).to eq(0)
15
- expect(output[:data]).to_not be_empty
13
+ describe 'ipconfig' do
14
+ subject(:output) { @winrm.cmd('ipconfig') }
15
+ it { should have_exit_code 0 }
16
+ it { should have_stdout_match /Windows IP Configuration/ }
17
+ it { should have_no_stderr }
16
18
  end
17
19
 
18
- it 'should run a CMD command with block' do
19
- outvar = ''
20
- @winrm.run_cmd('ipconfig', %w{/all}) do |stdout, stderr|
21
- outvar << stdout
22
- end
23
- expect(outvar).to match(/Windows IP Configuration/)
20
+ describe 'echo \'hello world\' using apostrophes' do
21
+ subject(:output) { @winrm.cmd("echo 'hello world'") }
22
+ it { should have_exit_code 0 }
23
+ it { should have_stdout_match /'hello world'/ }
24
+ it { should have_no_stderr }
25
+ end
26
+
27
+ describe 'ipconfig with /all argument' do
28
+ subject(:output) { @winrm.cmd('ipconfig', %w{/all}) }
29
+ it { should have_exit_code 0 }
30
+ it { should have_stdout_match /Windows IP Configuration/ }
31
+ it { should have_no_stderr }
24
32
  end
25
33
 
26
- it 'should run a CMD command that contains an apostrophe' do
27
- output = @winrm.run_cmd(%q{echo 'hello world'})
28
- expect(output[:exitcode]).to eq(0)
29
- expect(output[:data][0][:stdout]).to match(/'hello world'/)
34
+ describe 'dir with incorrect argument /z' do
35
+ subject(:output) { @winrm.cmd('dir /z') }
36
+ it { should have_exit_code 1 }
37
+ it { should have_no_stdout }
38
+ it { should have_stderr_match /Invalid switch/ }
30
39
  end
31
40
 
32
- it 'should run a CMD command that is empty' do
33
- output = @winrm.run_cmd('')
34
- expect(output[:exitcode]).to eq(0)
41
+ describe 'ipconfig && echo error 1>&2' do
42
+ subject(:output) { @winrm.cmd('ipconfig && echo error 1>&2') }
43
+ it { should have_exit_code 0 }
44
+ it { should have_stdout_match /Windows IP Configuration/ }
45
+ it { should have_stderr_match /error/ }
46
+ end
47
+
48
+ describe 'ipconfig with a block' do
49
+ subject(:stdout) do
50
+ outvar = ''
51
+ @winrm.cmd('ipconfig') do |stdout, stderr|
52
+ outvar << stdout
53
+ end
54
+ outvar
55
+ end
56
+ it { should match(/Windows IP Configuration/) }
35
57
  end
36
58
  end
@@ -0,0 +1,35 @@
1
+ describe "Exceptions", :unit => true do
2
+ describe WinRM::WinRMAuthorizationError do
3
+
4
+ let (:error) { WinRM::WinRMHTTPTransportError.new("Foo happened", 500) }
5
+
6
+ it 'adds the response code to the message' do
7
+ expect(error.message).to eq('Foo happened (500).')
8
+ end
9
+
10
+ it 'exposes the response code as an attribute' do
11
+ expect(error.status_code).to eq 500
12
+ end
13
+
14
+ it 'is a winrm error' do
15
+ expect(error).to be_kind_of(WinRM::WinRMError)
16
+ end
17
+ end
18
+
19
+ describe WinRM::WinRMWSManFault do
20
+
21
+ let (:error) { WinRM::WinRMWSManFault.new("fault text", 42) }
22
+
23
+ it 'exposes the fault text as an attribute' do
24
+ expect(error.fault_description).to eq('fault text')
25
+ end
26
+
27
+ it 'exposes the fault code as an attribute' do
28
+ expect(error.fault_code).to eq 42
29
+ end
30
+
31
+ it 'is a winrm error' do
32
+ expect(error).to be_kind_of(WinRM::WinRMError)
33
+ end
34
+ end
35
+ end