winrm 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3933c15aee7afe32e53f6b10123238e00b40a41c
4
- data.tar.gz: 781c8ba0280a77a5c324428f1f6b08e377142a94
3
+ metadata.gz: ad05b5c1fe16cfa1586a6a001022d0463e87d9cb
4
+ data.tar.gz: 07a2d85d2a0a9458049eaac2b23bbd68e518ae2d
5
5
  SHA512:
6
- metadata.gz: 002b41189de8c7a4a3204a455ea25a720ce478f4a89816f8e4a4de143030610d433f158b757c3b971e7062ade4f5a3e948967c77a54a736a42ac8320e2f747e4
7
- data.tar.gz: d2bfc89a6a58381f15371fde4ae6338f256206628d65f09ddc93df6b5b7c2a69fc9fa421fd4c8cbdf7a998bd6c71a0cb5c7fcc98b3fcaf6fb41a7f2dc3f61f4d
6
+ metadata.gz: c2181da8210604d6703c2f694c3388e9e8f670fba9673a3fac68b1b9d257e2f0e3412f810b3c94966a57e7035e1d1b153ee815a7b993694c71885925d6bff09e
7
+ data.tar.gz: 47d0c26bfab5a7f096091bf74f042ded6a25fe59683c83829907863176f5bbb337bb6b972fcc4e2d3d5977c150c65c81413c6044a23b75787b82ae8738e1fb38
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  Exclude:
3
+ - 'appveyor.yml'
3
4
  - 'lib/winrm/winrm_service.rb'
4
5
 
5
6
  Style/Encoding:
@@ -2,4 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - 2.1.0
5
+ - 2.1.0
6
+ before_install:
7
+ - gem update bundler
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Windows Remote Management (WinRM) for Ruby
2
2
  [![Build Status](https://travis-ci.org/WinRb/WinRM.svg?branch=master)](https://travis-ci.org/WinRb/WinRM)
3
3
  [![Gem Version](https://badge.fury.io/rb/winrm.svg)](http://badge.fury.io/rb/winrm)
4
+ [![Build status](https://ci.appveyor.com/api/projects/status/ods9tvos78k5c15h?svg=true)](https://ci.appveyor.com/project/winrb/winrm)
4
5
 
5
6
  This is a SOAP library that uses the functionality in Windows Remote
6
7
  Management(WinRM) to call native object in Windows. This includes, but is
@@ -20,9 +21,11 @@ require 'winrm'
20
21
  endpoint = 'http://mywinrmhost:5985/wsman'
21
22
  krb5_realm = 'EXAMPLE.COM'
22
23
  winrm = WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => krb5_realm)
23
- winrm.cmd('ipconfig /all') do |stdout, stderr|
24
- STDOUT.print stdout
25
- STDERR.print stderr
24
+ winrm.create_executor do |executor|
25
+ executor.run_cmd('ipconfig /all') do |stdout, stderr|
26
+ STDOUT.print stdout
27
+ STDERR.print stderr
28
+ end
26
29
  end
27
30
  ```
28
31
 
@@ -30,6 +33,11 @@ There are various connection types you can specify upon initialization:
30
33
 
31
34
  It is recommended that you <code>:disable_sspi => true</code> if you are using the plaintext or ssl transport.
32
35
 
36
+ ### Deprecated methods
37
+ As of version 1.5.0 `WinRM::WinRMWebService` methods `cmd`, `run_cmd`, `powershell`, and `run_powershell_script` have been deprecated and will be removed from the next major version of the WinRM gem.
38
+
39
+ Use the `run_cmd` and `run_powershell_script` of the `WinRM::CommandExecutor` class instead. The `CommandExecutor` allows multiple commands to be run from the same WinRM shell providing a significant performance improvement when issuing multiple calls.
40
+
33
41
  #### Plaintext
34
42
  ```ruby
35
43
  WinRM::WinRMWebService.new(endpoint, :plaintext, :user => myuser, :pass => mypass, :disable_sspi => true)
@@ -52,10 +60,13 @@ WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass, :ba
52
60
  # Enabling no_ssl_peer_verification is not recommended. HTTPS connections are still encrypted,
53
61
  # but the WinRM gem is not able to detect forged replies or man in the middle attacks.
54
62
  WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass, :basic_auth_only => true, :no_ssl_peer_verification => true)
63
+
64
+ # Verify against a known fingerprint
65
+ WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass, :basic_auth_only => true, :ssl_peer_fingerprint => '6C04B1A997BA19454B0CD31C65D7020A6FC2669D')
55
66
  ```
56
67
 
57
68
  ##### Create a self signed cert for WinRM
58
- You may want to create a self signed certificate for servicing https WinRM connections. First you must install makecert.exe from the Windows SDK, then you can use the following PowerShell script to create a cert and enable the WinRM HTTPS listener.
69
+ You may want to create a self signed certificate for servicing https WinRM connections. You can use the following PowerShell script to create a cert and enable the WinRM HTTPS listener. Unless you are running windows server 2012 R2 or later, you must install makecert.exe from the Windows SDK, otherwise use `New-SelfSignedCertificate`.
59
70
 
60
71
  ```powershell
61
72
  $hostname = $Env:ComputerName
@@ -63,6 +74,10 @@ $hostname = $Env:ComputerName
63
74
  C:\"Program Files"\"Microsoft SDKs"\Windows\v7.1\Bin\makecert.exe -r -pe -n "CN=$hostname,O=vagrant" -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 "$hostname.cer"
64
75
 
65
76
  $thumbprint = (& ls cert:LocalMachine/my).Thumbprint
77
+
78
+ # Windows 2012R2 and above can use New-SelfSignedCertificate
79
+ $thumbprint = (New-SelfSignedCertificate -DnsName $hostname -CertStoreLocation cert:\LocalMachine\my).Thumbprint
80
+
66
81
  $cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname=`"$hostname`";CertificateThumbprint=`"$thumbprint`"}'"
67
82
  iex $cmd
68
83
  ```
@@ -72,6 +87,29 @@ iex $cmd
72
87
  WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => 'MYREALM.COM')
73
88
  ```
74
89
 
90
+ ## Retries and opening a shell
91
+ Especially if provisioning a new machine, it's possible the winrm service is not yet running when first attempting to connect. The `WinRMWebService` accepts the options `:retry_limit` and `:retry_delay` to specify the maximum number of attempts to make and how long to wait in between. These default to 3 attempts and a 10 second delay.
92
+ ```ruby
93
+ WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass, :retry_limit => 30, :retry_delay => 10)
94
+ ```
95
+
96
+ ## Logging
97
+ The `WinRMWebService` exposes a `logger` attribute and uses the [logging](https://rubygems.org/gems/logging) gem to manage logging behavior. By default this appends to `STDOUT` and has a level of `:warn`, but one can adjust the level or add additional appenders.
98
+ ```ruby
99
+ winrm = WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass)
100
+
101
+ # suppress warnings
102
+ winrm.logger.warn = :error
103
+
104
+ # Log to a file
105
+ winrm.logger.add_appenders(Logging.appenders.file('error.log'))
106
+ ```
107
+
108
+ If a consuming application uses its own logger that complies to the logging API, you can simply swap it in:
109
+ ```ruby
110
+ winrm.logger = my_logger
111
+ ```
112
+
75
113
  ## Troubleshooting
76
114
  You may have some errors like ```WinRM::WinRMAuthorizationError```.
77
115
  You can run the following commands on the server to try to solve the problem:
@@ -82,6 +120,8 @@ winrm set winrm/config/service @{AllowUnencrypted="true"}
82
120
  ```
