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.
- checksums.yaml +4 -4
- data/README.md +7 -8
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/rwinrm +89 -0
- data/lib/winrm/exceptions/exceptions.rb +36 -26
- data/lib/winrm/helpers/iso8601_duration.rb +13 -17
- data/lib/winrm/http/response_handler.rb +70 -0
- data/lib/winrm/http/transport.rb +70 -43
- data/lib/winrm/soap_provider.rb +15 -19
- data/lib/winrm/winrm_service.rb +145 -132
- data/lib/winrm.rb +27 -19
- data/spec/auth_timeout_spec.rb +15 -0
- data/spec/cmd_spec.rb +44 -22
- data/spec/exception_spec.rb +35 -0
- data/spec/matchers.rb +72 -0
- data/spec/powershell_spec.rb +54 -6
- data/spec/response_handler_spec.rb +44 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/stubs/responses/open_shell_v1.xml +19 -0
- data/spec/stubs/responses/open_shell_v2.xml +20 -0
- data/spec/stubs/responses/soap_fault_v1.xml +36 -0
- data/spec/stubs/responses/soap_fault_v2.xml +42 -0
- data/spec/winrm_primitives_spec.rb +6 -6
- data/spec/wql_spec.rb +6 -3
- data/winrm.gemspec +8 -7
- metadata +51 -29
- data/spec/nori_type_casting_spec.rb +0 -28
- data/spec/test.ps1 +0 -14
data/lib/winrm/winrm_service.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
100
|
-
#
|
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
|
-
|
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
|
-
|
114
|
-
(
|
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
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
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 =
|
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(/'/, "'"))
|
140
147
|
|
141
|
-
|
142
|
-
(
|
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
|
-
|
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
|
-
|
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
|
-
(
|
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
|
-
|
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] = (
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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(
|
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
|
-
|
278
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
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]
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
-
@
|
307
|
-
|
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
|
-
{
|
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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
25
|
-
|
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
|
1
|
+
describe 'winrm client cmd', :integration => true do
|
2
2
|
before(:all) do
|
3
3
|
@winrm = winrm_connection
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
output
|
8
|
-
|
9
|
-
|
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
|
-
|
13
|
-
output
|
14
|
-
|
15
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
27
|
-
output
|
28
|
-
|
29
|
-
|
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
|
-
|
33
|
-
output
|
34
|
-
|
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
|