winrm 1.7.0 → 1.7.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/.gitignore +10 -10
- data/.rspec +3 -3
- data/.rubocop.yml +12 -12
- data/.travis.yml +12 -12
- data/Gemfile +9 -9
- data/LICENSE +202 -202
- data/README.md +194 -194
- data/Rakefile +36 -36
- data/Vagrantfile +9 -9
- data/appveyor.yml +42 -42
- data/bin/rwinrm +97 -97
- data/changelog.md +74 -71
- data/lib/winrm.rb +42 -42
- data/lib/winrm/command_executor.rb +224 -224
- data/lib/winrm/exceptions/exceptions.rb +57 -57
- data/lib/winrm/helpers/iso8601_duration.rb +58 -58
- data/lib/winrm/helpers/powershell_script.rb +42 -42
- data/lib/winrm/http/response_handler.rb +82 -82
- data/lib/winrm/http/transport.rb +421 -421
- data/lib/winrm/output.rb +43 -43
- data/lib/winrm/soap_provider.rb +39 -39
- data/lib/winrm/version.rb +7 -7
- data/lib/winrm/winrm_service.rb +556 -556
- data/preamble +17 -17
- data/spec/auth_timeout_spec.rb +16 -16
- data/spec/cmd_spec.rb +102 -102
- data/spec/command_executor_spec.rb +429 -429
- data/spec/config-example.yml +19 -19
- data/spec/exception_spec.rb +50 -50
- data/spec/issue_184_spec.rb +67 -67
- data/spec/issue_59_spec.rb +23 -23
- data/spec/matchers.rb +74 -74
- data/spec/output_spec.rb +110 -110
- data/spec/powershell_spec.rb +97 -97
- data/spec/response_handler_spec.rb +59 -59
- data/spec/spec_helper.rb +73 -73
- data/spec/stubs/responses/get_command_output_response.xml.erb +13 -13
- data/spec/stubs/responses/open_shell_v1.xml +19 -19
- data/spec/stubs/responses/open_shell_v2.xml +20 -20
- data/spec/stubs/responses/soap_fault_v1.xml +36 -36
- data/spec/stubs/responses/soap_fault_v2.xml +42 -42
- data/spec/stubs/responses/wmi_error_v2.xml +41 -41
- data/spec/transport_spec.rb +124 -124
- data/spec/winrm_options_spec.rb +76 -76
- data/spec/winrm_primitives_spec.rb +51 -51
- data/spec/wql_spec.rb +14 -14
- data/winrm.gemspec +40 -40
- metadata +2 -2
data/lib/winrm/output.rb
CHANGED
@@ -1,43 +1,43 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
#
|
3
|
-
# Copyright 2014 Max Lincoln <max@devopsy.com>
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
|
17
|
-
module WinRM
|
18
|
-
# This class holds raw output as a hash, and has convenience methods to parse.
|
19
|
-
class Output < Hash
|
20
|
-
def initialize
|
21
|
-
super
|
22
|
-
self[:data] = []
|
23
|
-
end
|
24
|
-
|
25
|
-
def output
|
26
|
-
self[:data].flat_map do |line|
|
27
|
-
[line[:stdout], line[:stderr]]
|
28
|
-
end.compact.join
|
29
|
-
end
|
30
|
-
|
31
|
-
def stdout
|
32
|
-
self[:data].map do |line|
|
33
|
-
line[:stdout]
|
34
|
-
end.compact.join
|
35
|
-
end
|
36
|
-
|
37
|
-
def stderr
|
38
|
-
self[:data].map do |line|
|
39
|
-
line[:stderr]
|
40
|
-
end.compact.join
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2014 Max Lincoln <max@devopsy.com>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module WinRM
|
18
|
+
# This class holds raw output as a hash, and has convenience methods to parse.
|
19
|
+
class Output < Hash
|
20
|
+
def initialize
|
21
|
+
super
|
22
|
+
self[:data] = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def output
|
26
|
+
self[:data].flat_map do |line|
|
27
|
+
[line[:stdout], line[:stderr]]
|
28
|
+
end.compact.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def stdout
|
32
|
+
self[:data].map do |line|
|
33
|
+
line[:stdout]
|
34
|
+
end.compact.join
|
35
|
+
end
|
36
|
+
|
37
|
+
def stderr
|
38
|
+
self[:data].map do |line|
|
39
|
+
line[:stderr]
|
40
|
+
end.compact.join
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/winrm/soap_provider.rb
CHANGED
@@ -1,39 +1,39 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
#
|
3
|
-
# Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
|
17
|
-
require 'httpclient'
|
18
|
-
require 'builder'
|
19
|
-
require 'gyoku'
|
20
|
-
require 'base64'
|
21
|
-
|
22
|
-
# SOAP constants for WinRM
|
23
|
-
module WinRM
|
24
|
-
NS_SOAP_ENV = 's' # http://www.w3.org/2003/05/soap-envelope
|
25
|
-
NS_ADDRESSING = 'a' # http://schemas.xmlsoap.org/ws/2004/08/addressing
|
26
|
-
NS_CIMBINDING = 'b' # http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd
|
27
|
-
NS_ENUM = 'n' # http://schemas.xmlsoap.org/ws/2004/09/enumeration
|
28
|
-
NS_TRANSFER = 'x' # http://schemas.xmlsoap.org/ws/2004/09/transfer
|
29
|
-
NS_WSMAN_DMTF = 'w' # http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
|
30
|
-
NS_WSMAN_MSFT = 'p' # http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
|
31
|
-
NS_SCHEMA_INST = 'xsi' # http://www.w3.org/2001/XMLSchema-instance
|
32
|
-
NS_WIN_SHELL = 'rsp' # http://schemas.microsoft.com/wbem/wsman/1/windows/shell
|
33
|
-
NS_WSMAN_FAULT = 'f' # http://schemas.microsoft.com/wbem/wsman/1/wsmanfault
|
34
|
-
NS_WSMAN_CONF = 'cfg' # http://schemas.microsoft.com/wbem/wsman/1/config
|
35
|
-
end
|
36
|
-
|
37
|
-
require 'winrm/exceptions/exceptions'
|
38
|
-
require 'winrm/winrm_service'
|
39
|
-
require 'winrm/http/transport'
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'httpclient'
|
18
|
+
require 'builder'
|
19
|
+
require 'gyoku'
|
20
|
+
require 'base64'
|
21
|
+
|
22
|
+
# SOAP constants for WinRM
|
23
|
+
module WinRM
|
24
|
+
NS_SOAP_ENV = 's' # http://www.w3.org/2003/05/soap-envelope
|
25
|
+
NS_ADDRESSING = 'a' # http://schemas.xmlsoap.org/ws/2004/08/addressing
|
26
|
+
NS_CIMBINDING = 'b' # http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd
|
27
|
+
NS_ENUM = 'n' # http://schemas.xmlsoap.org/ws/2004/09/enumeration
|
28
|
+
NS_TRANSFER = 'x' # http://schemas.xmlsoap.org/ws/2004/09/transfer
|
29
|
+
NS_WSMAN_DMTF = 'w' # http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
|
30
|
+
NS_WSMAN_MSFT = 'p' # http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
|
31
|
+
NS_SCHEMA_INST = 'xsi' # http://www.w3.org/2001/XMLSchema-instance
|
32
|
+
NS_WIN_SHELL = 'rsp' # http://schemas.microsoft.com/wbem/wsman/1/windows/shell
|
33
|
+
NS_WSMAN_FAULT = 'f' # http://schemas.microsoft.com/wbem/wsman/1/wsmanfault
|
34
|
+
NS_WSMAN_CONF = 'cfg' # http://schemas.microsoft.com/wbem/wsman/1/config
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'winrm/exceptions/exceptions'
|
38
|
+
require 'winrm/winrm_service'
|
39
|
+
require 'winrm/http/transport'
|
data/lib/winrm/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
# WinRM module
|
4
|
-
module WinRM
|
5
|
-
# The version of the WinRM library
|
6
|
-
VERSION = '1.7.
|
7
|
-
end
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# WinRM module
|
4
|
+
module WinRM
|
5
|
+
# The version of the WinRM library
|
6
|
+
VERSION = '1.7.1'
|
7
|
+
end
|
data/lib/winrm/winrm_service.rb
CHANGED
@@ -1,556 +1,556 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
#
|
3
|
-
# Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
|
17
|
-
require 'nori'
|
18
|
-
require 'rexml/document'
|
19
|
-
require 'securerandom'
|
20
|
-
require 'winrm/command_executor'
|
21
|
-
require_relative 'helpers/powershell_script'
|
22
|
-
|
23
|
-
module WinRM
|
24
|
-
# This is the main class that does the SOAP request/response logic. There are a few helper
|
25
|
-
# classes, but pretty much everything comes through here first.
|
26
|
-
class WinRMWebService
|
27
|
-
DEFAULT_TIMEOUT = 'PT60S'
|
28
|
-
DEFAULT_MAX_ENV_SIZE = 153600
|
29
|
-
DEFAULT_LOCALE = 'en-US'
|
30
|
-
|
31
|
-
attr_reader :endpoint, :timeout, :retry_limit, :retry_delay
|
32
|
-
|
33
|
-
attr_accessor :logger
|
34
|
-
|
35
|
-
# @param [String,URI] endpoint the WinRM webservice endpoint
|
36
|
-
# @param [Symbol] transport either :kerberos(default)/:ssl/:plaintext
|
37
|
-
# @param [Hash] opts Misc opts for the various transports.
|
38
|
-
# @see WinRM::HTTP::HttpTransport
|
39
|
-
# @see WinRM::HTTP::HttpGSSAPI
|
40
|
-
# @see WinRM::HTTP::HttpNegotiate
|
41
|
-
# @see WinRM::HTTP::HttpSSL
|
42
|
-
def initialize(endpoint, transport = :kerberos, opts = {})
|
43
|
-
@endpoint = endpoint
|
44
|
-
@timeout = DEFAULT_TIMEOUT
|
45
|
-
@max_env_sz = DEFAULT_MAX_ENV_SIZE
|
46
|
-
@locale = DEFAULT_LOCALE
|
47
|
-
setup_logger
|
48
|
-
configure_retries(opts)
|
49
|
-
begin
|
50
|
-
@xfer = send "init_#{transport}_transport", opts.merge({endpoint: endpoint})
|
51
|
-
rescue NoMethodError => e
|
52
|
-
raise "Invalid transport '#{transport}' specified, expected: negotiate, kerberos, plaintext, ssl."
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def init_negotiate_transport(opts)
|
57
|
-
HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
58
|
-
end
|
59
|
-
|
60
|
-
def init_kerberos_transport(opts)
|
61
|
-
require 'gssapi'
|
62
|
-
require 'gssapi/extensions'
|
63
|
-
HTTP::HttpGSSAPI.new(opts[:endpoint], opts[:realm], opts[:service], opts[:keytab], opts)
|
64
|
-
end
|
65
|
-
|
66
|
-
def init_plaintext_transport(opts)
|
67
|
-
HTTP::HttpPlaintext.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
68
|
-
end
|
69
|
-
|
70
|
-
def init_ssl_transport(opts)
|
71
|
-
if opts[:basic_auth_only]
|
72
|
-
HTTP::BasicAuthSSL.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
73
|
-
else
|
74
|
-
HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# Operation timeout.
|
79
|
-
#
|
80
|
-
# Unless specified the client receive timeout will be 10s + the operation
|
81
|
-
# timeout.
|
82
|
-
#
|
83
|
-
# @see http://msdn.microsoft.com/en-us/library/ee916629(v=PROT.13).aspx
|
84
|
-
#
|
85
|
-
# @param [Fixnum] The number of seconds to set the WinRM operation timeout
|
86
|
-
# @param [Fixnum] The number of seconds to set the Ruby receive timeout
|
87
|
-
# @return [String] The ISO 8601 formatted operation timeout
|
88
|
-
def set_timeout(op_timeout_sec, receive_timeout_sec=nil)
|
89
|
-
@timeout = Iso8601Duration.sec_to_dur(op_timeout_sec)
|
90
|
-
@xfer.receive_timeout = receive_timeout_sec || op_timeout_sec + 10
|
91
|
-
@timeout
|
92
|
-
end
|
93
|
-
alias :op_timeout :set_timeout
|
94
|
-
|
95
|
-
# Max envelope size
|
96
|
-
# @see http://msdn.microsoft.com/en-us/library/ee916127(v=PROT.13).aspx
|
97
|
-
# @param [Fixnum] byte_sz the max size in bytes to allow for the response
|
98
|
-
def max_env_size(byte_sz)
|
99
|
-
@max_env_sz = byte_sz
|
100
|
-
end
|
101
|
-
|
102
|
-
# Set the locale
|
103
|
-
# @see http://msdn.microsoft.com/en-us/library/gg567404(v=PROT.13).aspx
|
104
|
-
# @param [String] locale the locale to set for future messages
|
105
|
-
def locale(locale)
|
106
|
-
@locale = locale
|
107
|
-
end
|
108
|
-
|
109
|
-
# Create a Shell on the destination host
|
110
|
-
# @param [Hash<optional>] shell_opts additional shell options you can pass
|
111
|
-
# @option shell_opts [String] :i_stream Which input stream to open. Leave this alone unless you know what you're doing (default: stdin)
|
112
|
-
# @option shell_opts [String] :o_stream Which output stream to open. Leave this alone unless you know what you're doing (default: stdout stderr)
|
113
|
-
# @option shell_opts [String] :working_directory the directory to create the shell in
|
114
|
-
# @option shell_opts [Hash] :env_vars environment variables to set for the shell. For instance;
|
115
|
-
# :env_vars => {:myvar1 => 'val1', :myvar2 => 'var2'}
|
116
|
-
# @return [String] The ShellId from the SOAP response. This is our open shell instance on the remote machine.
|
117
|
-
def open_shell(shell_opts = {}, &block)
|
118
|
-
logger.debug("[WinRM] opening remote shell on #{@endpoint}")
|
119
|
-
i_stream = shell_opts.has_key?(:i_stream) ? shell_opts[:i_stream] : 'stdin'
|
120
|
-
o_stream = shell_opts.has_key?(:o_stream) ? shell_opts[:o_stream] : 'stdout stderr'
|
121
|
-
codepage = shell_opts.has_key?(:codepage) ? shell_opts[:codepage] : 65001 # utf8 as default codepage (from https://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx)
|
122
|
-
noprofile = shell_opts.has_key?(:noprofile) ? shell_opts[:noprofile] : 'FALSE'
|
123
|
-
h_opts = { "#{NS_WSMAN_DMTF}:OptionSet" => { "#{NS_WSMAN_DMTF}:Option" => [noprofile, codepage],
|
124
|
-
:attributes! => {"#{NS_WSMAN_DMTF}:Option" => {'Name' => ['WINRS_NOPROFILE','WINRS_CODEPAGE']}}}}
|
125
|
-
shell_body = {
|
126
|
-
"#{NS_WIN_SHELL}:InputStreams" => i_stream,
|
127
|
-
"#{NS_WIN_SHELL}:OutputStreams" => o_stream
|
128
|
-
}
|
129
|
-
shell_body["#{NS_WIN_SHELL}:WorkingDirectory"] = shell_opts[:working_directory] if shell_opts.has_key?(:working_directory)
|
130
|
-
shell_body["#{NS_WIN_SHELL}:IdleTimeOut"] = shell_opts[:idle_timeout] if(shell_opts.has_key?(:idle_timeout) && shell_opts[:idle_timeout].is_a?(String))
|
131
|
-
if(shell_opts.has_key?(:env_vars) && shell_opts[:env_vars].is_a?(Hash))
|
132
|
-
keys = shell_opts[:env_vars].keys
|
133
|
-
vals = shell_opts[:env_vars].values
|
134
|
-
shell_body["#{NS_WIN_SHELL}:Environment"] = {
|
135
|
-
"#{NS_WIN_SHELL}:Variable" => vals,
|
136
|
-
:attributes! => {"#{NS_WIN_SHELL}:Variable" => {'Name' => keys}}
|
137
|
-
}
|
138
|
-
end
|
139
|
-
builder = Builder::XmlMarkup.new
|
140
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
141
|
-
builder.tag! :env, :Envelope, namespaces do |env|
|
142
|
-
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_create,h_opts)) }
|
143
|
-
env.tag! :env, :Body do |body|
|
144
|
-
body.tag!("#{NS_WIN_SHELL}:Shell") { |s| s << Gyoku.xml(shell_body)}
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
resp_doc = send_message(builder.target!)
|
149
|
-
shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
|
150
|
-
logger.debug("[WinRM] remote shell #{shell_id} is open on #{@endpoint}")
|
151
|
-
|
152
|
-
if block_given?
|
153
|
-
begin
|
154
|
-
yield shell_id
|
155
|
-
ensure
|
156
|
-
close_shell(shell_id)
|
157
|
-
end
|
158
|
-
else
|
159
|
-
shell_id
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# Run a command on a machine with an open shell
|
164
|
-
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
165
|
-
# @param [String] command The command to run on the remote machine
|
166
|
-
# @param [Array<String>] arguments An array of arguments for this command
|
167
|
-
# @return [String] The CommandId from the SOAP response. This is the ID we need to query in order to get output.
|
168
|
-
def run_command(shell_id, command, arguments = [], cmd_opts = {}, &block)
|
169
|
-
consolemode = cmd_opts.has_key?(:console_mode_stdin) ? cmd_opts[:console_mode_stdin] : 'TRUE'
|
170
|
-
skipcmd = cmd_opts.has_key?(:skip_cmd_shell) ? cmd_opts[:skip_cmd_shell] : 'FALSE'
|
171
|
-
|
172
|
-
h_opts = { "#{NS_WSMAN_DMTF}:OptionSet" => {
|
173
|
-
"#{NS_WSMAN_DMTF}:Option" => [consolemode, skipcmd],
|
174
|
-
:attributes! => {"#{NS_WSMAN_DMTF}:Option" => {'Name' => ['WINRS_CONSOLEMODE_STDIN','WINRS_SKIP_CMD_SHELL']}}}
|
175
|
-
}
|
176
|
-
body = { "#{NS_WIN_SHELL}:Command" => "\"#{command}\"", "#{NS_WIN_SHELL}:Arguments" => arguments}
|
177
|
-
|
178
|
-
builder = Builder::XmlMarkup.new
|
179
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
180
|
-
builder.tag! :env, :Envelope, namespaces do |env|
|
181
|
-
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_command,h_opts,selector_shell_id(shell_id))) }
|
182
|
-
env.tag!(:env, :Body) do |env_body|
|
183
|
-
env_body.tag!("#{NS_WIN_SHELL}:CommandLine") { |cl| cl << Gyoku.xml(body) }
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Grab the command element and unescape any single quotes - issue 69
|
188
|
-
xml = builder.target!
|
189
|
-
escaped_cmd = /<#{NS_WIN_SHELL}:Command>(.+)<\/#{NS_WIN_SHELL}:Command>/m.match(xml)[1]
|
190
|
-
xml[escaped_cmd] = escaped_cmd.gsub(/'/, "'")
|
191
|
-
|
192
|
-
resp_doc = send_message(xml)
|
193
|
-
command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
|
194
|
-
|
195
|
-
if block_given?
|
196
|
-
begin
|
197
|
-
yield command_id
|
198
|
-
ensure
|
199
|
-
cleanup_command(shell_id, command_id)
|
200
|
-
end
|
201
|
-
else
|
202
|
-
command_id
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def write_stdin(shell_id, command_id, stdin)
|
207
|
-
# Signal the Command references to terminate (close stdout/stderr)
|
208
|
-
body = {
|
209
|
-
"#{NS_WIN_SHELL}:Send" => {
|
210
|
-
"#{NS_WIN_SHELL}:Stream" => {
|
211
|
-
"@Name" => 'stdin',
|
212
|
-
"@CommandId" => command_id,
|
213
|
-
:content! => Base64.encode64(stdin)
|
214
|
-
}
|
215
|
-
}
|
216
|
-
}
|
217
|
-
builder = Builder::XmlMarkup.new
|
218
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
219
|
-
builder.tag! :env, :Envelope, namespaces do |env|
|
220
|
-
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_send,selector_shell_id(shell_id))) }
|
221
|
-
env.tag!(:env, :Body) do |env_body|
|
222
|
-
env_body << Gyoku.xml(body)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
resp = send_message(builder.target!)
|
226
|
-
true
|
227
|
-
end
|
228
|
-
|
229
|
-
# Get the Output of the given shell and command
|
230
|
-
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
231
|
-
# @param [String] command_id The command id on the remote machine. See #run_command
|
232
|
-
# @return [Hash] Returns a Hash with a key :exitcode and :data. Data is an Array of Hashes where the cooresponding key
|
233
|
-
# 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
|
234
|
-
# the console.
|
235
|
-
def get_command_output(shell_id, command_id, &block)
|
236
|
-
body = { "#{NS_WIN_SHELL}:DesiredStream" => 'stdout stderr',
|
237
|
-
:attributes! => {"#{NS_WIN_SHELL}:DesiredStream" => {'CommandId' => command_id}}}
|
238
|
-
|
239
|
-
builder = Builder::XmlMarkup.new
|
240
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
241
|
-
builder.tag! :env, :Envelope, namespaces do |env|
|
242
|
-
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_receive,selector_shell_id(shell_id))) }
|
243
|
-
env.tag!(:env, :Body) do |env_body|
|
244
|
-
env_body.tag!("#{NS_WIN_SHELL}:Receive") { |cl| cl << Gyoku.xml(body) }
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
resp_doc = nil
|
249
|
-
request_msg = builder.target!
|
250
|
-
done_elems = []
|
251
|
-
output = Output.new
|
252
|
-
|
253
|
-
while done_elems.empty?
|
254
|
-
resp_doc = send_get_output_message(request_msg)
|
255
|
-
|
256
|
-
REXML::XPath.match(resp_doc, "//#{NS_WIN_SHELL}:Stream").each do |n|
|
257
|
-
next if n.text.nil? || n.text.empty?
|
258
|
-
|
259
|
-
# decode and replace invalid unicode characters
|
260
|
-
decoded_text = Base64.decode64(n.text).force_encoding('utf-8')
|
261
|
-
if ! decoded_text.valid_encoding?
|
262
|
-
if decoded_text.respond_to?(:scrub!)
|
263
|
-
decoded_text.scrub!
|
264
|
-
else
|
265
|
-
decoded_text = decoded_text.encode('utf-16', invalid: :replace, undef: :replace)
|
266
|
-
.encode('utf-8')
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# remove BOM which 2008R2 applies
|
271
|
-
stream = { n.attributes['Name'].to_sym => decoded_text.sub('\xEF\xBB\xBF', '') }
|
272
|
-
output[:data] << stream
|
273
|
-
yield stream[:stdout], stream[:stderr] if block_given?
|
274
|
-
end
|
275
|
-
|
276
|
-
# We may need to get additional output if the stream has not finished.
|
277
|
-
# The CommandState will change from Running to Done like so:
|
278
|
-
# @example
|
279
|
-
# from...
|
280
|
-
# <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Running"/>
|
281
|
-
# to...
|
282
|
-
# <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
|
283
|
-
# <rsp:ExitCode>0</rsp:ExitCode>
|
284
|
-
# </rsp:CommandState>
|
285
|
-
done_elems = REXML::XPath.match(resp_doc, "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']")
|
286
|
-
end
|
287
|
-
|
288
|
-
output[:exitcode] = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:ExitCode").text.to_i
|
289
|
-
output
|
290
|
-
end
|
291
|
-
|
292
|
-
# Clean-up after a command.
|
293
|
-
# @see #run_command
|
294
|
-
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
295
|
-
# @param [String] command_id The command id on the remote machine. See #run_command
|
296
|
-
# @return [true] This should have more error checking but it just returns true for now.
|
297
|
-
def cleanup_command(shell_id, command_id)
|
298
|
-
# Signal the Command references to terminate (close stdout/stderr)
|
299
|
-
body = { "#{NS_WIN_SHELL}:Code" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate' }
|
300
|
-
builder = Builder::XmlMarkup.new
|
301
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
302
|
-
builder.tag! :env, :Envelope, namespaces do |env|
|
303
|
-
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_signal,selector_shell_id(shell_id))) }
|
304
|
-
env.tag!(:env, :Body) do |env_body|
|
305
|
-
env_body.tag!("#{NS_WIN_SHELL}:Signal", {'CommandId' => command_id}) { |cl| cl << Gyoku.xml(body) }
|
306
|
-
end
|
307
|
-
end
|
308
|
-
resp = send_message(builder.target!)
|
309
|
-
true
|
310
|
-
end
|
311
|
-
|
312
|
-
# Close the shell
|
313
|
-
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
314
|
-
# @return [true] This should have more error checking but it just returns true for now.
|
315
|
-
def close_shell(shell_id)
|
316
|
-
logger.debug("[WinRM] closing remote shell #{shell_id} on #{@endpoint}")
|
317
|
-
builder = Builder::XmlMarkup.new
|
318
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
319
|
-
|
320
|
-
builder.tag!('env:Envelope', namespaces) do |env|
|
321
|
-
env.tag!('env:Header') { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_delete,selector_shell_id(shell_id))) }
|
322
|
-
env.tag!('env:Body')
|
323
|
-
end
|
324
|
-
|
325
|
-
resp = send_message(builder.target!)
|
326
|
-
logger.debug("[WinRM] remote shell #{shell_id} closed")
|
327
|
-
true
|
328
|
-
end
|
329
|
-
|
330
|
-
# DEPRECATED: Use WinRM::CommandExecutor#run_cmd instead
|
331
|
-
# Run a CMD command
|
332
|
-
# @param [String] command The command to run on the remote system
|
333
|
-
# @param [Array <String>] arguments arguments to the command
|
334
|
-
# @param [String] an existing and open shell id to reuse
|
335
|
-
# @return [Hash] :stdout and :stderr
|
336
|
-
def run_cmd(command, arguments = [], &block)
|
337
|
-
logger.warn("WinRM::WinRMWebService#run_cmd is deprecated. Use WinRM::CommandExecutor#run_cmd instead")
|
338
|
-
create_executor do |executor|
|
339
|
-
executor.run_cmd(command, arguments, &block)
|
340
|
-
end
|
341
|
-
end
|
342
|
-
alias :cmd :run_cmd
|
343
|
-
|
344
|
-
# DEPRECATED: Use WinRM::CommandExecutor#run_powershell_script instead
|
345
|
-
# Run a Powershell script that resides on the local box.
|
346
|
-
# @param [IO,String] script_file an IO reference for reading the Powershell script or the actual file contents
|
347
|
-
# @param [String] an existing and open shell id to reuse
|
348
|
-
# @return [Hash] :stdout and :stderr
|
349
|
-
def run_powershell_script(script_file, &block)
|
350
|
-
logger.warn("WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead")
|
351
|
-
create_executor do |executor|
|
352
|
-
executor.run_powershell_script(script_file, &block)
|
353
|
-
end
|
354
|
-
end
|
355
|
-
alias :powershell :run_powershell_script
|
356
|
-
|
357
|
-
# Creates a CommandExecutor initialized with this WinRMWebService
|
358
|
-
# If called with a block, create_executor yields an executor and
|
359
|
-
# ensures that the executor is closed after the block completes.
|
360
|
-
# The CommandExecutor is simply returned if no block is given.
|
361
|
-
# @yieldparam [CommandExecutor] a CommandExecutor instance
|
362
|
-
# @return [CommandExecutor] a CommandExecutor instance
|
363
|
-
def create_executor(&block)
|
364
|
-
executor = CommandExecutor.new(self)
|
365
|
-
executor.open
|
366
|
-
|
367
|
-
if block_given?
|
368
|
-
begin
|
369
|
-
yield executor
|
370
|
-
ensure
|
371
|
-
executor.close
|
372
|
-
end
|
373
|
-
else
|
374
|
-
executor
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
|
-
# Run a WQL Query
|
379
|
-
# @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
|
380
|
-
# @param [String] wql The WQL query
|
381
|
-
# @return [Hash] Returns a Hash that contain the key/value pairs returned from the query.
|
382
|
-
def run_wql(wql)
|
383
|
-
|
384
|
-
body = { "#{NS_WSMAN_DMTF}:OptimizeEnumeration" => nil,
|
385
|
-
"#{NS_WSMAN_DMTF}:MaxElements" => '32000',
|
386
|
-
"#{NS_WSMAN_DMTF}:Filter" => wql,
|
387
|
-
:attributes! => { "#{NS_WSMAN_DMTF}:Filter" => {'Dialect' => 'http://schemas.microsoft.com/wbem/wsman/1/WQL'}}
|
388
|
-
}
|
389
|
-
|
390
|
-
builder = Builder::XmlMarkup.new
|
391
|
-
builder.instruct!(:xml, :encoding => 'UTF-8')
|
392
|
-
builder.tag! :env, :Envelope, namespaces do |env|
|
393
|
-
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_wmi,action_enumerate)) }
|
394
|
-
env.tag!(:env, :Body) do |env_body|
|
395
|
-
env_body.tag!("#{NS_ENUM}:Enumerate") { |en| en << Gyoku.xml(body) }
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
resp = send_message(builder.target!)
|
400
|
-
parser = Nori.new(:parser => :rexml, :advanced_typecasting => false, :convert_tags_to => lambda { |tag| tag.snakecase.to_sym }, :strip_namespaces => true)
|
401
|
-
hresp = parser.parse(resp.to_s)[:envelope][:body]
|
402
|
-
|
403
|
-
# Normalize items so the type always has an array even if it's just a single item.
|
404
|
-
items = {}
|
405
|
-
if hresp[:enumerate_response][:items]
|
406
|
-
hresp[:enumerate_response][:items].each_pair do |k,v|
|
407
|
-
if v.is_a?(Array)
|
408
|
-
items[k] = v
|
409
|
-
else
|
410
|
-
items[k] = [v]
|
411
|
-
end
|
412
|
-
end
|
413
|
-
end
|
414
|
-
items
|
415
|
-
end
|
416
|
-
alias :wql :run_wql
|
417
|
-
|
418
|
-
def toggle_nori_type_casting(to)
|
419
|
-
logger.warn('toggle_nori_type_casting is deprecated and has no effect, ' +
|
420
|
-
'please remove calls to it')
|
421
|
-
end
|
422
|
-
|
423
|
-
private
|
424
|
-
|
425
|
-
def setup_logger
|
426
|
-
@logger = Logging.logger[self]
|
427
|
-
@logger.level = :warn
|
428
|
-
@logger.add_appenders(Logging.appenders.stdout)
|
429
|
-
end
|
430
|
-
|
431
|
-
def configure_retries(opts)
|
432
|
-
@retry_delay = opts[:retry_delay] || 10
|
433
|
-
@retry_limit = opts[:retry_limit] || 3
|
434
|
-
end
|
435
|
-
|
436
|
-
def namespaces
|
437
|
-
{
|
438
|
-
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
439
|
-
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
440
|
-
'xmlns:env' => 'http://www.w3.org/2003/05/soap-envelope',
|
441
|
-
'xmlns:a' => 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
|
442
|
-
'xmlns:b' => 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd',
|
443
|
-
'xmlns:n' => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration',
|
444
|
-
'xmlns:x' => 'http://schemas.xmlsoap.org/ws/2004/09/transfer',
|
445
|
-
'xmlns:w' => 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd',
|
446
|
-
'xmlns:p' => 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd',
|
447
|
-
'xmlns:rsp' => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell',
|
448
|
-
'xmlns:cfg' => 'http://schemas.microsoft.com/wbem/wsman/1/config',
|
449
|
-
}
|
450
|
-
end
|
451
|
-
|
452
|
-
def header
|
453
|
-
{ "#{NS_ADDRESSING}:To" => "#{@xfer.endpoint.to_s}",
|
454
|
-
"#{NS_ADDRESSING}:ReplyTo" => {
|
455
|
-
"#{NS_ADDRESSING}:Address" => 'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous',
|
456
|
-
:attributes! => {"#{NS_ADDRESSING}:Address" => {'mustUnderstand' => true}}},
|
457
|
-
"#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => @max_env_sz,
|
458
|
-
"#{NS_ADDRESSING}:MessageID" => "uuid:#{SecureRandom.uuid.to_s.upcase}",
|
459
|
-
"#{NS_WSMAN_DMTF}:Locale/" => '',
|
460
|
-
"#{NS_WSMAN_MSFT}:DataLocale/" => '',
|
461
|
-
"#{NS_WSMAN_DMTF}:OperationTimeout" => @timeout,
|
462
|
-
:attributes! => {
|
463
|
-
"#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => {'mustUnderstand' => true},
|
464
|
-
"#{NS_WSMAN_DMTF}:Locale/" => {'xml:lang' => @locale, 'mustUnderstand' => false},
|
465
|
-
"#{NS_WSMAN_MSFT}:DataLocale/" => {'xml:lang' => @locale, 'mustUnderstand' => false}
|
466
|
-
}}
|
467
|
-
end
|
468
|
-
|
469
|
-
# merge the various header hashes and make sure we carry all of the attributes
|
470
|
-
# through instead of overwriting them.
|
471
|
-
def merge_headers(*headers)
|
472
|
-
hdr = {}
|
473
|
-
headers.each do |h|
|
474
|
-
hdr.merge!(h) do |k,v1,v2|
|
475
|
-
v1.merge!(v2) if k == :attributes!
|
476
|
-
end
|
477
|
-
end
|
478
|
-
hdr
|
479
|
-
end
|
480
|
-
|
481
|
-
def send_get_output_message(message)
|
482
|
-
send_message(message)
|
483
|
-
rescue WinRMWSManFault => e
|
484
|
-
# If no output is available before the wsman:OperationTimeout expires,
|
485
|
-
# the server MUST return a WSManFault with the Code attribute equal to
|
486
|
-
# 2150858793. When the client receives this fault, it SHOULD issue
|
487
|
-
# another Receive request.
|
488
|
-
# http://msdn.microsoft.com/en-us/library/cc251676.aspx
|
489
|
-
if e.fault_code == '2150858793'
|
490
|
-
logger.debug("[WinRM] retrying receive request after timeout")
|
491
|
-
retry
|
492
|
-
else
|
493
|
-
raise
|
494
|
-
end
|
495
|
-
end
|
496
|
-
|
497
|
-
def send_message(message)
|
498
|
-
@xfer.send_request(message)
|
499
|
-
end
|
500
|
-
|
501
|
-
|
502
|
-
# Helper methods for SOAP Headers
|
503
|
-
|
504
|
-
def resource_uri_cmd
|
505
|
-
{"#{NS_WSMAN_DMTF}:ResourceURI" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
506
|
-
:attributes! => {"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => true}}}
|
507
|
-
end
|
508
|
-
|
509
|
-
def resource_uri_wmi(namespace = 'root/cimv2/*')
|
510
|
-
{"#{NS_WSMAN_DMTF}:ResourceURI" => "http://schemas.microsoft.com/wbem/wsman/1/wmi/#{namespace}",
|
511
|
-
:attributes! => {"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => true}}}
|
512
|
-
end
|
513
|
-
|
514
|
-
def action_create
|
515
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Create',
|
516
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
517
|
-
end
|
518
|
-
|
519
|
-
def action_delete
|
520
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete',
|
521
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
522
|
-
end
|
523
|
-
|
524
|
-
def action_command
|
525
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command',
|
526
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
527
|
-
end
|
528
|
-
|
529
|
-
def action_receive
|
530
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive',
|
531
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
532
|
-
end
|
533
|
-
|
534
|
-
def action_signal
|
535
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal',
|
536
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
537
|
-
end
|
538
|
-
|
539
|
-
def action_send
|
540
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send',
|
541
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
542
|
-
end
|
543
|
-
|
544
|
-
def action_enumerate
|
545
|
-
{"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate',
|
546
|
-
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
547
|
-
end
|
548
|
-
|
549
|
-
def selector_shell_id(shell_id)
|
550
|
-
{"#{NS_WSMAN_DMTF}:SelectorSet" =>
|
551
|
-
{"#{NS_WSMAN_DMTF}:Selector" => shell_id, :attributes! => {"#{NS_WSMAN_DMTF}:Selector" => {'Name' => 'ShellId'}}}
|
552
|
-
}
|
553
|
-
end
|
554
|
-
|
555
|
-
end # WinRMWebService
|
556
|
-
end # WinRM
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'nori'
|
18
|
+
require 'rexml/document'
|
19
|
+
require 'securerandom'
|
20
|
+
require 'winrm/command_executor'
|
21
|
+
require_relative 'helpers/powershell_script'
|
22
|
+
|
23
|
+
module WinRM
|
24
|
+
# This is the main class that does the SOAP request/response logic. There are a few helper
|
25
|
+
# classes, but pretty much everything comes through here first.
|
26
|
+
class WinRMWebService
|
27
|
+
DEFAULT_TIMEOUT = 'PT60S'
|
28
|
+
DEFAULT_MAX_ENV_SIZE = 153600
|
29
|
+
DEFAULT_LOCALE = 'en-US'
|
30
|
+
|
31
|
+
attr_reader :endpoint, :timeout, :retry_limit, :retry_delay
|
32
|
+
|
33
|
+
attr_accessor :logger
|
34
|
+
|
35
|
+
# @param [String,URI] endpoint the WinRM webservice endpoint
|
36
|
+
# @param [Symbol] transport either :kerberos(default)/:ssl/:plaintext
|
37
|
+
# @param [Hash] opts Misc opts for the various transports.
|
38
|
+
# @see WinRM::HTTP::HttpTransport
|
39
|
+
# @see WinRM::HTTP::HttpGSSAPI
|
40
|
+
# @see WinRM::HTTP::HttpNegotiate
|
41
|
+
# @see WinRM::HTTP::HttpSSL
|
42
|
+
def initialize(endpoint, transport = :kerberos, opts = {})
|
43
|
+
@endpoint = endpoint
|
44
|
+
@timeout = DEFAULT_TIMEOUT
|
45
|
+
@max_env_sz = DEFAULT_MAX_ENV_SIZE
|
46
|
+
@locale = DEFAULT_LOCALE
|
47
|
+
setup_logger
|
48
|
+
configure_retries(opts)
|
49
|
+
begin
|
50
|
+
@xfer = send "init_#{transport}_transport", opts.merge({endpoint: endpoint})
|
51
|
+
rescue NoMethodError => e
|
52
|
+
raise "Invalid transport '#{transport}' specified, expected: negotiate, kerberos, plaintext, ssl."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def init_negotiate_transport(opts)
|
57
|
+
HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
58
|
+
end
|
59
|
+
|
60
|
+
def init_kerberos_transport(opts)
|
61
|
+
require 'gssapi'
|
62
|
+
require 'gssapi/extensions'
|
63
|
+
HTTP::HttpGSSAPI.new(opts[:endpoint], opts[:realm], opts[:service], opts[:keytab], opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
def init_plaintext_transport(opts)
|
67
|
+
HTTP::HttpPlaintext.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
68
|
+
end
|
69
|
+
|
70
|
+
def init_ssl_transport(opts)
|
71
|
+
if opts[:basic_auth_only]
|
72
|
+
HTTP::BasicAuthSSL.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
73
|
+
else
|
74
|
+
HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:pass], opts)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Operation timeout.
|
79
|
+
#
|
80
|
+
# Unless specified the client receive timeout will be 10s + the operation
|
81
|
+
# timeout.
|
82
|
+
#
|
83
|
+
# @see http://msdn.microsoft.com/en-us/library/ee916629(v=PROT.13).aspx
|
84
|
+
#
|
85
|
+
# @param [Fixnum] The number of seconds to set the WinRM operation timeout
|
86
|
+
# @param [Fixnum] The number of seconds to set the Ruby receive timeout
|
87
|
+
# @return [String] The ISO 8601 formatted operation timeout
|
88
|
+
def set_timeout(op_timeout_sec, receive_timeout_sec=nil)
|
89
|
+
@timeout = Iso8601Duration.sec_to_dur(op_timeout_sec)
|
90
|
+
@xfer.receive_timeout = receive_timeout_sec || op_timeout_sec + 10
|
91
|
+
@timeout
|
92
|
+
end
|
93
|
+
alias :op_timeout :set_timeout
|
94
|
+
|
95
|
+
# Max envelope size
|
96
|
+
# @see http://msdn.microsoft.com/en-us/library/ee916127(v=PROT.13).aspx
|
97
|
+
# @param [Fixnum] byte_sz the max size in bytes to allow for the response
|
98
|
+
def max_env_size(byte_sz)
|
99
|
+
@max_env_sz = byte_sz
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set the locale
|
103
|
+
# @see http://msdn.microsoft.com/en-us/library/gg567404(v=PROT.13).aspx
|
104
|
+
# @param [String] locale the locale to set for future messages
|
105
|
+
def locale(locale)
|
106
|
+
@locale = locale
|
107
|
+
end
|
108
|
+
|
109
|
+
# Create a Shell on the destination host
|
110
|
+
# @param [Hash<optional>] shell_opts additional shell options you can pass
|
111
|
+
# @option shell_opts [String] :i_stream Which input stream to open. Leave this alone unless you know what you're doing (default: stdin)
|
112
|
+
# @option shell_opts [String] :o_stream Which output stream to open. Leave this alone unless you know what you're doing (default: stdout stderr)
|
113
|
+
# @option shell_opts [String] :working_directory the directory to create the shell in
|
114
|
+
# @option shell_opts [Hash] :env_vars environment variables to set for the shell. For instance;
|
115
|
+
# :env_vars => {:myvar1 => 'val1', :myvar2 => 'var2'}
|
116
|
+
# @return [String] The ShellId from the SOAP response. This is our open shell instance on the remote machine.
|
117
|
+
def open_shell(shell_opts = {}, &block)
|
118
|
+
logger.debug("[WinRM] opening remote shell on #{@endpoint}")
|
119
|
+
i_stream = shell_opts.has_key?(:i_stream) ? shell_opts[:i_stream] : 'stdin'
|
120
|
+
o_stream = shell_opts.has_key?(:o_stream) ? shell_opts[:o_stream] : 'stdout stderr'
|
121
|
+
codepage = shell_opts.has_key?(:codepage) ? shell_opts[:codepage] : 65001 # utf8 as default codepage (from https://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx)
|
122
|
+
noprofile = shell_opts.has_key?(:noprofile) ? shell_opts[:noprofile] : 'FALSE'
|
123
|
+
h_opts = { "#{NS_WSMAN_DMTF}:OptionSet" => { "#{NS_WSMAN_DMTF}:Option" => [noprofile, codepage],
|
124
|
+
:attributes! => {"#{NS_WSMAN_DMTF}:Option" => {'Name' => ['WINRS_NOPROFILE','WINRS_CODEPAGE']}}}}
|
125
|
+
shell_body = {
|
126
|
+
"#{NS_WIN_SHELL}:InputStreams" => i_stream,
|
127
|
+
"#{NS_WIN_SHELL}:OutputStreams" => o_stream
|
128
|
+
}
|
129
|
+
shell_body["#{NS_WIN_SHELL}:WorkingDirectory"] = shell_opts[:working_directory] if shell_opts.has_key?(:working_directory)
|
130
|
+
shell_body["#{NS_WIN_SHELL}:IdleTimeOut"] = shell_opts[:idle_timeout] if(shell_opts.has_key?(:idle_timeout) && shell_opts[:idle_timeout].is_a?(String))
|
131
|
+
if(shell_opts.has_key?(:env_vars) && shell_opts[:env_vars].is_a?(Hash))
|
132
|
+
keys = shell_opts[:env_vars].keys
|
133
|
+
vals = shell_opts[:env_vars].values
|
134
|
+
shell_body["#{NS_WIN_SHELL}:Environment"] = {
|
135
|
+
"#{NS_WIN_SHELL}:Variable" => vals,
|
136
|
+
:attributes! => {"#{NS_WIN_SHELL}:Variable" => {'Name' => keys}}
|
137
|
+
}
|
138
|
+
end
|
139
|
+
builder = Builder::XmlMarkup.new
|
140
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
141
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
142
|
+
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_create,h_opts)) }
|
143
|
+
env.tag! :env, :Body do |body|
|
144
|
+
body.tag!("#{NS_WIN_SHELL}:Shell") { |s| s << Gyoku.xml(shell_body)}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
resp_doc = send_message(builder.target!)
|
149
|
+
shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
|
150
|
+
logger.debug("[WinRM] remote shell #{shell_id} is open on #{@endpoint}")
|
151
|
+
|
152
|
+
if block_given?
|
153
|
+
begin
|
154
|
+
yield shell_id
|
155
|
+
ensure
|
156
|
+
close_shell(shell_id)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
shell_id
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Run a command on a machine with an open shell
|
164
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
165
|
+
# @param [String] command The command to run on the remote machine
|
166
|
+
# @param [Array<String>] arguments An array of arguments for this command
|
167
|
+
# @return [String] The CommandId from the SOAP response. This is the ID we need to query in order to get output.
|
168
|
+
def run_command(shell_id, command, arguments = [], cmd_opts = {}, &block)
|
169
|
+
consolemode = cmd_opts.has_key?(:console_mode_stdin) ? cmd_opts[:console_mode_stdin] : 'TRUE'
|
170
|
+
skipcmd = cmd_opts.has_key?(:skip_cmd_shell) ? cmd_opts[:skip_cmd_shell] : 'FALSE'
|
171
|
+
|
172
|
+
h_opts = { "#{NS_WSMAN_DMTF}:OptionSet" => {
|
173
|
+
"#{NS_WSMAN_DMTF}:Option" => [consolemode, skipcmd],
|
174
|
+
:attributes! => {"#{NS_WSMAN_DMTF}:Option" => {'Name' => ['WINRS_CONSOLEMODE_STDIN','WINRS_SKIP_CMD_SHELL']}}}
|
175
|
+
}
|
176
|
+
body = { "#{NS_WIN_SHELL}:Command" => "\"#{command}\"", "#{NS_WIN_SHELL}:Arguments" => arguments}
|
177
|
+
|
178
|
+
builder = Builder::XmlMarkup.new
|
179
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
180
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
181
|
+
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_command,h_opts,selector_shell_id(shell_id))) }
|
182
|
+
env.tag!(:env, :Body) do |env_body|
|
183
|
+
env_body.tag!("#{NS_WIN_SHELL}:CommandLine") { |cl| cl << Gyoku.xml(body) }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Grab the command element and unescape any single quotes - issue 69
|
188
|
+
xml = builder.target!
|
189
|
+
escaped_cmd = /<#{NS_WIN_SHELL}:Command>(.+)<\/#{NS_WIN_SHELL}:Command>/m.match(xml)[1]
|
190
|
+
xml[escaped_cmd] = escaped_cmd.gsub(/'/, "'")
|
191
|
+
|
192
|
+
resp_doc = send_message(xml)
|
193
|
+
command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
|
194
|
+
|
195
|
+
if block_given?
|
196
|
+
begin
|
197
|
+
yield command_id
|
198
|
+
ensure
|
199
|
+
cleanup_command(shell_id, command_id)
|
200
|
+
end
|
201
|
+
else
|
202
|
+
command_id
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def write_stdin(shell_id, command_id, stdin)
|
207
|
+
# Signal the Command references to terminate (close stdout/stderr)
|
208
|
+
body = {
|
209
|
+
"#{NS_WIN_SHELL}:Send" => {
|
210
|
+
"#{NS_WIN_SHELL}:Stream" => {
|
211
|
+
"@Name" => 'stdin',
|
212
|
+
"@CommandId" => command_id,
|
213
|
+
:content! => Base64.encode64(stdin)
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
217
|
+
builder = Builder::XmlMarkup.new
|
218
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
219
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
220
|
+
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_send,selector_shell_id(shell_id))) }
|
221
|
+
env.tag!(:env, :Body) do |env_body|
|
222
|
+
env_body << Gyoku.xml(body)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
resp = send_message(builder.target!)
|
226
|
+
true
|
227
|
+
end
|
228
|
+
|
229
|
+
# Get the Output of the given shell and command
|
230
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
231
|
+
# @param [String] command_id The command id on the remote machine. See #run_command
|
232
|
+
# @return [Hash] Returns a Hash with a key :exitcode and :data. Data is an Array of Hashes where the cooresponding key
|
233
|
+
# 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
|
234
|
+
# the console.
|
235
|
+
def get_command_output(shell_id, command_id, &block)
|
236
|
+
body = { "#{NS_WIN_SHELL}:DesiredStream" => 'stdout stderr',
|
237
|
+
:attributes! => {"#{NS_WIN_SHELL}:DesiredStream" => {'CommandId' => command_id}}}
|
238
|
+
|
239
|
+
builder = Builder::XmlMarkup.new
|
240
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
241
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
242
|
+
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_receive,selector_shell_id(shell_id))) }
|
243
|
+
env.tag!(:env, :Body) do |env_body|
|
244
|
+
env_body.tag!("#{NS_WIN_SHELL}:Receive") { |cl| cl << Gyoku.xml(body) }
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
resp_doc = nil
|
249
|
+
request_msg = builder.target!
|
250
|
+
done_elems = []
|
251
|
+
output = Output.new
|
252
|
+
|
253
|
+
while done_elems.empty?
|
254
|
+
resp_doc = send_get_output_message(request_msg)
|
255
|
+
|
256
|
+
REXML::XPath.match(resp_doc, "//#{NS_WIN_SHELL}:Stream").each do |n|
|
257
|
+
next if n.text.nil? || n.text.empty?
|
258
|
+
|
259
|
+
# decode and replace invalid unicode characters
|
260
|
+
decoded_text = Base64.decode64(n.text).force_encoding('utf-8')
|
261
|
+
if ! decoded_text.valid_encoding?
|
262
|
+
if decoded_text.respond_to?(:scrub!)
|
263
|
+
decoded_text.scrub!
|
264
|
+
else
|
265
|
+
decoded_text = decoded_text.encode('utf-16', invalid: :replace, undef: :replace)
|
266
|
+
.encode('utf-8')
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# remove BOM which 2008R2 applies
|
271
|
+
stream = { n.attributes['Name'].to_sym => decoded_text.sub('\xEF\xBB\xBF', '') }
|
272
|
+
output[:data] << stream
|
273
|
+
yield stream[:stdout], stream[:stderr] if block_given?
|
274
|
+
end
|
275
|
+
|
276
|
+
# We may need to get additional output if the stream has not finished.
|
277
|
+
# The CommandState will change from Running to Done like so:
|
278
|
+
# @example
|
279
|
+
# from...
|
280
|
+
# <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Running"/>
|
281
|
+
# to...
|
282
|
+
# <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
|
283
|
+
# <rsp:ExitCode>0</rsp:ExitCode>
|
284
|
+
# </rsp:CommandState>
|
285
|
+
done_elems = REXML::XPath.match(resp_doc, "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']")
|
286
|
+
end
|
287
|
+
|
288
|
+
output[:exitcode] = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:ExitCode").text.to_i
|
289
|
+
output
|
290
|
+
end
|
291
|
+
|
292
|
+
# Clean-up after a command.
|
293
|
+
# @see #run_command
|
294
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
295
|
+
# @param [String] command_id The command id on the remote machine. See #run_command
|
296
|
+
# @return [true] This should have more error checking but it just returns true for now.
|
297
|
+
def cleanup_command(shell_id, command_id)
|
298
|
+
# Signal the Command references to terminate (close stdout/stderr)
|
299
|
+
body = { "#{NS_WIN_SHELL}:Code" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate' }
|
300
|
+
builder = Builder::XmlMarkup.new
|
301
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
302
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
303
|
+
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_signal,selector_shell_id(shell_id))) }
|
304
|
+
env.tag!(:env, :Body) do |env_body|
|
305
|
+
env_body.tag!("#{NS_WIN_SHELL}:Signal", {'CommandId' => command_id}) { |cl| cl << Gyoku.xml(body) }
|
306
|
+
end
|
307
|
+
end
|
308
|
+
resp = send_message(builder.target!)
|
309
|
+
true
|
310
|
+
end
|
311
|
+
|
312
|
+
# Close the shell
|
313
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
314
|
+
# @return [true] This should have more error checking but it just returns true for now.
|
315
|
+
def close_shell(shell_id)
|
316
|
+
logger.debug("[WinRM] closing remote shell #{shell_id} on #{@endpoint}")
|
317
|
+
builder = Builder::XmlMarkup.new
|
318
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
319
|
+
|
320
|
+
builder.tag!('env:Envelope', namespaces) do |env|
|
321
|
+
env.tag!('env:Header') { |h| h << Gyoku.xml(merge_headers(header,resource_uri_cmd,action_delete,selector_shell_id(shell_id))) }
|
322
|
+
env.tag!('env:Body')
|
323
|
+
end
|
324
|
+
|
325
|
+
resp = send_message(builder.target!)
|
326
|
+
logger.debug("[WinRM] remote shell #{shell_id} closed")
|
327
|
+
true
|
328
|
+
end
|
329
|
+
|
330
|
+
# DEPRECATED: Use WinRM::CommandExecutor#run_cmd instead
|
331
|
+
# Run a CMD command
|
332
|
+
# @param [String] command The command to run on the remote system
|
333
|
+
# @param [Array <String>] arguments arguments to the command
|
334
|
+
# @param [String] an existing and open shell id to reuse
|
335
|
+
# @return [Hash] :stdout and :stderr
|
336
|
+
def run_cmd(command, arguments = [], &block)
|
337
|
+
logger.warn("WinRM::WinRMWebService#run_cmd is deprecated. Use WinRM::CommandExecutor#run_cmd instead")
|
338
|
+
create_executor do |executor|
|
339
|
+
executor.run_cmd(command, arguments, &block)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
alias :cmd :run_cmd
|
343
|
+
|
344
|
+
# DEPRECATED: Use WinRM::CommandExecutor#run_powershell_script instead
|
345
|
+
# Run a Powershell script that resides on the local box.
|
346
|
+
# @param [IO,String] script_file an IO reference for reading the Powershell script or the actual file contents
|
347
|
+
# @param [String] an existing and open shell id to reuse
|
348
|
+
# @return [Hash] :stdout and :stderr
|
349
|
+
def run_powershell_script(script_file, &block)
|
350
|
+
logger.warn("WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead")
|
351
|
+
create_executor do |executor|
|
352
|
+
executor.run_powershell_script(script_file, &block)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
alias :powershell :run_powershell_script
|
356
|
+
|
357
|
+
# Creates a CommandExecutor initialized with this WinRMWebService
|
358
|
+
# If called with a block, create_executor yields an executor and
|
359
|
+
# ensures that the executor is closed after the block completes.
|
360
|
+
# The CommandExecutor is simply returned if no block is given.
|
361
|
+
# @yieldparam [CommandExecutor] a CommandExecutor instance
|
362
|
+
# @return [CommandExecutor] a CommandExecutor instance
|
363
|
+
def create_executor(&block)
|
364
|
+
executor = CommandExecutor.new(self)
|
365
|
+
executor.open
|
366
|
+
|
367
|
+
if block_given?
|
368
|
+
begin
|
369
|
+
yield executor
|
370
|
+
ensure
|
371
|
+
executor.close
|
372
|
+
end
|
373
|
+
else
|
374
|
+
executor
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Run a WQL Query
|
379
|
+
# @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
|
380
|
+
# @param [String] wql The WQL query
|
381
|
+
# @return [Hash] Returns a Hash that contain the key/value pairs returned from the query.
|
382
|
+
def run_wql(wql)
|
383
|
+
|
384
|
+
body = { "#{NS_WSMAN_DMTF}:OptimizeEnumeration" => nil,
|
385
|
+
"#{NS_WSMAN_DMTF}:MaxElements" => '32000',
|
386
|
+
"#{NS_WSMAN_DMTF}:Filter" => wql,
|
387
|
+
:attributes! => { "#{NS_WSMAN_DMTF}:Filter" => {'Dialect' => 'http://schemas.microsoft.com/wbem/wsman/1/WQL'}}
|
388
|
+
}
|
389
|
+
|
390
|
+
builder = Builder::XmlMarkup.new
|
391
|
+
builder.instruct!(:xml, :encoding => 'UTF-8')
|
392
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
393
|
+
env.tag!(:env, :Header) { |h| h << Gyoku.xml(merge_headers(header,resource_uri_wmi,action_enumerate)) }
|
394
|
+
env.tag!(:env, :Body) do |env_body|
|
395
|
+
env_body.tag!("#{NS_ENUM}:Enumerate") { |en| en << Gyoku.xml(body) }
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
resp = send_message(builder.target!)
|
400
|
+
parser = Nori.new(:parser => :rexml, :advanced_typecasting => false, :convert_tags_to => lambda { |tag| tag.snakecase.to_sym }, :strip_namespaces => true)
|
401
|
+
hresp = parser.parse(resp.to_s)[:envelope][:body]
|
402
|
+
|
403
|
+
# Normalize items so the type always has an array even if it's just a single item.
|
404
|
+
items = {}
|
405
|
+
if hresp[:enumerate_response][:items]
|
406
|
+
hresp[:enumerate_response][:items].each_pair do |k,v|
|
407
|
+
if v.is_a?(Array)
|
408
|
+
items[k] = v
|
409
|
+
else
|
410
|
+
items[k] = [v]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
items
|
415
|
+
end
|
416
|
+
alias :wql :run_wql
|
417
|
+
|
418
|
+
def toggle_nori_type_casting(to)
|
419
|
+
logger.warn('toggle_nori_type_casting is deprecated and has no effect, ' +
|
420
|
+
'please remove calls to it')
|
421
|
+
end
|
422
|
+
|
423
|
+
private
|
424
|
+
|
425
|
+
def setup_logger
|
426
|
+
@logger = Logging.logger[self]
|
427
|
+
@logger.level = :warn
|
428
|
+
@logger.add_appenders(Logging.appenders.stdout)
|
429
|
+
end
|
430
|
+
|
431
|
+
def configure_retries(opts)
|
432
|
+
@retry_delay = opts[:retry_delay] || 10
|
433
|
+
@retry_limit = opts[:retry_limit] || 3
|
434
|
+
end
|
435
|
+
|
436
|
+
def namespaces
|
437
|
+
{
|
438
|
+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
439
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
440
|
+
'xmlns:env' => 'http://www.w3.org/2003/05/soap-envelope',
|
441
|
+
'xmlns:a' => 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
|
442
|
+
'xmlns:b' => 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd',
|
443
|
+
'xmlns:n' => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration',
|
444
|
+
'xmlns:x' => 'http://schemas.xmlsoap.org/ws/2004/09/transfer',
|
445
|
+
'xmlns:w' => 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd',
|
446
|
+
'xmlns:p' => 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd',
|
447
|
+
'xmlns:rsp' => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell',
|
448
|
+
'xmlns:cfg' => 'http://schemas.microsoft.com/wbem/wsman/1/config',
|
449
|
+
}
|
450
|
+
end
|
451
|
+
|
452
|
+
def header
|
453
|
+
{ "#{NS_ADDRESSING}:To" => "#{@xfer.endpoint.to_s}",
|
454
|
+
"#{NS_ADDRESSING}:ReplyTo" => {
|
455
|
+
"#{NS_ADDRESSING}:Address" => 'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous',
|
456
|
+
:attributes! => {"#{NS_ADDRESSING}:Address" => {'mustUnderstand' => true}}},
|
457
|
+
"#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => @max_env_sz,
|
458
|
+
"#{NS_ADDRESSING}:MessageID" => "uuid:#{SecureRandom.uuid.to_s.upcase}",
|
459
|
+
"#{NS_WSMAN_DMTF}:Locale/" => '',
|
460
|
+
"#{NS_WSMAN_MSFT}:DataLocale/" => '',
|
461
|
+
"#{NS_WSMAN_DMTF}:OperationTimeout" => @timeout,
|
462
|
+
:attributes! => {
|
463
|
+
"#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => {'mustUnderstand' => true},
|
464
|
+
"#{NS_WSMAN_DMTF}:Locale/" => {'xml:lang' => @locale, 'mustUnderstand' => false},
|
465
|
+
"#{NS_WSMAN_MSFT}:DataLocale/" => {'xml:lang' => @locale, 'mustUnderstand' => false}
|
466
|
+
}}
|
467
|
+
end
|
468
|
+
|
469
|
+
# merge the various header hashes and make sure we carry all of the attributes
|
470
|
+
# through instead of overwriting them.
|
471
|
+
def merge_headers(*headers)
|
472
|
+
hdr = {}
|
473
|
+
headers.each do |h|
|
474
|
+
hdr.merge!(h) do |k,v1,v2|
|
475
|
+
v1.merge!(v2) if k == :attributes!
|
476
|
+
end
|
477
|
+
end
|
478
|
+
hdr
|
479
|
+
end
|
480
|
+
|
481
|
+
def send_get_output_message(message)
|
482
|
+
send_message(message)
|
483
|
+
rescue WinRMWSManFault => e
|
484
|
+
# If no output is available before the wsman:OperationTimeout expires,
|
485
|
+
# the server MUST return a WSManFault with the Code attribute equal to
|
486
|
+
# 2150858793. When the client receives this fault, it SHOULD issue
|
487
|
+
# another Receive request.
|
488
|
+
# http://msdn.microsoft.com/en-us/library/cc251676.aspx
|
489
|
+
if e.fault_code == '2150858793'
|
490
|
+
logger.debug("[WinRM] retrying receive request after timeout")
|
491
|
+
retry
|
492
|
+
else
|
493
|
+
raise
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
def send_message(message)
|
498
|
+
@xfer.send_request(message)
|
499
|
+
end
|
500
|
+
|
501
|
+
|
502
|
+
# Helper methods for SOAP Headers
|
503
|
+
|
504
|
+
def resource_uri_cmd
|
505
|
+
{"#{NS_WSMAN_DMTF}:ResourceURI" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
506
|
+
:attributes! => {"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => true}}}
|
507
|
+
end
|
508
|
+
|
509
|
+
def resource_uri_wmi(namespace = 'root/cimv2/*')
|
510
|
+
{"#{NS_WSMAN_DMTF}:ResourceURI" => "http://schemas.microsoft.com/wbem/wsman/1/wmi/#{namespace}",
|
511
|
+
:attributes! => {"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => true}}}
|
512
|
+
end
|
513
|
+
|
514
|
+
def action_create
|
515
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Create',
|
516
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
517
|
+
end
|
518
|
+
|
519
|
+
def action_delete
|
520
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete',
|
521
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
522
|
+
end
|
523
|
+
|
524
|
+
def action_command
|
525
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command',
|
526
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
527
|
+
end
|
528
|
+
|
529
|
+
def action_receive
|
530
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive',
|
531
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
532
|
+
end
|
533
|
+
|
534
|
+
def action_signal
|
535
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal',
|
536
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
537
|
+
end
|
538
|
+
|
539
|
+
def action_send
|
540
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send',
|
541
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
542
|
+
end
|
543
|
+
|
544
|
+
def action_enumerate
|
545
|
+
{"#{NS_ADDRESSING}:Action" => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate',
|
546
|
+
:attributes! => {"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => true}}}
|
547
|
+
end
|
548
|
+
|
549
|
+
def selector_shell_id(shell_id)
|
550
|
+
{"#{NS_WSMAN_DMTF}:SelectorSet" =>
|
551
|
+
{"#{NS_WSMAN_DMTF}:Selector" => shell_id, :attributes! => {"#{NS_WSMAN_DMTF}:Selector" => {'Name' => 'ShellId'}}}
|
552
|
+
}
|
553
|
+
end
|
554
|
+
|
555
|
+
end # WinRMWebService
|
556
|
+
end # WinRM
|