winrm 1.4.0 → 1.5.0

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