winrm 1.7.1 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -10
  3. data/.rspec +3 -3
  4. data/.rubocop.yml +12 -12
  5. data/.travis.yml +12 -12
  6. data/Gemfile +9 -9
  7. data/LICENSE +202 -202
  8. data/README.md +194 -194
  9. data/Rakefile +36 -36
  10. data/Vagrantfile +9 -9
  11. data/appveyor.yml +42 -42
  12. data/bin/rwinrm +97 -97
  13. data/changelog.md +77 -74
  14. data/lib/winrm.rb +42 -42
  15. data/lib/winrm/command_executor.rb +224 -224
  16. data/lib/winrm/command_output_decoder.rb +53 -0
  17. data/lib/winrm/exceptions/exceptions.rb +57 -57
  18. data/lib/winrm/helpers/iso8601_duration.rb +58 -58
  19. data/lib/winrm/helpers/powershell_script.rb +42 -42
  20. data/lib/winrm/http/response_handler.rb +82 -82
  21. data/lib/winrm/http/transport.rb +421 -421
  22. data/lib/winrm/output.rb +43 -43
  23. data/lib/winrm/soap_provider.rb +39 -39
  24. data/lib/winrm/version.rb +7 -7
  25. data/lib/winrm/winrm_service.rb +547 -556
  26. data/preamble +17 -17
  27. data/spec/auth_timeout_spec.rb +16 -16
  28. data/spec/cmd_spec.rb +102 -102
  29. data/spec/command_executor_spec.rb +429 -429
  30. data/spec/command_output_decoder_spec.rb +37 -0
  31. data/spec/config-example.yml +19 -19
  32. data/spec/exception_spec.rb +50 -50
  33. data/spec/issue_184_spec.rb +67 -67
  34. data/spec/issue_59_spec.rb +23 -23
  35. data/spec/matchers.rb +74 -74
  36. data/spec/output_spec.rb +110 -110
  37. data/spec/powershell_spec.rb +97 -97
  38. data/spec/response_handler_spec.rb +59 -59
  39. data/spec/spec_helper.rb +73 -73
  40. data/spec/stubs/responses/get_command_output_response.xml.erb +13 -13
  41. data/spec/stubs/responses/open_shell_v1.xml +19 -19
  42. data/spec/stubs/responses/open_shell_v2.xml +20 -20
  43. data/spec/stubs/responses/soap_fault_v1.xml +36 -36
  44. data/spec/stubs/responses/soap_fault_v2.xml +42 -42
  45. data/spec/stubs/responses/wmi_error_v2.xml +41 -41
  46. data/spec/transport_spec.rb +124 -124
  47. data/spec/winrm_options_spec.rb +76 -76
  48. data/spec/winrm_primitives_spec.rb +51 -51
  49. data/spec/wql_spec.rb +14 -14
  50. data/winrm.gemspec +40 -40
  51. metadata +5 -3
