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
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2016 Matt Wrock <matt@mattwrock.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
+ module WinRM
18
+ module PSRP
19
+ module MessageData
20
+ # session capability message type
21
+ class SessionCapability < Base
22
+ def protocol_version
23
+ clixml[:version].select { |v| v.attributes['N'] == 'protocolversion' }.first
24
+ end
25
+
26
+ def ps_version
27
+ clixml[:version].select { |v| v.attributes['N'] == 'PSVersion' }.first
28
+ end
29
+
30
+ def serialization_version
31
+ clixml[:version].select { |v| v.attributes['N'] == 'SerializationVersion' }.first
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2016 Matt Wrock <matt@mattwrock.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_relative 'fragment'
18
+
19
+ module WinRM
20
+ # PowerShell Remoting Protcol module
21
+ module PSRP
22
+ # PowerShell Remoting Protocol message fragmenter.
23
+ class MessageDefragmenter
24
+ def initialize
25
+ @messages = {}
26
+ end
27
+
28
+ def defragment(base64_bytes)
29
+ fragment = fragment_from(Base64.decode64(base64_bytes))
30
+
31
+ @messages[fragment.object_id] ||= []
32
+ @messages[fragment.object_id].push fragment
33
+
34
+ if fragment.end_fragment
35
+ blob = []
36
+ @messages.delete(fragment.object_id).each { |frag| blob += frag.blob }
37
+ return message_from(blob.pack('C*'))
38
+ end
39
+ end
40
+
41
+ def fragment_from(byte_string)
42
+ Fragment.new(
43
+ byte_string[0..7].reverse.unpack('Q')[0],
44
+ byte_string[21..-1].bytes,
45
+ byte_string[8..15].reverse.unpack('Q')[0],
46
+ byte_string[16].unpack('C')[0][0] == 1,
47
+ byte_string[16].unpack('C')[0][1] == 1
48
+ )
49
+ end
50
+
51
+ def message_from(byte_string)
52
+ Message.new(
53
+ '00000000-0000-0000-0000-000000000000',
54
+ byte_string[4..7].unpack('V')[0],
55
+ byte_string[40..-1],
56
+ '00000000-0000-0000-0000-000000000000',
57
+ byte_string[0..3].unpack('V')[0]
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
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 'erubis'
18
+ require_relative 'message'
19
+
20
+ module WinRM
21
+ module PSRP
22
+ # Creates WinRM::PSRP::Message instances for various PSRP messages
23
+ class MessageFactory
24
+ class << self
25
+ # Creates a new session capability PSRP message.
26
+ # @param runspace_pool_id [String] The UUID of the remote shell/runspace pool.
27
+ def session_capability_message(runspace_pool_id)
28
+ Message.new(
29
+ runspace_pool_id,
30
+ Message::MESSAGE_TYPES[:session_capability],
31
+ render('session_capability')
32
+ )
33
+ end
34
+
35
+ # Creates a new init runspace pool PSRP message.
36
+ # @param runspace_pool_id [String] The UUID of the remote shell/runspace pool.
37
+ def init_runspace_pool_message(runspace_pool_id)
38
+ Message.new(
39
+ runspace_pool_id,
40
+ Message::MESSAGE_TYPES[:init_runspacepool],
41
+ render('init_runspace_pool')
42
+ )
43
+ end
44
+
45
+ # Creates a new PSRP message that creates pipline to execute a command.
46
+ # @param runspace_pool_id [String] The UUID of the remote shell/runspace pool.
47
+ # @param pipeline_id [String] The UUID to correlate the command/pipeline
48
+ # response.
49
+ # @param command [String] The command passed to Invoke-Expression.
50
+ def create_pipeline_message(runspace_pool_id, pipeline_id, command)
51
+ Message.new(
52
+ runspace_pool_id,
53
+ Message::MESSAGE_TYPES[:create_pipeline],
54
+ render('create_pipeline', command: command.encode(xml: :text)),
55
+ pipeline_id
56
+ )
57
+ end
58
+
59
+ private
60
+
61
+ # Renders the specified template with the given context
62
+ # @param template [String] The base filename of the PSRP message template.
63
+ # @param context [Hash] Any options required for rendering the template.
64
+ # @return [String] The rendered XML PSRP message.
65
+ # @api private
66
+ def render(template, context = nil)
67
+ template_path = File.expand_path(
68
+ "#{File.dirname(__FILE__)}/#{template}.xml.erb")
69
+ template = File.read(template_path)
70
+ Erubis::Eruby.new(template).result(context)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2016 Matt Wrock <matt@mattwrock.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_relative 'fragment'
18
+
19
+ module WinRM
20
+ # PowerShell Remoting Protcol module
21
+ module PSRP
22
+ # PowerShell Remoting Protocol message fragmenter.
23
+ class MessageFragmenter
24
+ DEFAULT_BLOB_LENGTH = 32_768
25
+
26
+ def initialize(max_blob_length = DEFAULT_BLOB_LENGTH)
27
+ @object_id = 0
28
+ @max_blob_length = max_blob_length || DEFAULT_BLOB_LENGTH
29
+ end
30
+
31
+ attr_reader :object_id
32
+ attr_accessor :max_blob_length
33
+
34
+ def fragment(message)
35
+ @object_id += 1
36
+ message_bytes = message.bytes
37
+ bytes_fragmented = 0
38
+ fragment_id = 0
39
+ fragment = nil
40
+
41
+ while bytes_fragmented < message_bytes.length
42
+ last_byte = bytes_fragmented + max_blob_length
43
+ last_byte = message_bytes.length if last_byte > message_bytes.length
44
+ fragment = Fragment.new(
45
+ object_id,
46
+ message.bytes[bytes_fragmented..last_byte - 1],
47
+ fragment_id,
48
+ bytes_fragmented == 0,
49
+ last_byte == message_bytes.length
50
+ )
51
+ fragment_id += 1
52
+ bytes_fragmented = last_byte
53
+ yield fragment if block_given?
54
+ end
55
+
56
+ fragment
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,120 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2016 Matt Wrock <matt@mattwrock.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 'base64'
18
+ require_relative 'message'
19
+
20
+ module WinRM
21
+ module PSRP
22
+ # Handles decoding a raw powershell output response
23
+ class PowershellOutputDecoder
24
+ # Decode the raw SOAP output into decoded PSRP message,
25
+ # Removes BOM and replaces encoded line endings
26
+ # @param raw_output [String] The raw encoded output
27
+ # @return [String] The decoded output
28
+ def decode(message)
29
+ case message.type
30
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_output]
31
+ decode_pipeline_output(message)
32
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:runspacepool_host_call]
33
+ decode_host_call(message)
34
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_host_call]
35
+ decode_host_call(message)
36
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:error_record]
37
+ decode_error_record(message)
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def decode_pipeline_output(message)
44
+ message.parsed_data.output
45
+ end
46
+
47
+ def decode_host_call(message)
48
+ text = begin
49
+ case message.parsed_data.method_identifier
50
+ when /WriteLine/, 'WriteErrorLine'
51
+ "#{message.parsed_data.method_parameters[:s]}\r\n"
52
+ when 'WriteDebugLine'
53
+ "Debug: #{message.parsed_data.method_parameters[:s]}\r\n"
54
+ when 'WriteWarningLine'
55
+ "Warning: #{message.parsed_data.method_parameters[:s]}\r\n"
56
+ when 'WriteVerboseLine'
57
+ "Verbose: #{message.parsed_data.method_parameters[:s]}\r\n"
58
+ when /Write[1-2]/
59
+ message.parsed_data.method_parameters[:s]
60
+ end
61
+ end
62
+
63
+ hex_decode(text)
64
+ end
65
+
66
+ def decode_error_record(message)
67
+ parsed = message.parsed_data
68
+ text = begin
69
+ case parsed.fully_qualified_error_id
70
+ when 'Microsoft.PowerShell.Commands.WriteErrorException'
71
+ render_write_error_exception(parsed)
72
+ when 'NativeCommandError'
73
+ render_native_command_error(parsed)
74
+ when 'NativeCommandErrorMessage'
75
+ parsed.exception[:message]
76
+ else
77
+ render_exception(parsed)
78
+ end
79
+ end
80
+
81
+ hex_decode(text)
82
+ end
83
+
84
+ def render_write_error_exception(parsed)
85
+ <<EOH
86
+ #{parsed.invocation_info[:line]} : #{parsed.exception[:message]}
87
+ + CategoryInfo : #{parsed.error_category_message}
88
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
89
+ EOH
90
+ end
91
+
92
+ def render_exception(parsed)
93
+ <<EOH
94
+ #{parsed.exception[:message]}
95
+ #{parsed.invocation_info[:position_message]}
96
+ + CategoryInfo : #{parsed.error_category_message}
97
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
98
+ EOH
99
+ end
100
+
101
+ def render_native_command_error(parsed)
102
+ <<EOH
103
+ #{parsed.invocation_info[:my_command]} : #{parsed.exception[:message]}
104
+ + CategoryInfo : #{parsed.error_category_message}
105
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
106
+ EOH
107
+ end
108
+
109
+ private
110
+
111
+ def hex_decode(text)
112
+ return unless text
113
+
114
+ text.gsub(/_x(\h\h\h\h)_/) do
115
+ Regexp.last_match[1].hex.chr
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,93 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2016 Matt Wrock <matt@mattwrock.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_relative 'powershell_output_decoder'
19
+ require_relative 'message_defragmenter'
20
+
21
+ module WinRM
22
+ module PSRP
23
+ # Class for reading powershell responses in Receive_Response messages
24
+ class ReceiveResponseReader < WSMV::ReceiveResponseReader
25
+ # Creates a new ReceiveResponseReader
26
+ # @param transport [HttpTransport] The WinRM SOAP transport
27
+ # @param logger [Logger] The logger to log diagnostic messages to
28
+ def initialize(transport, logger)
29
+ super
30
+ @output_decoder = PowershellOutputDecoder.new
31
+ end
32
+
33
+ # Reads PSRP messages sent in one or more receive response messages
34
+ # @param wsmv_message [WinRM::WSMV::Base] A wsmv message to send to endpoint
35
+ # @param wait_for_done_state whether to poll for a CommandState of Done
36
+ # @yield [Message] PSRP Message in response
37
+ # @yieldreturn [Array<Message>] All messages in response
38
+ def read_message(wsmv_message, wait_for_done_state = false)
39
+ messages = []
40
+ defragmenter = MessageDefragmenter.new
41
+ read_response(wsmv_message, wait_for_done_state) do |stream|
42
+ message = defragmenter.defragment(stream[:text])
43
+ next unless message
44
+ if block_given?
45
+ yield message
46
+ else
47
+ messages.push(message)
48
+ end
49
+ end
50
+ messages unless block_given?
51
+ end
52
+
53
+ # Reads streams and returns decoded output
54
+ # @param wsmv_message [WinRM::WSMV::Base] A wsmv message to send to endpoint
55
+ # @yieldparam [string] standard out response text
56
+ # @yieldparam [string] standard error response text
57
+ # @yieldreturn [WinRM::Output] The command output
58
+ def read_output(wsmv_message)
59
+ with_output do |output|
60
+ read_message(wsmv_message, true) do |message|
61
+ exit_code = find_exit_code(message)
62
+ output.exitcode = exit_code if exit_code
63
+ decoded_text = @output_decoder.decode(message)
64
+ next unless decoded_text
65
+ out = { stream_type(message) => decoded_text }
66
+ output << out
67
+ yield [out[:stdout], out[:stderr]] if block_given?
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def stream_type(message)
75
+ type = :stdout
76
+ case message.type
77
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:error_record]
78
+ type = :stderr
79
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_host_call]
80
+ type = :stderr if message.data.include?('WriteError')
81
+ end
82
+ type
83
+ end
84
+
85
+ def find_exit_code(message)
86
+ parsed = message.parsed_data
87
+ return nil unless parsed.is_a?(MessageData::PipelineHostCall)
88
+
89
+ parsed.method_parameters[:i32].to_i if parsed.method_identifier == 'SetShouldExit'
90
+ end
91
+ end
92
+ end
93
+ end