83
121
  You can read more about that on issue [#29](https://github.com/WinRb/WinRM/issues/29)
84
122
 
123
+ Also see [this post](http://www.hurryupandwait.io/blog/understanding-and-troubleshooting-winrm-connection-and-authentication-a-thrill-seekers-guide-to-adventure) for more general tips related to winrm connection and authentication issues.
124
+
85
125
 
86
126
  ## Current features
87
127
 
@@ -128,7 +168,7 @@ Once you have the dependencies, you can run the unit tests with `rake`:
128
168
  $ bundle exec rake spec
129
169
  ```
130
170
 
131
- To run the integration tests you will need a Windows box with the WinRM service properly configured. Its easiest to use a Vagrant Windows box.
171
+ To run the integration tests you will need a Windows box with the WinRM service properly configured. Its easiest to use a Vagrant Windows box (mwrock/Windows2012R2 is public on [atlas](https://atlas.hashicorp.com/mwrock/boxes/Windows2012R2) with an evaluation version of Windows 2012 R2).
132
172
 
133
173
  1. Create a Windows VM with WinRM configured (see above).
134
174
  2. Copy the config-example.yml to config.yml - edit this file with your WinRM connection details.
@@ -0,0 +1,51 @@
1
+ version: "master-{build}"
2
+
3
+ os: Windows Server 2012 R2
4
+ platform:
5
+ - x64
6
+
7
+ environment:
8
+ winrm_user: test_user
9
+ winrm_pass: Pass@word1
10
+
11
+ matrix:
12
+ - ruby_version: "21"
13
+ winrm_endpoint: http://localhost:5985/wsman
14
+
15
+ - ruby_version: "21"
16
+ winrm_auth_type: ssl
17
+ winrm_endpoint: https://localhost:5986/wsman
18
+ winrm_no_ssl_peer_verification: true
19
+
20
+ - ruby_version: "21"
21
+ winrm_auth_type: ssl
22
+ winrm_endpoint: https://localhost:5986/wsman
23
+ use_ssl_peer_fingerprint: true
24
+
25
+ clone_folder: c:\projects\winrm
26
+ clone_depth: 1
27
+ branches:
28
+ only:
29
+ - master
30
+
31
+ install:
32
+ - ps: net user /add $env:winrm_user $env:winrm_pass
33
+ - ps: net localgroup administrators $env:winrm_user /add
34
+ - ps: $env:winrm_cert = (New-SelfSignedCertificate -DnsName localhost -CertStoreLocation cert:\localmachine\my).Thumbprint
35
+ - ps: winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"localhost`";CertificateThumbprint=`"$($env:winrm_cert)`"}"
36
+ - ps: winrm set winrm/config/client/auth '@{Basic="true"}'
37
+ - ps: winrm set winrm/config/service/auth '@{Basic="true"}'
38
+ - ps: winrm set winrm/config/service '@{AllowUnencrypted="true"}'
39
+ - ps: $env:PATH="C:\Ruby$env:ruby_version\bin;$env:PATH"
40
+ - ps: Write-Host $env:PATH
41
+ - ps: ruby --version
42
+ - ps: gem --version
43
+ - ps: gem install bundler --quiet --no-ri --no-rdoc
44
+ - ps: bundler --version
45
+
46
+ build_script:
47
+ - bundle install || bundle install || bundle install
48
+
49
+ test_script:
50
+ - SET SPEC_OPTS=--format progress
51
+ - bundle exec rake integration
data/bin/rwinrm CHANGED
File without changes
@@ -1,5 +1,14 @@
1
1
  # WinRM Gem Changelog
2
2
 
3
+ # 1.5.0
4
+ - Deprecating `WinRM::WinRMWebService` methods `cmd`, `run_cmd`, `powershell`, and `run_powershell_script` in favor of the `run_cmd` and `run_powershell_script` methods of the `WinRM::CommandExecutor` class. The `CommandExecutor` allows multiple commands to be run from the same WinRM shell providing a significant performance improvement when issuing multiple calls.
5
+ - Added an `:ssl_peer_fingerprint` option to be used instead of `:no_ssl_peer_verification` and allows a specific certificate to be verified.
6
+ - Opening a winrm shell is retriable with configurable delay and retry limit.
7
+ - Logging apends to `stdout` by default and can be replaced with a logger from a consuming application.
8
+
9
+ # 1.4.0
10
+ - Added WinRM::Version so the gem version is available at runtime for consumers.
11
+
3
12
  # 1.3.6
4
13
  - Remove BOM from response (Issue #159) added by Windows 2008R2
5
14
 
@@ -0,0 +1,242 @@
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 [Integer,nil] the safe maximum number of commands that can
39
+ # be executed in one remote shell session, or `nil` if the
40
+ # threshold has not yet been determined
41
+ attr_reader :max_commands
42
+
43
+ # @return [WinRM::WinRMWebService] a WinRM web service object
44
+ attr_reader :service
45
+
46
+ # @return [String,nil] the identifier for the current open remote
47
+ # shell session, or `nil` if the session is not open
48
+ attr_reader :shell
49
+
50
+ # Creates a CommandExecutor given a `WinRM::WinRMWebService` object.
51
+ #
52
+ # @param service [WinRM::WinRMWebService] a winrm web service object
53
+ # responds to `#debug` and `#info` (default: `nil`)
54
+ def initialize(service)
55
+ @service = service
56
+ @logger = service.logger
57
+ @command_count = 0
58
+ end
59
+
60
+ # Closes the open remote shell session. This method can be called
61
+ # multiple times, even if there is no open session.
62
+ def close
63
+ return if shell.nil?
64
+
65
+ service.close_shell(shell)
66
+ remove_finalizer
67
+ @shell = nil
68
+ end
69
+
70
+ # Opens a remote shell session for reuse. The maxiumum
71
+ # command-per-shell threshold is also determined the first time this
72
+ # method is invoked and cached for later invocations.
73
+ #
74
+ # @return [String] the remote shell session indentifier
75
+ def open
76
+ close
77
+ retryable(service.retry_limit, service.retry_delay) { @shell = service.open_shell }
78
+ add_finalizer(shell)
79
+ @command_count = 0
80
+ determine_max_commands unless max_commands
81
+ shell
82
+ end
83
+
84
+ # Runs a CMD command.
85
+ #
86
+ # @param command [String] the command to run on the remote system
87
+ # @param arguments [Array<String>] arguments to the command
88
+ # @yield [stdout, stderr] yields more live access the standard
89
+ # output and standard error streams as they are returns, if
90
+ # streaming behavior is desired
91
+ # @return [WinRM::Output] output object with stdout, stderr, and
92
+ # exit code
93
+ def run_cmd(command, arguments = [], &block)
94
+ reset if command_count_exceeded?
95
+ ensure_open_shell!
96
+
97
+ @command_count += 1
98
+ result = nil
99
+ service.run_command(shell, command, arguments) do |command_id|
100
+ result = service.get_command_output(shell, command_id, &block)
101
+ end
102
+ result
103
+ end
104
+
105
+ # Run a Powershell script that resides on the local box.
106
+ #
107
+ # @param script_file [IO,String] an IO reference for reading the
108
+ # Powershell script or the actual file contents
109
+ # @yield [stdout, stderr] yields more live access the standard
110
+ # output and standard error streams as they are returns, if
111
+ # streaming behavior is desired
112
+ # @return [WinRM::Output] output object with stdout, stderr, and
113
+ # exit code
114
+ def run_powershell_script(script_file, &block)
115
+ # this code looks overly compact in an attempt to limit local
116
+ # variable assignments that may contain large strings and
117
+ # consequently bloat the Ruby VM
118
+ run_cmd(
119
+ 'powershell',
120
+ [
121
+ '-encodedCommand',
122
+ ::WinRM::PowershellScript.new(
123
+ safe_script(script_file.is_a?(IO) ? script_file.read : script_file)
124
+ ).encoded
125
+ ],
126
+ &block
127
+ )
128
+ end
129
+
130
+ private
131
+
132
+ # @return [Integer] the default maximum number of commands which can be
133
+ # executed in one remote shell session on "older" versions of Windows
134
+ # @api private
135
+ LEGACY_LIMIT = 15
136
+
137
+ # @return [Integer] the default maximum number of commands which can be
138
+ # executed in one remote shell session on "modern" versions of Windows
139
+ # @api private
140
+ MODERN_LIMIT = 1500
141
+
142
+ # @return [String] the PowerShell command used to determine the version
143
+ # of Windows
144
+ # @api private
145
+ PS1_OS_VERSION = '[environment]::OSVersion.Version.tostring()'.freeze
146
+
147
+ # @return [Integer] the number of executed commands on the remote
148
+ # shell session
149
+ # @api private
150
+ attr_accessor :command_count
151
+
152
+ # @return [#debug,#info] the logger
153
+ # @api private
154
+ attr_reader :logger
155
+
156
+ # Creates a finalizer for this connection which will close the open
157
+ # remote shell session when the object is garabage collected or on
158
+ # Ruby VM shutdown.
159
+ #
160
+ # @param shell_id [String] the remote shell identifier
161
+ # @api private
162
+ def add_finalizer(shell_id)
163
+ ObjectSpace.define_finalizer(self, self.class.finalize(shell_id, service))
164
+ end
165
+
166
+ # @return [true,false] whether or not the number of exeecuted commands
167
+ # have exceeded the maxiumum threshold
168
+ # @api private
169
+ def command_count_exceeded?
170
+ command_count > max_commands.to_i
171
+ end
172
+
173
+ # Ensures that there is an open remote shell session.
174
+ #
175
+ # @raise [WinRM::WinRMError] if there is no open shell
176
+ # @api private
177
+ def ensure_open_shell!
178
+ fail ::WinRM::WinRMError, "#{self.class}#open must be called " \
179
+ 'before any run methods are invoked' if shell.nil?
180
+ end
181
+
182
+ # Determines the safe maximum number of commands that can be executed
183
+ # on a remote shell session by interrogating the remote host.
184
+ #
185
+ # @api private
186
+ def determine_max_commands
187
+ os_version = run_powershell_script(PS1_OS_VERSION).stdout.chomp
188
+ @max_commands = os_version < '6.2' ? LEGACY_LIMIT : MODERN_LIMIT
189
+ @max_commands -= 2 # to be safe
190
+ end
191
+
192
+ # Removes any finalizers for this connection.
193
+ #
194
+ # @api private
195
+ def remove_finalizer
196
+ ObjectSpace.undefine_finalizer(self)
197
+ end
198
+
199
+ # Closes the remote shell session and opens a new one.
200
+ #
201
+ # @api private
202
+ def reset
203
+ logger.debug("Resetting WinRM shell (Max command limit is #{max_commands})")
204
+ open
205
+ end
206
+
207
+ # Yields to a block and reties the block if certain rescuable
208
+ # exceptions are raised.
209
+ #
210
+ # @param retries [Integer] the number of times to retry before failing
211
+ # @option delay [Float] the number of seconds to wait until
212
+ # attempting a retry
213
+ # @api private
214
+ def retryable(retries, delay)
215
+ yield
216
+ rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH.call => e
217
+ if (retries -= 1) > 0
218
+ logger.info("[WinRM] connection failed. retrying in #{delay} seconds (#{e.inspect})")
219
+ sleep(delay)
220
+ retry
221
+ else
222
+ logger.warn("[WinRM] connection failed, terminating (#{e.inspect})")
223
+ raise
224
+ end
225
+ end
226
+
227
+ RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
228
+ [
229
+ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
230
+ Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
231
+ ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
232
+ HTTPClient::KeepAliveDisconnected,
233
+ HTTPClient::ConnectTimeoutError
234
+ ].freeze
235
+ end
236
+
237
+ # suppress the progress stream from leaking to stderr
238
+ def safe_script(script)
239
+ "$ProgressPreference='SilentlyContinue';" + script
240
+ end
241
+ end
242
+ end
@@ -40,12 +40,13 @@ module WinRM
40
40
  # @param [String] The XML SOAP message
41
41
  # @returns [REXML::Document] The parsed response body
42
42
  def send_request(message)
43
+ ssl_peer_fingerprint_verification!
43
44
  log_soap_message(message)
44
- hdr = {
45
- 'Content-Type' => 'application/soap+xml;charset=UTF-8',
46
- 'Content-Length' => message.length }
45
+ hdr = { 'Content-Type' => 'application/soap+xml;charset=UTF-8',
46
+ 'Content-Length' => message.length }
47
47
  resp = @httpcli.post(@endpoint, message, hdr)
48
48
  log_soap_message(resp.http_body.content)
49
+ verify_ssl_fingerprint(resp.peer_cert)
49
50
  handler = WinRM::ResponseHandler.new(resp.http_body.content, resp.status)
50
51
  handler.parse_to_xml
51
52
  end
@@ -67,6 +68,41 @@ module WinRM
67
68
  @httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
68
69
  end
69
70
 
71
+ # SSL Peer Fingerprint Verification prior to connecting
72
+ def ssl_peer_fingerprint_verification!
73
+ return unless @ssl_peer_fingerprint && ! @ssl_peer_fingerprint_verified
74
+
75
+ with_untrusted_ssl_connection do |connection|
76
+ connection_cert = connection.peer_cert_chain.last
77
+ verify_ssl_fingerprint(connection_cert)
78
+ end
79
+ @logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
80
+ @ssl_peer_fingerprint_verified = true
81
+ no_ssl_peer_verification!
82
+ end
83
+
84
+ # Connect without verification to retrieve untrusted ssl context
85
+ def with_untrusted_ssl_connection
86
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
87
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
88
+ tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
89
+ begin
90
+ ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
91
+ ssl_connection.connect
92
+ yield ssl_connection
93
+ ensure
94
+ tcp_connection.close
95
+ end
96
+ end
97
+
98
+ # compare @ssl_peer_fingerprint to current ssl context
99
+ def verify_ssl_fingerprint(cert)
100
+ return unless @ssl_peer_fingerprint
101
+ conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
102
+ return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
103
+ fail "ssl fingerprint mismatch!!!!\n"
104
+ end
105
+
70
106
  # HTTP Client receive timeout. How long should a remote call wait for a
71
107
  # for a response from WinRM?
72
108
  def receive_timeout=(sec)
@@ -112,6 +148,7 @@ module WinRM
112
148
  no_sspi_auth! if opts[:disable_sspi]
113
149
  basic_auth_only! if opts[:basic_auth_only]
114
150
  no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
151
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
115
152
  end
116
153
  end
117
154
 
@@ -3,5 +3,5 @@
3
3
  # WinRM module
4
4
  module WinRM
5
5
  # The version of the WinRM library
6
- VERSION = '1.4.0'
6
+ VERSION = '1.5.0'
7
7
  end
@@ -16,6 +16,7 @@
16
16
 
17
17
  require 'nori'
18
18
  require 'rexml/document'
19
+ require 'winrm/command_executor'
19
20
  require_relative 'helpers/powershell_script'
20
21
 
21
22
  module WinRM
@@ -26,7 +27,9 @@ module WinRM
26
27
  DEFAULT_MAX_ENV_SIZE = 153600
27
28
  DEFAULT_LOCALE = 'en-US'
28
29
 
29
- attr_reader :endpoint, :timeout
30
+ attr_reader :endpoint, :timeout, :retry_limit, :retry_delay
31
+
32
+ attr_accessor :logger
30
33
 
31
34
  # @param [String,URI] endpoint the WinRM webservice endpoint
32
35
  # @param [Symbol] transport either :kerberos(default)/:ssl/:plaintext
@@ -39,7 +42,8 @@ module WinRM
39
42
  @timeout = DEFAULT_TIMEOUT
40
43
  @max_env_sz = DEFAULT_MAX_ENV_SIZE
41
44
  @locale = DEFAULT_LOCALE
42
- @logger = Logging.logger[self]
45
+ setup_logger
46
+ configure_retries(opts)
43
47
  case transport
44
48
  when :kerberos
45
49
  require 'gssapi'
@@ -94,6 +98,7 @@ module WinRM
94
98
  # :env_vars => {:myvar1 => 'val1', :myvar2 => 'var2'}
95
99
  # @return [String] The ShellId from the SOAP response. This is our open shell instance on the remote machine.
96
100
  def open_shell(shell_opts = {}, &block)
101
+ logger.debug("[WinRM] opening remote shell on #{@endpoint}")
97
102
  i_stream = shell_opts.has_key?(:i_stream) ? shell_opts[:i_stream] : 'stdin'
98
103
  o_stream = shell_opts.has_key?(:o_stream) ? shell_opts[:o_stream] : 'stdout stderr'
99
104
  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)
@@ -125,6 +130,7 @@ module WinRM
125
130
 
126
131
  resp_doc = send_message(builder.target!)
127
132
  shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
133
+ logger.debug("[WinRM] remote shell #{shell_id} is open on #{@endpoint}")
128
134
 
129
135
  if block_given?
130
136
  begin
@@ -279,6 +285,7 @@ module WinRM
279
285
  # @param [String] shell_id The shell id on the remote machine. See #open_shell
280
286
  # @return [true] This should have more error checking but it just returns true for now.
281
287
  def close_shell(shell_id)
288
+ logger.debug("[WinRM] closing remote shell #{shell_id} on #{@endpoint}")
282
289
  builder = Builder::XmlMarkup.new
283
290
  builder.instruct!(:xml, :encoding => 'UTF-8')
284
291
 
@@ -288,38 +295,57 @@ module WinRM
288
295
  end
289
296
 
290
297
  resp = send_message(builder.target!)
298
+ logger.debug("[WinRM] remote shell #{shell_id} closed")
291
299
  true
292
300
  end
293
301
 
302
+ # DEPRECATED: Use WinRM::CommandExecutor#run_cmd instead
294
303
  # Run a CMD command
295
304
  # @param [String] command The command to run on the remote system
296
305
  # @param [Array <String>] arguments arguments to the command
297
306
  # @param [String] an existing and open shell id to reuse
298
307
  # @return [Hash] :stdout and :stderr
299
308
  def run_cmd(command, arguments = [], &block)
300
- command_output = nil
301
- open_shell do |shell_id|
302
- run_command(shell_id, command, arguments) do |command_id|
303
- command_output = get_command_output(shell_id, command_id, &block)
304
- end
309
+ logger.warn("WinRM::WinRMWebService#run_cmd is deprecated. Use WinRM::CommandExecutor#run_cmd instead")
310
+ create_executor do |executor|
311
+ executor.run_cmd(command, arguments, &block)
305
312
  end
306
- command_output
307
313
  end
308
314
  alias :cmd :run_cmd
309
315
 
310
-
316
+ # DEPRECATED: Use WinRM::CommandExecutor#run_powershell_script instead
311
317
  # Run a Powershell script that resides on the local box.
312
318
  # @param [IO,String] script_file an IO reference for reading the Powershell script or the actual file contents
313
319
  # @param [String] an existing and open shell id to reuse
314
320
  # @return [Hash] :stdout and :stderr
315
321
  def run_powershell_script(script_file, &block)
316
- # if an IO object is passed read it..otherwise assume the contents of the file were passed
317
- script_text = script_file.respond_to?(:read) ? script_file.read : script_file
318
- script = WinRM::PowershellScript.new(script_text)
319
- run_cmd("powershell -encodedCommand #{script.encoded()}", &block)
322
+ logger.warn("WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_cmd instead")
323
+ create_executor do |executor|
324
+ executor.run_powershell_script(script_file, &block)
325
+ end
320
326
  end
321
327
  alias :powershell :run_powershell_script
322
328
 
329
+ # Creates a CommandExecutor initialized with this WinRMWebService
330
+ # If called with a block, create_executor yields an executor and
331
+ # ensures that the executor is closed after the block completes.
332
+ # The CommandExecutor is simply returned if no block is given.
333
+ # @yieldparam [CommandExecutor] a CommandExecutor instance
334
+ # @return [CommandExecutor] a CommandExecutor instance
335
+ def create_executor(&block)
336
+ executor = CommandExecutor.new(self)
337
+ executor.open
338
+
339
+ if block_given?
340
+ begin
341
+ yield executor
342
+ ensure
343
+ executor.close
344
+ end
345
+ else
346
+ executor
347
+ end
348
+ end
323
349
 
324
350
  # Run a WQL Query
325
351
  # @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
@@ -362,12 +388,23 @@ module WinRM
362
388
  alias :wql :run_wql
363
389
 
364
390
  def toggle_nori_type_casting(to)
365
- @logger.warn('toggle_nori_type_casting is deprecated and has no effect, ' +
391
+ logger.warn('toggle_nori_type_casting is deprecated and has no effect, ' +
366
392
  'please remove calls to it')
367
393
  end
368
394
 
369
395
  private
370
396
 
397
+ def setup_logger
398
+ @logger = Logging.logger[self]
399
+ @logger.level = :warn
400
+ @logger.add_appenders(Logging.appenders.stdout)
401
+ end
402
+
403
+ def configure_retries(opts)
404
+ @retry_delay = opts[:retry_delay] || 10
405
+ @retry_limit = opts[:retry_limit] || 3
406
+ end
407
+
371
408
  def namespaces
372
409
  {
373
410
  'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
@@ -0,0 +1,440 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the 'License');
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an 'AS IS' BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'winrm/command_executor'
20
+
21
+ require 'base64'
22
+ require 'securerandom'
23
+
24
+ describe WinRM::CommandExecutor, unit: true do
25
+ let(:logged_output) { StringIO.new }
26
+ let(:shell_id) { 'shell-123' }
27
+ let(:executor_args) { [service, logger] }
28
+ let(:executor) { WinRM::CommandExecutor.new(service) }
29
+ let(:service) do
30
+ double(
31
+ 'winrm_service',
32
+ logger: Logging.logger['test'],
33
+ retry_limit: 1,
34
+ retry_delay: 1
35
+ )
36
+ end
37
+
38
+ let(:version_output) do
39
+ o = ::WinRM::Output.new
40
+ o[:exitcode] = 0
41
+ o[:data].concat([{ stdout: '6.3.9600.0\r\n' }])
42
+ o
43
+ end
44
+
45
+ before do
46
+ allow(service).to receive(:open_shell).and_return(shell_id)
47
+
48
+ stub_powershell_script(
49
+ shell_id,
50
+ "$ProgressPreference='SilentlyContinue';[environment]::OSVersion.Version.tostring()",
51
+ version_output
52
+ )
53
+ end
54
+
55
+ describe '#close' do
56
+ it 'calls service#close_shell' do
57
+ executor.open
58
+ expect(service).to receive(:close_shell).with(shell_id)
59
+
60
+ executor.close
61
+ end
62
+
63
+ it 'only calls service#close_shell once for multiple calls' do
64
+ executor.open
65
+ expect(service).to receive(:close_shell).with(shell_id).once
66
+
67
+ executor.close
68
+ executor.close
69
+ executor.close
70
+ end
71
+
72
+ it 'undefines finalizer' do
73
+ allow(service).to receive(:close_shell)
74
+ allow(ObjectSpace).to receive(:define_finalizer) { |e, _| e == executor }
75
+ expect(ObjectSpace).to receive(:undefine_finalizer).with(executor)
76
+ executor.open
77
+
78
+ executor.close
79
+ end
80
+ end
81
+
82
+ describe '#open' do
83
+ it 'calls service#open_shell' do
84
+ expect(service).to receive(:open_shell).and_return(shell_id)
85
+
86
+ executor.open
87
+ end
88
+
89
+ it 'defines a finalizer' do
90
+ expect(ObjectSpace).to receive(:define_finalizer) do |e, _|
91
+ expect(e).to eq(executor)
92
+ end
93
+
94
+ executor.open
95
+ end
96
+
97
+ it 'returns a shell id as a string' do
98
+ expect(executor.open).to eq shell_id
99
+ end
100
+
101
+ describe 'failed connection attempts' do
102
+ let(:error) { HTTPClient::ConnectTimeoutError }
103
+ let(:limit) { 3 }
104
+ let(:delay) { 0.1 }
105
+
106
+ before do
107
+ allow(service).to receive(:open_shell).and_raise(error)
108
+ allow(service).to receive(:retry_delay).and_return(delay)
109
+ allow(service).to receive(:retry_limit).and_return(limit)
110
+ end
111
+
112
+ it 'attempts to connect :retry_limit times' do
113
+ begin
114
+ allow(service).to receive(:open_shell).exactly.times(limit)
115
+ executor.open
116
+ rescue # rubocop:disable Lint/HandleExceptions
117
+ # the raise is not what is being tested here, rather its side-effect
118
+ end
119
+ end
120
+
121
+ it 'raises the inner error after retries' do
122
+ expect { executor.open }.to raise_error(error)
123
+ end
124
+ end
125
+
126
+ describe 'for modern windows distributions' do
127
+ let(:version_output) do
128
+ o = ::WinRM::Output.new
129
+ o[:exitcode] = 0
130
+ o[:data].concat([{ stdout: '6.3.9600.0\r\n' }])
131
+ o
132
+ end
133
+
134
+ it 'sets #max_commands to 1500 - 2' do
135
+ expect(executor.max_commands).to eq nil
136
+ executor.open
137
+
138
+ expect(executor.max_commands).to eq(1500 - 2)
139
+ end
140
+ end
141
+
142
+ describe 'for older/legacy windows distributions' do
143
+ let(:version_output) do
144
+ o = ::WinRM::Output.new
145
+ o[:exitcode] = 0
146
+ o[:data].concat([{ stdout: '6.1.8500.0\r\n' }])
147
+ o
148
+ end
149
+
150
+ it 'sets #max_commands to 15 - 2' do
151
+ expect(executor.max_commands).to eq nil
152
+ executor.open
153
+
154
+ expect(executor.max_commands).to eq(15 - 2)
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#run_cmd' do
160
+ describe 'when #open has not been previously called' do
161
+ it 'raises a WinRMError error' do
162
+ expect { executor.run_cmd('nope') }.to raise_error(
163
+ ::WinRM::WinRMError,
164
+ "#{executor.class}#open must be called before any run methods are invoked"
165
+ )
166
+ end
167
+ end
168
+
169
+ describe 'when #open has been previously called' do
170
+ let(:command_id) { 'command-123' }
171
+
172
+ let(:echo_output) do
173
+ o = ::WinRM::Output.new
174
+ o[:exitcode] = 0
175
+ o[:data].concat([
176
+ { stdout: 'Hello\r\n' },
177
+ { stderr: 'Psst\r\n' }
178
+ ])
179
+ o
180
+ end
181
+
182
+ before do
183
+ stub_cmd(shell_id, 'echo', ['Hello'], echo_output, command_id)
184
+
185
+ executor.open
186
+ end
187
+
188
+ it 'calls service#run_command' do
189
+ expect(service).to receive(:run_command).with(shell_id, 'echo', ['Hello'])
190
+
191
+ executor.run_cmd('echo', ['Hello'])
192
+ end
193
+
194
+ it 'calls service#get_command_output to get results' do
195
+ expect(service).to receive(:get_command_output).with(shell_id, command_id)
196
+
197
+ executor.run_cmd('echo', ['Hello'])
198
+ end
199
+
200
+ it 'calls service#get_command_output with a block to get results' do
201
+ blk = proc { |_, _| 'something' }
202
+ expect(service).to receive(:get_command_output).with(shell_id, command_id, &blk)
203
+
204
+ executor.run_cmd('echo', ['Hello'], &blk)
205
+ end
206
+
207
+ it 'returns an Output object hash' do
208
+ expect(executor.run_cmd('echo', ['Hello'])).to eq echo_output
209
+ end
210
+
211
+ it 'runs the block in #get_command_output when given' do
212
+ io_out = StringIO.new
213
+ io_err = StringIO.new
214
+ stub_cmd(
215
+ shell_id,
216
+ 'echo',
217
+ ['Hello'],
218
+ echo_output,
219
+ command_id
220
+ ).and_yield(echo_output.stdout, echo_output.stderr)
221
+ output = executor.run_cmd('echo', ['Hello']) do |stdout, stderr|
222
+ io_out << stdout if stdout
223
+ io_err << stderr if stderr
224
+ end
225
+
226
+ expect(io_out.string).to eq 'Hello\r\n'
227
+ expect(io_err.string).to eq 'Psst\r\n'
228
+ expect(output).to eq echo_output
229
+ end
230
+ end
231
+
232
+ describe 'when called many times over time' do
233
+ # use a 'old' version of windows with lower max_commands threshold
234
+ # to trigger quicker shell recyles
235
+ let(:version_output) do
236
+ o = ::WinRM::Output.new
237
+ o[:exitcode] = 0
238
+ o[:data].concat([{ stdout: '6.1.8500.0\r\n' }])
239
+ o
240
+ end
241
+
242
+ let(:echo_output) do
243
+ o = ::WinRM::Output.new
244
+ o[:exitcode] = 0
245
+ o[:data].concat([{ stdout: 'Hello\r\n' }])
246
+ o
247
+ end
248
+
249
+ before do
250
+ allow(service).to receive(:open_shell).and_return('s1', 's2')
251
+ allow(service).to receive(:close_shell)
252
+ allow(service).to receive(:run_command).and_yield('command-xxx')
253
+ allow(service).to receive(:get_command_output).and_return(echo_output)
254
+ stub_powershell_script(
255
+ 's1',
256
+ "$ProgressPreference='SilentlyContinue';[environment]::OSVersion.Version.tostring()",
257
+ version_output
258
+ )
259
+ end
260
+
261
+ it 'resets the shell when #max_commands threshold is tripped' do
262
+ iterations = 35
263
+ reset_times = iterations / (15 - 2)
264
+
265
+ expect(service).to receive(:close_shell).exactly(reset_times).times
266
+ executor.open
267
+ iterations.times { executor.run_cmd('echo', ['Hello']) }
268
+ end
269
+ end
270
+ end
271
+
272
+ describe '#run_powershell_script' do
273
+ describe 'when #open has not been previously called' do
274
+ it 'raises a WinRMError error' do
275
+ expect { executor.run_powershell_script('nope') }.to raise_error(
276
+ ::WinRM::WinRMError,
277
+ "#{executor.class}#open must be called before any run methods are invoked"
278
+ )
279
+ end
280
+ end
281
+
282
+ describe 'when #open has been previously called' do
283
+ let(:command_id) { 'command-123' }
284
+
285
+ let(:echo_output) do
286
+ o = ::WinRM::Output.new
287
+ o[:exitcode] = 0
288
+ o[:data].concat([
289
+ { stdout: 'Hello\r\n' },
290
+ { stderr: 'Psst\r\n' }
291
+ ])
292
+ o
293
+ end
294
+
295
+ before do
296
+ stub_powershell_script(
297
+ shell_id,
298
+ "$ProgressPreference='SilentlyContinue';echo Hello",
299
+ echo_output,
300
+ command_id
301
+ )
302
+
303
+ executor.open
304
+ end
305
+
306
+ it 'calls service#run_command' do
307
+ expect(service).to receive(:run_command).with(
308
+ shell_id,
309
+ 'powershell',
310
+ [
311
+ '-encodedCommand',
312
+ ::WinRM::PowershellScript.new("$ProgressPreference='SilentlyContinue';echo Hello")
313
+ .encoded
314
+ ]
315
+ )
316
+
317
+ executor.run_powershell_script('echo Hello')
318
+ end
319
+
320
+ it 'calls service#get_command_output to get results' do
321
+ expect(service).to receive(:get_command_output).with(shell_id, command_id)
322
+
323
+ executor.run_powershell_script('echo Hello')
324
+ end
325
+
326
+ it 'calls service#get_command_output with a block to get results' do
327
+ blk = proc { |_, _| 'something' }
328
+ expect(service).to receive(:get_command_output).with(shell_id, command_id, &blk)
329
+
330
+ executor.run_powershell_script('echo Hello', &blk)
331
+ end
332
+
333
+ it 'returns an Output object hash' do
334
+ expect(executor.run_powershell_script('echo Hello')).to eq echo_output
335
+ end
336
+
337
+ it 'runs the block in #get_command_output when given' do
338
+ io_out = StringIO.new
339
+ io_err = StringIO.new
340
+ stub_cmd(shell_id, 'echo', ['Hello'], echo_output, command_id)
341
+ .and_yield(echo_output.stdout, echo_output.stderr)
342
+ output = executor.run_powershell_script('echo Hello') do |stdout, stderr|
343
+ io_out << stdout if stdout
344
+ io_err << stderr if stderr
345
+ end
346
+
347
+ expect(io_out.string).to eq 'Hello\r\n'
348
+ expect(io_err.string).to eq 'Psst\r\n'
349
+ expect(output).to eq echo_output
350
+ end
351
+ end
352
+
353
+ describe 'when called many times over time' do
354
+ # use a 'old' version of windows with lower max_commands threshold
355
+ # to trigger quicker shell recyles
356
+ let(:version_output) do
357
+ o = ::WinRM::Output.new
358
+ o[:exitcode] = 0
359
+ o[:data].concat([{ stdout: '6.1.8500.0\r\n' }])
360
+ o
361
+ end
362
+
363
+ let(:echo_output) do
364
+ o = ::WinRM::Output.new
365
+ o[:exitcode] = 0
366
+ o[:data].concat([{ stdout: 'Hello\r\n' }])
367
+ o
368
+ end
369
+
370
+ before do
371
+ allow(service).to receive(:open_shell).and_return('s1', 's2')
372
+ allow(service).to receive(:close_shell)
373
+ allow(service).to receive(:run_command).and_yield('command-xxx')
374
+ allow(service).to receive(:get_command_output).and_return(echo_output)
375
+ stub_powershell_script(
376
+ 's1',
377
+ "$ProgressPreference='SilentlyContinue';[environment]::OSVersion.Version.tostring()",
378
+ version_output
379
+ )
380
+ end
381
+
382
+ it 'resets the shell when #max_commands threshold is tripped' do
383
+ iterations = 35
384
+ reset_times = iterations / (15 - 2)
385
+
386
+ expect(service).to receive(:close_shell).exactly(reset_times).times
387
+ executor.open
388
+ iterations.times { executor.run_powershell_script('echo Hello') }
389
+ end
390
+ end
391
+ end
392
+
393
+ describe '#shell' do
394
+ it 'is initially nil' do
395
+ expect(executor.shell).to eq nil
396
+ end
397
+
398
+ it 'is set after #open is called' do
399
+ executor.open
400
+
401
+ expect(executor.shell).to eq shell_id
402
+ end
403
+ end
404
+
405
+ def decode(powershell)
406
+ Base64.strict_decode64(powershell).encode('UTF-8', 'UTF-16LE')
407
+ end
408
+
409
+ def debug_line_with(msg)
410
+ /^D, .* : #{Regexp.escape(msg)}/
411
+ end
412
+
413
+ def regexify(string)
414
+ Regexp.new(Regexp.escape(string))
415
+ end
416
+
417
+ def regexify_line(string)
418
+ Regexp.new("^#{Regexp.escape(string)}$")
419
+ end
420
+
421
+ # rubocop:disable Metrics/ParameterLists
422
+ def stub_cmd(shell_id, cmd, args, output, command_id = nil, &block)
423
+ command_id ||= SecureRandom.uuid
424
+
425
+ allow(service).to receive(:run_command).with(shell_id, cmd, args).and_yield(command_id)
426
+ allow(service).to receive(:get_command_output).with(shell_id, command_id, &block)
427
+ .and_return(output)
428
+ end
429
+
430
+ def stub_powershell_script(shell_id, script, output, command_id = nil)
431
+ stub_cmd(
432
+ shell_id,
433
+ 'powershell',
434
+ ['-encodedCommand', ::WinRM::PowershellScript.new(script).encoded],
435
+ output,
436
+ command_id
437
+ )
438
+ end
439
+ # rubocop:enable Metrics/ParameterLists
440
+ end
@@ -6,10 +6,10 @@ describe 'issue 59', integration: true do
6
6
 
7
7
  describe 'long running script without output' do
8
8
  it 'should not error' do
9
- output = @winrm.powershell('sleep 60; Write-Host "Hello"')
10
- expect(output).to have_exit_code 0
11
- expect(output).to have_stdout_match(/Hello/)
12
- expect(output).to have_no_stderr
9
+ out = @winrm.powershell('$ProgressPreference="SilentlyContinue";sleep 60; Write-Host "Hello"')
10
+ expect(out).to have_exit_code 0
11
+ expect(out).to have_stdout_match(/Hello/)
12
+ expect(out).to have_no_stderr
13
13
  end
14
14
  end
15
15
  end
@@ -4,12 +4,6 @@ describe 'winrm client powershell', integration: true do
4
4
  @winrm = winrm_connection
5
5
  end
6
6
 
7
- describe 'empty string' do
8
- subject(:output) { @winrm.powershell('') }
9
- it { should have_exit_code 4_294_770_688 }
10
- it { should have_stderr_match(/Cannot process the command because of a missing parameter/) }
11
- end
12
-
13
7
  describe 'ipconfig' do
14
8
  subject(:output) { @winrm.powershell('ipconfig') }
15
9
  it { should have_exit_code 0 }
@@ -7,13 +7,39 @@ require_relative 'matchers'
7
7
 
8
8
  # Creates a WinRM connection for integration tests
9
9
  module ConnectionHelper
10
+ # rubocop:disable AbcSize
10
11
  def winrm_connection
11
- config = symbolize_keys(YAML.load(File.read(winrm_config_path)))
12
- config[:options].merge!(basic_auth_only: true) unless config[:auth_type].eql? :kerberos
13
12
  winrm = WinRM::WinRMWebService.new(
14
13
  config[:endpoint], config[:auth_type].to_sym, config[:options])
14
+ winrm.logger.level = :error
15
15
  winrm
16
16
  end
17
+ # rubocop:enable AbcSize
18
+
19
+ def config
20
+ @config ||= begin
21
+ cfg = symbolize_keys(YAML.load(File.read(winrm_config_path)))
22
+ cfg[:options].merge!(basic_auth_only: true) unless cfg[:auth_type].eql? :kerberos
23
+ merge_environment!(cfg)
24
+ cfg
25
+ end
26
+ end
27
+
28
+ def merge_environment!(config)
29
+ merge_config_option_from_environment(config, 'user')
30
+ merge_config_option_from_environment(config, 'pass')
31
+ merge_config_option_from_environment(config, 'no_ssl_peer_verification')
32
+ if ENV['use_ssl_peer_fingerprint']
33
+ config[:options][:ssl_peer_fingerprint] = ENV['winrm_cert']
34
+ end
35
+ config[:endpoint] = ENV['winrm_endpoint'] if ENV['winrm_endpoint']
36
+ config[:auth_type] = ENV['winrm_auth_type'] if ENV['winrm_auth_type']
37
+ end
38
+
39
+ def merge_config_option_from_environment(config, key)
40
+ env_key = 'winrm_' + key
41
+ config[:options][key.to_sym] = ENV[env_key] if ENV[env_key]
42
+ end
17
43
 
18
44
  def winrm_config_path
19
45
  # Copy config-example.yml to config.yml and edit for your local configuration
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winrm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Wanek
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-09 00:00:00.000000000 Z
12
+ date: 2016-01-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gssapi
@@ -198,9 +198,11 @@ files:
198
198
  - README.md
199
199
  - Rakefile
200
200
  - Vagrantfile
201
+ - appveyor.yml
201
202
  - bin/rwinrm
202
203
  - changelog.md
203
204
  - lib/winrm.rb
205
+ - lib/winrm/command_executor.rb
204
206
  - lib/winrm/exceptions/exceptions.rb
205
207
  - lib/winrm/helpers/iso8601_duration.rb
206
208
  - lib/winrm/helpers/powershell_script.rb
@@ -213,6 +215,7 @@ files:
213
215
  - preamble
214
216
  - spec/auth_timeout_spec.rb
215
217
  - spec/cmd_spec.rb
218
+ - spec/command_executor_spec.rb
216
219
  - spec/config-example.yml
217
220
  - spec/exception_spec.rb
218
221
  - spec/issue_59_spec.rb
@@ -254,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
257
  version: '0'
255
258
  requirements: []
256
259
  rubyforge_project:
257
- rubygems_version: 2.4.5
260
+ rubygems_version: 2.4.8
258
261
  signing_key:
259
262
  specification_version: 4
260
263
  summary: Ruby library for Windows Remote Management