winrm 0.0.6 → 1.0.0rc1

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,37 +0,0 @@
1
- #############################################################################
2
- # Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
3
- #
4
- #
5
- # This file is part of WinRM.
6
- #
7
- # WinRM is free software: you can redistribute it and/or
8
- # modify it under the terms of the GNU General Public License as published
9
- # by the Free Software Foundation, either version 3 of the License, or (at
10
- # your option) any later version.
11
- #
12
- # WinRM is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15
- # Public License for more details.
16
- #
17
- # You should have received a copy of the GNU General Public License along
18
- # with WinRM. If not, see <http://www.gnu.org/licenses/>.
19
- #############################################################################
20
-
21
- # Custom extensions to the class String
22
- class String
23
- # Change CamelCased strings to ruby_cased strings
24
- # It uses the lookahead assertion ?= In this case it basically says match
25
- # anything followed by a capital letter, but not the capital letter itself.
26
- # @see http://www.pcre.org/pcre.txt The PCRE guide for more details
27
- def ruby_case
28
- self.split(/(?=[A-Z])/).join('_').downcase
29
- end
30
-
31
- # Change a ruby_cased string to CamelCased
32
- def camel_case
33
- self.split(/_/).map { |i|
34
- i.sub(/^./) { |s| s.upcase }
35
- }.join
36
- end
37
- end # String
@@ -1,36 +0,0 @@
1
- #############################################################################
2
- # Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
3
- #
4
- #
5
- # This file is part of WinRM.
6
- #
7
- # WinRM is free software: you can redistribute it and/or
8
- # modify it under the terms of the GNU General Public License as published
9
- # by the Free Software Foundation, either version 3 of the License, or (at
10
- # your option) any later version.
11
- #
12
- # WinRM is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15
- # Public License for more details.
16
- #
17
- # You should have received a copy of the GNU General Public License along
18
- # with WinRM. If not, see <http://www.gnu.org/licenses/>.
19
- #############################################################################
20
- require 'handsoap'
21
-
22
- module WinRM
23
- module SOAP
24
- NS_ADDRESSING ='a' # http://schemas.xmlsoap.org/ws/2004/08/addressing
25
- NS_CIMBINDING ='b' # http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd
26
- NS_ENUM ='n' # http://schemas.xmlsoap.org/ws/2004/09/enumeration
27
- NS_TRANSFER ='x' # http://schemas.xmlsoap.org/ws/2004/09/transfer
28
- NS_WSMAN_DMTF ='w' # http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
29
- NS_WSMAN_MSFT ='p' # http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
30
- NS_SCHEMA_INST ='xsi' # http://www.w3.org/2001/XMLSchema-instance
31
- NS_WIN_SHELL ='rsp' # http://schemas.microsoft.com/wbem/wsman/1/windows/shell
32
- end
33
- end
34
-
35
- require 'soap/exceptions/exceptions'
36
- require 'soap/winrm_service'
@@ -1,387 +0,0 @@
1
- #############################################################################
2
- # Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
3
- #
4
- #
5
- # This file is part of WinRM.
6
- #
7
- # WinRM is free software: you can redistribute it and/or
8
- # modify it under the terms of the GNU General Public License as published
9
- # by the Free Software Foundation, either version 3 of the License, or (at
10
- # your option) any later version.
11
- #
12
- # WinRM is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15
- # Public License for more details.
16
- #
17
- # You should have received a copy of the GNU General Public License along
18
- # with WinRM. If not, see <http://www.gnu.org/licenses/>.
19
- #############################################################################
20
- Handsoap.http_driver = :http_client
21
-
22
- module WinRM
23
- module SOAP
24
- class WinRMWebService < Handsoap::Service
25
- include SOAP
26
-
27
- @@raw_soap = false
28
-
29
- def initialize()
30
- if $DEBUG
31
- @debug = File.new('winrm_debug.out', 'w')
32
- @debug.sync = true
33
- end
34
- end
35
-
36
- def self.set_auth(user,pass)
37
- @@user = user
38
- @@pass = pass
39
- true
40
- end
41
-
42
- def self.set_ca_trust_path(file_or_dir)
43
- @@ca_trust_store = file_or_dir
44
- true
45
- end
46
-
47
- # Turn off parsing and just return the soap response
48
- def self.raw_soap!
49
- @@raw_soap = true
50
- end
51
-
52
-
53
- # ********* Begin Hooks *********
54
-
55
- def on_create_document(doc)
56
- doc.alias NS_ADDRESSING, 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
57
- doc.alias NS_ENUM, 'http://schemas.xmlsoap.org/ws/2004/09/enumeration'
58
- doc.alias NS_WSMAN_DMTF, 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
59
- doc.alias NS_WSMAN_MSFT, 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd'
60
- doc.alias NS_SCHEMA_INST,'http://www.w3.org/2001/XMLSchema-instance'
61
- doc.alias NS_WIN_SHELL, 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell'
62
- doc.alias NS_CIMBINDING, 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd'
63
-
64
- header = doc.find('Header')
65
- header.add("#{NS_ADDRESSING}:To", WinRMWebService.uri)
66
- header.add("#{NS_ADDRESSING}:ReplyTo") {|rto|
67
- rto.add("#{NS_ADDRESSING}:Address",'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous') {|addr|
68
- addr.set_attr('mustUnderstand','true')
69
- }
70
- }
71
- header.add("#{NS_WSMAN_DMTF}:MaxEnvelopeSize",'153600') {|mes|
72
- mes.set_attr('mustUnderstand','true')
73
- }
74
- header.add("#{NS_ADDRESSING}:MessageID", "uuid:#{UUID.generate.upcase}")
75
- header.add("#{NS_WSMAN_DMTF}:Locale") {|loc|
76
- loc.set_attr('xml:lang','en-US')
77
- loc.set_attr('mustUnderstand','false')
78
- }
79
- header.add("#{NS_WSMAN_MSFT}:DataLocale") {|loc|
80
- loc.set_attr('xml:lang','en-US')
81
- loc.set_attr('mustUnderstand','false')
82
- }
83
- header.add("#{NS_WSMAN_DMTF}:OperationTimeout",'PT60.000S')
84
- end
85
-
86
- # Adds knowledge of namespaces to the response object. These have to be identical to the
87
- # URIs returned in the XML response. For example, I had some issues with the 'soap'
88
- # namespace because my original URI did not end in a '/'
89
- # @example
90
- # Won't work: http://schemas.xmlsoap.org/soap/envelope
91
- # Works: http://schemas.xmlsoap.org/soap/envelope/
92
- def on_response_document(doc)
93
- doc.add_namespace NS_ADDRESSING, 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
94
- doc.add_namespace NS_ENUM, 'http://schemas.xmlsoap.org/ws/2004/09/enumeration'
95
- doc.add_namespace NS_TRANSFER, 'http://schemas.xmlsoap.org/ws/2004/09/transfer'
96
- doc.add_namespace NS_WSMAN_DMTF, 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
97
- doc.add_namespace NS_WSMAN_MSFT, 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd'
98
- doc.add_namespace NS_WIN_SHELL, 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell'
99
- doc.add_namespace NS_CIMBINDING, 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd'
100
- end
101
-
102
- def on_after_create_http_request(req)
103
- req.set_auth @@user, @@pass
104
- req.set_header('Content-Type','application/soap+xml;charset=UTF-8')
105
- req.set_trust_ca_file(@@ca_trust_store) if defined?(@@ca_trust_store)
106
- #puts "SOAP DOCUMENT=\n#{req.body}"
107
- end
108
-
109
- def on_http_error(resp)
110
- case resp.status
111
- when 401
112
- raise WinRMAuthorizationError, "#{resp.headers}\n------\n#{resp.body}"
113
- else
114
- raise WinRMWebServiceError, "#{resp.headers}\n------\n#{resp.body}"
115
- end
116
- end
117
-
118
-
119
- # ********** End Hooks **********
120
-
121
-
122
- # Create a Shell on the destination host
123
- # @param [String<optional>] i_stream Which input stream to open. Leave this alone unless you know what you're doing
124
- # @param [String<optional>] o_stream Which output stream to open. Leave this alone unless you know what you're doing
125
- # @return [String] The ShellId from the SOAP response. This is our open shell instance on the remote machine.
126
- def open_shell(i_stream = 'stdin', o_stream = 'stdout stderr')
127
- header = {
128
- "#{NS_WSMAN_DMTF}:OptionSet" => [
129
- {"#{NS_WSMAN_DMTF}:Option" => {:name => 'WINRS_NOPROFILE', :text =>"FALSE"}},
130
- {"#{NS_WSMAN_DMTF}:Option" => {:name => 'WINRS_CODEPAGE', :text =>"437"}}
131
- ]
132
- }.merge(resource_uri_cmd).merge(action_create)
133
-
134
- resp = invoke("#{NS_WIN_SHELL}:Shell", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |shell|
135
- shell.add("#{NS_WIN_SHELL}:InputStreams", i_stream)
136
- shell.add("#{NS_WIN_SHELL}:OutputStreams",o_stream)
137
- end
138
-
139
- # Get the Shell ID from the response
140
- (resp/"//*[@Name='ShellId']").to_s
141
- end
142
-
143
- # Run a command on a machine with an open shell
144
- # @param [String] shell_id The shell id on the remote machine. See #open_shell
145
- # @param [String] command The command to run on the remote machine
146
- # @return [String] The CommandId from the SOAP response. This is the ID we need to query in order to get output.
147
- def run_command(shell_id, command)
148
- header = {
149
- "#{NS_WSMAN_DMTF}:OptionSet" => {
150
- "#{NS_WSMAN_DMTF}:Option" => {:name => 'WINRS_CONSOLEMODE_STDIN', :text =>"TRUE"},
151
- }
152
- }.merge(resource_uri_cmd).merge(action_command).merge(selector_shell_id(shell_id))
153
-
154
- # Issue the Command
155
- resp = invoke("#{NS_WIN_SHELL}:CommandLine", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |cli|
156
- cli.add("#{NS_WIN_SHELL}:Command","\"#{command}\"")
157
- end
158
-
159
- (resp/"//#{NS_WIN_SHELL}:CommandId").to_s
160
- end
161
-
162
- # Get the Output of the given shell and command
163
- # @param [String] shell_id The shell id on the remote machine. See #open_shell
164
- # @param [String] command_id The command id on the remote machine. See #run_command
165
- # @return [Hash] Returns a Hash with a key :exitcode and :data. Data is an Array of Hashes where the cooresponding key
166
- # 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
167
- # the console.
168
- def get_command_output(shell_id, command_id)
169
- header = {}.merge(resource_uri_cmd).merge(action_receive).merge(selector_shell_id(shell_id))
170
-
171
- # Get Command Output
172
- resp = invoke("#{NS_WIN_SHELL}:Receive", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |rec|
173
- rec.add("#{NS_WIN_SHELL}:DesiredStream",'stdout stderr') do |ds|
174
- ds.set_attr('CommandId', command_id)
175
- end
176
- end
177
-
178
- output = {:data => []}
179
- (resp/"//#{NS_WIN_SHELL}:Stream").each do |n|
180
- next if n.to_s.nil?
181
- output[:data] << {n['Name'].to_sym => Base64.decode64(n.to_s)}
182
- end
183
-
184
- # We may need to get additional output if the stream has not finished.
185
- # The CommandState will change from Running to Done like so:
186
- # @example
187
- # from...
188
- # <rsp:CommandState CommandId="495C3B09-E0B0-442A-9958-83B529F76C2C" State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Running"/>
189
- # to...
190
- # <rsp:CommandState CommandId="495C3B09-E0B0-442A-9958-83B529F76C2C" State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
191
- # <rsp:ExitCode>0</rsp:ExitCode>
192
- # </rsp:CommandState>
193
- if((resp/"//#{NS_WIN_SHELL}:ExitCode").empty?)
194
- output.merge!(get_command_output(shell_id,command_id)) do |key, old_data, new_data|
195
- old_data += new_data
196
- end
197
- else
198
- output[:exitcode] = (resp/"//#{NS_WIN_SHELL}:ExitCode").first.to_i
199
- end
200
- output
201
- end
202
-
203
- # Clean-up after a command.
204
- # @see #run_command
205
- # @param [String] shell_id The shell id on the remote machine. See #open_shell
206
- # @param [String] command_id The command id on the remote machine. See #run_command
207
- # @return [true] This should have more error checking but it just returns true for now.
208
- def cleanup_command(shell_id, command_id)
209
- header = {}.merge(resource_uri_cmd).merge(action_signal).merge(selector_shell_id(shell_id))
210
- # Signal the Command references to terminate (close stdout/stderr)
211
- resp = invoke("#{NS_WIN_SHELL}:Signal", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |sig|
212
- sig.set_attr('CommandId', command_id)
213
- sig.add("#{NS_WIN_SHELL}:Code",'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate')
214
- end
215
- true
216
- end
217
-
218
- # Close the shell
219
- # @param [String] shell_id The shell id on the remote machine. See #open_shell
220
- # @return [true] This should have more error checking but it just returns true for now.
221
- def close_shell(shell_id)
222
- header = {}.merge(resource_uri_cmd).merge(action_delete).merge(selector_shell_id(shell_id))
223
- # Delete the Shell reference
224
- resp = invoke(:nil_body, {:soap_action => nil, :soap_body => true, :http_options => nil, :soap_header => header})
225
- true
226
- end
227
-
228
- # Run a CMD command
229
- # @param [String] command The command to run on the remote system
230
- # @return [Hash] :stdout and :stderr
231
- def run_cmd(command)
232
- shell_id = open_shell
233
- command_id = run_command(shell_id, command)
234
- command_output = get_command_output(shell_id, command_id)
235
- cleanup_command(shell_id, command_id)
236
- close_shell(shell_id)
237
- command_output
238
- end
239
-
240
-
241
- # Run a Powershell script that resides on the local box.
242
- # @param [String] script_file The string representing the path to a Powershell script
243
- # @return [Hash] :stdout and :stderr
244
- def run_powershell_script(script_file)
245
- script = File.read(script_file)
246
- script = script.chars.to_a.join("\x00").chomp
247
- if(defined?(script.encode))
248
- script = script.encode('ASCII-8BIT')
249
- script = Base64.strict_encode64(script)
250
- else
251
- script = Base64.encode64(script).chomp
252
- end
253
-
254
-
255
- shell_id = open_shell
256
- command_id = run_command(shell_id, "powershell -encodedCommand #{script}")
257
- command_output = get_command_output(shell_id, command_id)
258
- cleanup_command(shell_id, command_id)
259
- close_shell(shell_id)
260
- command_output
261
- end
262
-
263
-
264
- # Run a WQL Query
265
- # @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
266
- # @param [String] wql The WQL query
267
- # @return [Array<Hash>] Returns an array of Hashes that contain the key/value pairs returned from the query.
268
- def run_wql(wql)
269
- header = {}.merge(resource_uri_wmi).merge(action_enumerate)
270
-
271
- begin
272
- resp = invoke("#{NS_ENUM}:Enumerate", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |enum|
273
- enum.add("#{NS_WSMAN_DMTF}:OptimizeEnumeration")
274
- enum.add("#{NS_WSMAN_DMTF}:MaxElements",'32000')
275
- mattr = nil
276
- enum.add("#{NS_WSMAN_DMTF}:Filter", wql) do |filt|
277
- filt.set_attr('Dialect','http://schemas.microsoft.com/wbem/wsman/1/WQL')
278
- end
279
- end
280
- rescue Handsoap::Fault => e
281
- raise WinRMWebServiceError, e.reason
282
- end
283
-
284
- query_response = []
285
- (resp/"//#{NS_ENUM}:EnumerateResponse//#{NS_WSMAN_DMTF}:Items/*").each do |i|
286
- qitem = {}
287
- (i/'*').each do |si|
288
- qitem[si.node_name] = si.to_s
289
- end
290
- query_response << qitem
291
- end
292
- query_response
293
- end
294
-
295
-
296
- # To create an empty body set :soap_body => true in the invoke options and set the action to :nil_body
297
- def iterate_hash_array(element, hash_array)
298
- add_hierarchy!(element, hash_array, nil) unless hash_array.key?(:nil_body)
299
- end
300
-
301
-
302
- protected
303
-
304
- # Add a hierarchy of elements from hash data
305
- # @example Hash to XML
306
- # {:this => {:text =>'that'},'top' => {:id => '32fss', :text => 'TestText', {'middle' => 'bottom'}}}
307
- # becomes...
308
- # <this>that</this>
309
- # <top Id='32fss'>
310
- # TestText
311
- # <middle>bottom</middle>
312
- # </top>
313
- def add_hierarchy!(node, e_hash, prefix = NS_ADDRESSING)
314
- prefix << ":" unless prefix.nil?
315
- e_hash.each_pair do |k,v|
316
- name = (k.is_a?(Symbol) && k != :text) ? k.to_s.camel_case : k
317
- if v.is_a? Hash
318
- node.add("#{prefix}#{name}", v[:text]) do |n|
319
- add_hierarchy!(n, v, prefix)
320
- end
321
- elsif v.is_a? Array
322
- node.add("#{prefix}#{name}") do |n|
323
- v.each do |i|
324
- add_hierarchy!(n, i, prefix)
325
- end
326
- end
327
- else
328
- node.set_attr(name, v) unless k == :text
329
- end
330
- end
331
- end
332
-
333
-
334
- # Private Methods (Builders and Parsers)
335
- private
336
-
337
- def build!(node, opts = {}, &block)
338
- #EwsBuilder.new(node, opts, &block)
339
- end
340
-
341
- def parse!(response, opts = {})
342
- return response if @@raw_soap
343
- #EwsParser.new(response).parse(opts)
344
- end
345
-
346
-
347
- # Helper methods for SOAP Headers
348
-
349
- def resource_uri_cmd
350
- {"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd'}}
351
- end
352
-
353
- def resource_uri_wmi(namespace = 'root/cimv2/*')
354
- {"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => 'true', :text => "http://schemas.microsoft.com/wbem/wsman/1/wmi/#{namespace}"}}
355
- end
356
-
357
- def action_create
358
- {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Create'}}
359
- end
360
-
361
- def action_delete
362
- {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete'}}
363
- end
364
-
365
- def action_command
366
- {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command'}}
367
- end
368
-
369
- def action_receive
370
- {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive'}}
371
- end
372
-
373
- def action_signal
374
- {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal'}}
375
- end
376
-
377
- def action_enumerate
378
- {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate'}}
379
- end
380
-
381
- def selector_shell_id(shell_id)
382
- {"#{NS_WSMAN_DMTF}:SelectorSet" => {"#{NS_WSMAN_DMTF}:Selector" => {:name => 'ShellId', :text => shell_id}}}
383
- end
384
-
385
- end # class WinRMWebService
386
- end # module SOAP
387
- end # WinRM