winrm 1.7.1 → 1.7.2

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.
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