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