winrm 1.8.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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