winrm 1.2.0 → 1.3.0.dev.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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