data/lib/winrm.rb CHANGED
@@ -1,42 +1,42 @@
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 'date'
18
- require 'logging'
19
- require_relative 'winrm/version'
20
-
21
- # Main WinRM module entry point
22
- module WinRM
23
- # Enable logging if it is requested. We do this before
24
- # anything else so that we can setup the output before
25
- # any logging occurs.
26
- if ENV['WINRM_LOG'] && ENV['WINRM_LOG'] != ''
27
- begin
28
- Logging.logger.root.level = ENV['WINRM_LOG']
29
- Logging.logger.root.appenders = Logging.appenders.stderr
30
- rescue ArgumentError
31
- # This means that the logging level wasn't valid
32
- $stderr.puts "Invalid WINRM_LOG level is set: #{ENV['WINRM_LOG']}"
33
- $stderr.puts ''
34
- $stderr.puts 'Please use one of the standard log levels: ' \
35
- 'debug, info, warn, or error'
36
- end
37
- end
38
- end
39
-
40
- require 'winrm/output'
41
- require 'winrm/helpers/iso8601_duration'
42
- require 'winrm/soap_provider'
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 'date'
18
+ require 'logging'
19
+ require_relative 'winrm/version'
20
+
21
+ # Main WinRM module entry point
22
+ module WinRM
23
+ # Enable logging if it is requested. We do this before
24
+ # anything else so that we can setup the output before
25
+ # any logging occurs.
26
+ if ENV['WINRM_LOG'] && ENV['WINRM_LOG'] != ''
27
+ begin
28
+ Logging.logger.root.level = ENV['WINRM_LOG']
29
+ Logging.logger.root.appenders = Logging.appenders.stderr
30
+ rescue ArgumentError
31
+ # This means that the logging level wasn't valid
32
+ $stderr.puts "Invalid WINRM_LOG level is set: #{ENV['WINRM_LOG']}"
33
+ $stderr.puts ''
34
+ $stderr.puts 'Please use one of the standard log levels: ' \
35
+ 'debug, info, warn, or error'
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'winrm/output'
41
+ require 'winrm/helpers/iso8601_duration'
42
+ require 'winrm/soap_provider'
@@ -1,224 +1,224 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
- # Copyright 2015 Matt Wrock <matt@mattwrock.com>
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
-
18
- module WinRM
19
- # Object which can execute multiple commands and Powershell scripts in
20
- # one shared remote shell session. The maximum number of commands per
21
- # shell is determined by interrogating the remote host when the session
22
- # is opened and the remote shell is automatically recycled before the
23
- # threshold is reached.
24
- #
25
- # @author Shawn Neal <sneal@sneal.net>
26
- # @author Matt Wrock <matt@mattwrock.com>
27
- # @author Fletcher Nichol <fnichol@nichol.ca>
28
- class CommandExecutor
29
- # Closes an open remote shell session left open
30
- # after a command executor is garbage collecyted.
31
- #
32
- # @param shell_id [String] the remote shell identifier
33
- # @param service [WinRM::WinRMWebService] a winrm web service object
34
- def self.finalize(shell_id, service)
35
- proc { service.close_shell(shell_id) }
36
- end
37
-
38
- # @return [WinRM::WinRMWebService] a WinRM web service object
39
- attr_reader :service
40
-
41
- # @return [String,nil] the identifier for the current open remote
42
- # shell session, or `nil` if the session is not open
43
- attr_reader :shell
44
-
45
- # Creates a CommandExecutor given a `WinRM::WinRMWebService` object.
46
- #
47
- # @param service [WinRM::WinRMWebService] a winrm web service object
48
- # responds to `#debug` and `#info` (default: `nil`)
49
- def initialize(service)
50
- @service = service
51
- @command_count = 0
52
- end
53
-
54
- # Closes the open remote shell session. This method can be called
55
- # multiple times, even if there is no open session.
56
- def close
57
- return if shell.nil?
58
-
59
- service.close_shell(shell)
60
- remove_finalizer
61
- @shell = nil
62
- end
63
-
64
- # Opens a remote shell session for reuse. The maxiumum
65
- # command-per-shell threshold is also determined the first time this
66
- # method is invoked and cached for later invocations.
67
- #
68
- # @return [String] the remote shell session indentifier
69
- def open
70
- close
71
- retryable(service.retry_limit, service.retry_delay) do
72
- @shell = service.open_shell(codepage: code_page)
73
- end
74
- add_finalizer(shell)
75
- @command_count = 0
76
- shell
77
- end
78
-
79
- # Runs a CMD command.
80
- #
81
- # @param command [String] the command to run on the remote system
82
- # @param arguments [Array<String>] arguments to the command
83
- # @yield [stdout, stderr] yields more live access the standard
84
- # output and standard error streams as they are returns, if
85
- # streaming behavior is desired
86
- # @return [WinRM::Output] output object with stdout, stderr, and
87
- # exit code
88
- def run_cmd(command, arguments = [], &block)
89
- reset if command_count > max_commands
90
- ensure_open_shell!
91
-
92
- @command_count += 1
93
- result = nil
94
- service.run_command(shell, command, arguments) do |command_id|
95
- result = service.get_command_output(shell, command_id, &block)
96
- end
97
- result
98
- end
99
-
100
- # Run a Powershell script that resides on the local box.
101
- #
102
- # @param script_file [IO,String] an IO reference for reading the
103
- # Powershell script or the actual file contents
104
- # @yield [stdout, stderr] yields more live access the standard
105
- # output and standard error streams as they are returns, if
106
- # streaming behavior is desired
107
- # @return [WinRM::Output] output object with stdout, stderr, and
108
- # exit code
109
- def run_powershell_script(script_file, &block)
110
- # this code looks overly compact in an attempt to limit local
111
- # variable assignments that may contain large strings and
112
- # consequently bloat the Ruby VM
113
- run_cmd(
114
- 'powershell',
115
- [
116
- '-encodedCommand',
117
- ::WinRM::PowershellScript.new(
118
- script_file.is_a?(IO) ? script_file.read : script_file
119
- ).encoded
120
- ],
121
- &block
122
- )
123
- end
124
-
125
- # Code page appropriate to os version. utf-8 (65001) is buggy pre win7/2k8r2
126
- # So send MS-DOS (437) for earlier versions
127
- #
128
- # @return [Integer] code page in use
129
- def code_page
130
- @code_page ||= os_version < Gem::Version.new('6.1') ? 437 : 65_001
131
- end
132
-
133
- # @return [Integer] the safe maximum number of commands that can
134
- # be executed in one remote shell session
135
- def max_commands
136
- @max_commands ||= (os_version < Gem::Version.new('6.2') ? 15 : 1500) - 2
137
- end
138
-
139
- private
140
-
141
- # @return [Integer] the number of executed commands on the remote
142
- # shell session
143
- # @api private
144
- attr_accessor :command_count
145
-
146
- # Creates a finalizer for this connection which will close the open
147
- # remote shell session when the object is garabage collected or on
148
- # Ruby VM shutdown.
149
- #
150
- # @param shell_id [String] the remote shell identifier
151
- # @api private
152
- def add_finalizer(shell_id)
153
- ObjectSpace.define_finalizer(self, self.class.finalize(shell_id, service))
154
- end
155
-
156
- # Ensures that there is an open remote shell session.
157
- #
158
- # @raise [WinRM::WinRMError] if there is no open shell
159
- # @api private
160
- def ensure_open_shell!
161
- fail ::WinRM::WinRMError, "#{self.class}#open must be called " \
162
- 'before any run methods are invoked' if shell.nil?
163
- end
164
-
165
- # Fetches the OS build bersion of the remote endpoint
166
- #
167
- # @api private
168
- def os_version
169
- @os_version ||= begin
170
- version = nil
171
- wql = service.run_wql('select version from Win32_OperatingSystem')
172
- if wql[:xml_fragment]
173
- version = wql[:xml_fragment].first[:version] if wql[:xml_fragment].first[:version]
174
- end
175
- fail ::WinRM::WinRMError, 'Unable to determine endpoint os version' if version.nil?
176
- Gem::Version.new(version)
177
- end
178
- end
179
-
180
- # Removes any finalizers for this connection.
181
- #
182
- # @api private
183
- def remove_finalizer
184
- ObjectSpace.undefine_finalizer(self)
185
- end
186
-
187
- # Closes the remote shell session and opens a new one.
188
- #
189
- # @api private
190
- def reset
191
- service.logger.debug("Resetting WinRM shell (Max command limit is #{max_commands})")
192
- open
193
- end
194
-
195
- # Yields to a block and reties the block if certain rescuable
196
- # exceptions are raised.
197
- #
198
- # @param retries [Integer] the number of times to retry before failing
199
- # @option delay [Float] the number of seconds to wait until
200
- # attempting a retry
201
- # @api private
202
- def retryable(retries, delay)
203
- yield
204
- rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH.call => e
205
- if (retries -= 1) > 0
206
- service.logger.info("[WinRM] connection failed. retrying in #{delay} seconds: #{e.inspect}")
207
- sleep(delay)
208
- retry
209
- else
210
- service.logger.warn("[WinRM] connection failed, terminating (#{e.inspect})")
211
- raise
212
- end
213
- end
214
-
215
- RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
216
- [
217
- Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
218
- Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
219
- ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
220
- HTTPClient::KeepAliveDisconnected, HTTPClient::ConnectTimeoutError
221
- ].freeze
222
- end
223
- end
224
- end
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
+ # Copyright 2015 Matt Wrock <matt@mattwrock.com>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module WinRM
19
+ # Object which can execute multiple commands and Powershell scripts in
20
+ # one shared remote shell session. The maximum number of commands per
21
+ # shell is determined by interrogating the remote host when the session
22
+ # is opened and the remote shell is automatically recycled before the
23
+ # threshold is reached.
24
+ #
25
+ # @author Shawn Neal <sneal@sneal.net>
26
+ # @author Matt Wrock <matt@mattwrock.com>
27
+ # @author Fletcher Nichol <fnichol@nichol.ca>
28
+ class CommandExecutor
29
+ # Closes an open remote shell session left open
30
+ # after a command executor is garbage collecyted.
31
+ #
32
+ # @param shell_id [String] the remote shell identifier
33
+ # @param service [WinRM::WinRMWebService] a winrm web service object
34
+ def self.finalize(shell_id, service)
35
+ proc { service.close_shell(shell_id) }
36
+ end
37
+
38
+ # @return [WinRM::WinRMWebService] a WinRM web service object
39
+ attr_reader :service
40
+
41
+ # @return [String,nil] the identifier for the current open remote
42
+ # shell session, or `nil` if the session is not open
43
+ attr_reader :shell
44
+
45
+ # Creates a CommandExecutor given a `WinRM::WinRMWebService` object.
46
+ #
47
+ # @param service [WinRM::WinRMWebService] a winrm web service object
48
+ # responds to `#debug` and `#info` (default: `nil`)
49
+ def initialize(service)
50
+ @service = service
51
+ @command_count = 0
52
+ end
53
+
54
+ # Closes the open remote shell session. This method can be called
55
+ # multiple times, even if there is no open session.
56
+ def close
57
+ return if shell.nil?
58
+
59
+ service.close_shell(shell)
60
+ remove_finalizer
61
+ @shell = nil
62
+ end
63
+
64
+ # Opens a remote shell session for reuse. The maxiumum
65
+ # command-per-shell threshold is also determined the first time this
66
+ # method is invoked and cached for later invocations.
67
+ #
68
+ # @return [String] the remote shell session indentifier
69
+ def open
70
+ close
71
+ retryable(service.retry_limit, service.retry_delay) do
72
+ @shell = service.open_shell(codepage: code_page)
73
+ end
74
+ add_finalizer(shell)
75
+ @command_count = 0
76
+ shell
77
+ end
78
+
79
+ # Runs a CMD command.
80
+ #
81
+ # @param command [String] the command to run on the remote system
82
+ # @param arguments [Array<String>] arguments to the command
83
+ # @yield [stdout, stderr] yields more live access the standard
84
+ # output and standard error streams as they are returns, if
85
+ # streaming behavior is desired
86
+ # @return [WinRM::Output] output object with stdout, stderr, and
87
+ # exit code
88
+ def run_cmd(command, arguments = [], &block)
89
+ reset if command_count > max_commands
90
+ ensure_open_shell!
91
+
92
+ @command_count += 1
93
+ result = nil
94
+ service.run_command(shell, command, arguments) do |command_id|
95
+ result = service.get_command_output(shell, command_id, &block)
96
+ end
97
+ result
98
+ end
99
+
100
+ # Run a Powershell script that resides on the local box.
101
+ #
102
+ # @param script_file [IO,String] an IO reference for reading the
103
+ # Powershell script or the actual file contents
104
+ # @yield [stdout, stderr] yields more live access the standard
105
+ # output and standard error streams as they are returns, if
106
+ # streaming behavior is desired
107
+ # @return [WinRM::Output] output object with stdout, stderr, and
108
+ # exit code
109
+ def run_powershell_script(script_file, &block)
110
+ # this code looks overly compact in an attempt to limit local
111
+ # variable assignments that may contain large strings and
112
+ # consequently bloat the Ruby VM
113
+ run_cmd(
114
+ 'powershell',
115
+ [
116
+ '-encodedCommand',
117
+ ::WinRM::PowershellScript.new(
118
+ script_file.is_a?(IO) ? script_file.read : script_file
119
+ ).encoded
120
+ ],
121
+ &block
122
+ )
123
+ end
124
+
125
+ # Code page appropriate to os version. utf-8 (65001) is buggy pre win7/2k8r2
126
+ # So send MS-DOS (437) for earlier versions
127
+ #
128
+ # @return [Integer] code page in use
129
+ def code_page
130
+ @code_page ||= os_version < Gem::Version.new('6.1') ? 437 : 65_001
131
+ end
132
+
133
+ # @return [Integer] the safe maximum number of commands that can
134
+ # be executed in one remote shell session
135
+ def max_commands
136
+ @max_commands ||= (os_version < Gem::Version.new('6.2') ? 15 : 1500) - 2
137
+ end
138
+
139
+ private
140
+
141
+ # @return [Integer] the number of executed commands on the remote
142
+ # shell session
143
+ # @api private
144
+ attr_accessor :command_count
145
+
146
+ # Creates a finalizer for this connection which will close the open
147
+ # remote shell session when the object is garabage collected or on
148
+ # Ruby VM shutdown.
149
+ #
150
+ # @param shell_id [String] the remote shell identifier
151
+ # @api private
152
+ def add_finalizer(shell_id)
153
+ ObjectSpace.define_finalizer(self, self.class.finalize(shell_id, service))
154
+ end
155
+
156
+ # Ensures that there is an open remote shell session.
157
+ #
158
+ # @raise [WinRM::WinRMError] if there is no open shell
159
+ # @api private
160
+ def ensure_open_shell!
161
+ fail ::WinRM::WinRMError, "#{self.class}#open must be called " \
162
+ 'before any run methods are invoked' if shell.nil?
163
+ end
164
+
165
+ # Fetches the OS build bersion of the remote endpoint
166
+ #
167
+ # @api private
168
+ def os_version
169
+ @os_version ||= begin
170
+ version = nil
171
+ wql = service.run_wql('select version from Win32_OperatingSystem')
172
+ if wql[:xml_fragment]
173
+ version = wql[:xml_fragment].first[:version] if wql[:xml_fragment].first[:version]
174
+ end
175
+ fail ::WinRM::WinRMError, 'Unable to determine endpoint os version' if version.nil?
176
+ Gem::Version.new(version)
177
+ end
178
+ end
179
+
180
+ # Removes any finalizers for this connection.
181
+ #
182
+ # @api private
183
+ def remove_finalizer
184
+ ObjectSpace.undefine_finalizer(self)
185
+ end
186
+
187
+ # Closes the remote shell session and opens a new one.
188
+ #
189
+ # @api private
190
+ def reset
191
+ service.logger.debug("Resetting WinRM shell (Max command limit is #{max_commands})")
192
+ open
193
+ end
194
+
195
+ # Yields to a block and reties the block if certain rescuable
196
+ # exceptions are raised.
197
+ #
198
+ # @param retries [Integer] the number of times to retry before failing
199
+ # @option delay [Float] the number of seconds to wait until
200
+ # attempting a retry
201
+ # @api private
202
+ def retryable(retries, delay)
203
+ yield
204
+ rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH.call => e
205
+ if (retries -= 1) > 0
206
+ service.logger.info("[WinRM] connection failed. retrying in #{delay} seconds: #{e.inspect}")
207
+ sleep(delay)
208
+ retry
209
+ else
210
+ service.logger.warn("[WinRM] connection failed, terminating (#{e.inspect})")
211
+ raise
212
+ end
213
+ end
214
+
215
+ RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
216
+ [
217
+ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
218
+ Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
219
+ ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
220
+ HTTPClient::KeepAliveDisconnected, HTTPClient::ConnectTimeoutError
221
+ ].freeze
222
+ end
223
+ end
224
+ end