chef-winrm 2.3.10

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +277 -0
  4. data/bin/rwinrm +90 -0
  5. data/lib/winrm/connection.rb +84 -0
  6. data/lib/winrm/connection_opts.rb +92 -0
  7. data/lib/winrm/exceptions.rb +88 -0
  8. data/lib/winrm/http/response_handler.rb +127 -0
  9. data/lib/winrm/http/transport.rb +466 -0
  10. data/lib/winrm/http/transport_factory.rb +64 -0
  11. data/lib/winrm/output.rb +58 -0
  12. data/lib/winrm/psrp/create_pipeline.xml.erb +167 -0
  13. data/lib/winrm/psrp/fragment.rb +68 -0
  14. data/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -0
  15. data/lib/winrm/psrp/message.rb +128 -0
  16. data/lib/winrm/psrp/message_data/base.rb +47 -0
  17. data/lib/winrm/psrp/message_data/error_record.rb +68 -0
  18. data/lib/winrm/psrp/message_data/pipeline_host_call.rb +30 -0
  19. data/lib/winrm/psrp/message_data/pipeline_output.rb +48 -0
  20. data/lib/winrm/psrp/message_data/pipeline_state.rb +38 -0
  21. data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +30 -0
  22. data/lib/winrm/psrp/message_data/runspacepool_state.rb +37 -0
  23. data/lib/winrm/psrp/message_data/session_capability.rb +34 -0
  24. data/lib/winrm/psrp/message_data.rb +40 -0
  25. data/lib/winrm/psrp/message_defragmenter.rb +62 -0
  26. data/lib/winrm/psrp/message_factory.rb +86 -0
  27. data/lib/winrm/psrp/message_fragmenter.rb +58 -0
  28. data/lib/winrm/psrp/powershell_output_decoder.rb +142 -0
  29. data/lib/winrm/psrp/receive_response_reader.rb +95 -0
  30. data/lib/winrm/psrp/session_capability.xml.erb +7 -0
  31. data/lib/winrm/psrp/uuid.rb +39 -0
  32. data/lib/winrm/shells/base.rb +192 -0
  33. data/lib/winrm/shells/cmd.rb +59 -0
  34. data/lib/winrm/shells/power_shell.rb +202 -0
  35. data/lib/winrm/shells/retryable.rb +44 -0
  36. data/lib/winrm/shells/shell_factory.rb +56 -0
  37. data/lib/winrm/version.rb +5 -0
  38. data/lib/winrm/wsmv/base.rb +57 -0
  39. data/lib/winrm/wsmv/cleanup_command.rb +60 -0
  40. data/lib/winrm/wsmv/close_shell.rb +49 -0
  41. data/lib/winrm/wsmv/command.rb +100 -0
  42. data/lib/winrm/wsmv/command_output.rb +75 -0
  43. data/lib/winrm/wsmv/command_output_decoder.rb +54 -0
  44. data/lib/winrm/wsmv/configuration.rb +44 -0
  45. data/lib/winrm/wsmv/create_pipeline.rb +64 -0
  46. data/lib/winrm/wsmv/create_shell.rb +115 -0
  47. data/lib/winrm/wsmv/header.rb +213 -0
  48. data/lib/winrm/wsmv/init_runspace_pool.rb +96 -0
  49. data/lib/winrm/wsmv/iso8601_duration.rb +58 -0
  50. data/lib/winrm/wsmv/keep_alive.rb +66 -0
  51. data/lib/winrm/wsmv/receive_response_reader.rb +128 -0
  52. data/lib/winrm/wsmv/send_data.rb +66 -0
  53. data/lib/winrm/wsmv/soap.rb +49 -0
  54. data/lib/winrm/wsmv/wql_pull.rb +54 -0
  55. data/lib/winrm/wsmv/wql_query.rb +98 -0
  56. data/lib/winrm/wsmv/write_stdin.rb +86 -0
  57. data/lib/winrm.rb +37 -0
  58. metadata +333 -0
@@ -0,0 +1,68 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ module PSRP
17
+ module MessageData
18
+ # error record message type
19
+ class ErrorRecord < Base
20
+ def exception
21
+ @exception ||= property_hash('Exception')
22
+ end
23
+
24
+ def fully_qualified_error_id
25
+ @fully_qualified_error_id ||= string_prop('FullyQualifiedErrorId')
26
+ end
27
+
28
+ def invocation_info
29
+ @invocation_info ||= property_hash('InvocationInfo')
30
+ end
31
+
32
+ def error_category_message
33
+ @error_category_message ||= string_prop('ErrorCategory_Message')
34
+ end
35
+
36
+ def error_details_script_stack_trace
37
+ @error_details_script_stack_trace ||= string_prop('ErrorDetails_ScriptStackTrace')
38
+ end
39
+
40
+ def doc
41
+ @doc ||= REXML::Document.new(raw)
42
+ end
43
+
44
+ def string_prop(prop_name)
45
+ prop = REXML::XPath.first(doc, "//*[@N='#{prop_name}']")
46
+ prop.text if prop
47
+ end
48
+
49
+ def property_hash(prop_name)
50
+ prop_nodes = REXML::XPath.first(doc, "//*[@N='#{prop_name}']/Props")
51
+ return {} if prop_nodes.nil?
52
+
53
+ prop_nodes.elements.each_with_object({}) do |node, props|
54
+ name = node.attributes['N']
55
+ props[underscore(name).to_sym] = node.text if node.text
56
+ end
57
+ end
58
+
59
+ def underscore(camel)
60
+ camel.gsub(/::/, '/')
61
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
62
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
63
+ .tr('-', '_').downcase
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ module PSRP
17
+ module MessageData
18
+ # pipeline host call message type
19
+ class PipelineHostCall < Base
20
+ def method_identifier
21
+ clixml[:obj][0][:to_string]
22
+ end
23
+
24
+ def method_parameters
25
+ clixml[:obj][1][:lst]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rexml/document' unless defined?(REXML::Document)
16
+
17
+ module WinRM
18
+ module PSRP
19
+ module MessageData
20
+ # Handles decoding a raw powershell output response
21
+ class PipelineOutput < Base
22
+ def output
23
+ extract_out_string(remove_bom(raw.force_encoding('utf-8')))
24
+ end
25
+
26
+ private
27
+
28
+ def extract_out_string(text)
29
+ doc = REXML::Document.new(text)
30
+ doc.root.get_elements('//S').map do |node|
31
+ text = ''
32
+ if node.text
33
+ text << node.text.gsub(/(_x\h\h\h\h_)+/) do |match|
34
+ match.scan(/_x(\h\h\h\h)_/).flatten.map(&:hex)
35
+ .pack('S*').force_encoding('utf-16le').encode('utf-8')
36
+ end.chomp
37
+ end
38
+ text << "\r\n"
39
+ end.join
40
+ end
41
+
42
+ def remove_bom(text)
43
+ text.sub("\xEF\xBB\xBF", '')
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ module PSRP
17
+ module MessageData
18
+ # pipeline state message type
19
+ class PipelineState < Base
20
+ NOT_STARTED = 0
21
+ RUNNING = 1
22
+ STOPPING = 2
23
+ STOPPED = 3
24
+ COMPLETED = 4
25
+ FAILED = 5
26
+ DISCONNECTED = 6
27
+
28
+ def pipeline_state
29
+ clixml[:i32].to_i
30
+ end
31
+
32
+ def exception_as_error_record
33
+ @exception_as_error_record ||= ErrorRecord.new(raw) if pipeline_state == FAILED
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ module PSRP
17
+ module MessageData
18
+ # runspace pool host call message type
19
+ class RunspacepoolHostCall < Base
20
+ def method_identifier
21
+ clixml[:obj][0][:to_string]
22
+ end
23
+
24
+ def method_parameters
25
+ clixml[:obj][1][:lst]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ module PSRP
17
+ module MessageData
18
+ # runspace pool state message type
19
+ class RunspacepoolState < Base
20
+ BEFORE_OPEN = 0
21
+ OPENING = 1
22
+ OPENED = 2
23
+ CLOSED = 3
24
+ CLOSING = 4
25
+ BROKEN = 5
26
+ NEGOTIATION_SENT = 6
27
+ NEGOTIATION_SUCCEEDED = 7
28
+ CONNECTING = 8
29
+ DISCONNECTED = 9
30
+
31
+ def runspace_state
32
+ clixml[:i32].to_i
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ module PSRP
17
+ module MessageData
18
+ # session capability message type
19
+ class SessionCapability < Base
20
+ def protocol_version
21
+ clixml[:version].select { |v| v.attributes['N'] == 'protocolversion' }.first
22
+ end
23
+
24
+ def ps_version
25
+ clixml[:version].select { |v| v.attributes['N'] == 'PSVersion' }.first
26
+ end
27
+
28
+ def serialization_version
29
+ clixml[:version].select { |v| v.attributes['N'] == 'SerializationVersion' }.first
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'message_data/base'
16
+ require_relative 'message_data/error_record'
17
+ require_relative 'message_data/pipeline_output'
18
+ require_relative 'message_data/pipeline_host_call'
19
+ require_relative 'message_data/pipeline_state'
20
+ require_relative 'message_data/runspacepool_host_call'
21
+ require_relative 'message_data/runspacepool_state'
22
+ require_relative 'message_data/session_capability'
23
+
24
+ module WinRM
25
+ # PowerShell Remoting Protcol module
26
+ module PSRP
27
+ # PowerShell Remoting Protocol message data.
28
+ module MessageData
29
+ def self.parse(message)
30
+ type_key = WinRM::PSRP::Message::MESSAGE_TYPES.key(message.type)
31
+ type = camelize(type_key.to_s).to_sym
32
+ const_get(type).new(message.data) if MessageData.constants.include?(type)
33
+ end
34
+
35
+ def self.camelize(underscore)
36
+ underscore.split('_').collect(&:capitalize).join
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'fragment'
16
+
17
+ module WinRM
18
+ # PowerShell Remoting Protcol module
19
+ module PSRP
20
+ # PowerShell Remoting Protocol message fragmenter.
21
+ class MessageDefragmenter
22
+ def initialize
23
+ @messages = {}
24
+ end
25
+
26
+ def defragment(base64_bytes)
27
+ fragment = fragment_from(Base64.decode64(base64_bytes))
28
+
29
+ @messages[fragment.object_id] ||= []
30
+ @messages[fragment.object_id].push fragment
31
+
32
+ # rubocop:disable Style/GuardClause
33
+ if fragment.end_fragment
34
+ blob = []
35
+ @messages.delete(fragment.object_id).each { |frag| blob += frag.blob }
36
+ return message_from(blob.pack('C*'))
37
+ end
38
+ # rubocop:enable Style/GuardClause
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,86 @@
1
+ # Copyright 2016 Shawn Neal <sneal@sneal.net>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'erubi'
16
+ require_relative 'message'
17
+
18
+ module WinRM
19
+ module PSRP
20
+ # Creates WinRM::PSRP::Message instances for various PSRP messages
21
+ class MessageFactory
22
+ class << self
23
+ # Creates a new session capability PSRP message.
24
+ # @param runspace_pool_id [String] The UUID of the remote shell/runspace pool.
25
+ def session_capability_message(runspace_pool_id)
26
+ Message.new(
27
+ runspace_pool_id,
28
+ Message::MESSAGE_TYPES[:session_capability],
29
+ render('session_capability')
30
+ )
31
+ end
32
+
33
+ # Creates a new init runspace pool PSRP message.
34
+ # @param runspace_pool_id [String] The UUID of the remote shell/runspace pool.
35
+ def init_runspace_pool_message(runspace_pool_id)
36
+ Message.new(
37
+ runspace_pool_id,
38
+ Message::MESSAGE_TYPES[:init_runspacepool],
39
+ render('init_runspace_pool')
40
+ )
41
+ end
42
+
43
+ # Creates a new PSRP message that creates pipline to execute a command.
44
+ # @param runspace_pool_id [String] The UUID of the remote shell/runspace pool.
45
+ # @param pipeline_id [String] The UUID to correlate the command/pipeline
46
+ # response.
47
+ # @param command [String] The command passed to Invoke-Expression.
48
+ def create_pipeline_message(runspace_pool_id, pipeline_id, command)
49
+ Message.new(
50
+ runspace_pool_id,
51
+ Message::MESSAGE_TYPES[:create_pipeline],
52
+ render('create_pipeline', command: command.encode(xml: :text)),
53
+ pipeline_id
54
+ )
55
+ end
56
+
57
+ private
58
+
59
+ # Renders the specified template with the given context
60
+ # @param template [String] The base filename of the PSRP message template.
61
+ # @param context [Hash] Any options required for rendering the template.
62
+ # @return [String] The rendered XML PSRP message.
63
+ # @api private
64
+ def render(template, context = nil)
65
+ template_path = File.expand_path(
66
+ "#{File.dirname(__FILE__)}/#{template}.xml.erb"
67
+ )
68
+ template = File.read(template_path)
69
+ case context
70
+ when Hash
71
+ b = binding
72
+ locals = context.collect { |k, _| "#{k} = context[#{k.inspect}]; " }
73
+ b.eval(locals.join)
74
+ when Binding
75
+ b = context
76
+ when NilClass
77
+ b = binding
78
+ else
79
+ raise ArgumentError
80
+ end
81
+ b.eval(Erubi::Engine.new(template).src)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'fragment'
16
+
17
+ module WinRM
18
+ # PowerShell Remoting Protcol module
19
+ module PSRP
20
+ # PowerShell Remoting Protocol message fragmenter.
21
+ class MessageFragmenter
22
+ DEFAULT_BLOB_LENGTH = 32_768
23
+
24
+ def initialize(max_blob_length = DEFAULT_BLOB_LENGTH)
25
+ @object_id = 0
26
+ @max_blob_length = max_blob_length || DEFAULT_BLOB_LENGTH
27
+ end
28
+
29
+ attr_reader :object_id
30
+ attr_accessor :max_blob_length
31
+
32
+ def fragment(message)
33
+ @object_id += 1
34
+ message_bytes = message.bytes
35
+ bytes_fragmented = 0
36
+ fragment_id = 0
37
+ fragment = nil
38
+
39
+ while bytes_fragmented < message_bytes.length
40
+ last_byte = bytes_fragmented + max_blob_length
41
+ last_byte = message_bytes.length if last_byte > message_bytes.length
42
+ fragment = Fragment.new(
43
+ object_id,
44
+ message.bytes[bytes_fragmented..last_byte - 1],
45
+ fragment_id,
46
+ bytes_fragmented.zero?,
47
+ last_byte == message_bytes.length
48
+ )
49
+ fragment_id += 1
50
+ bytes_fragmented = last_byte
51
+ yield fragment if block_given?
52
+ end
53
+
54
+ fragment
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,142 @@
1
+ # Copyright 2016 Matt Wrock <matt@mattwrock.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'base64' unless defined?(Base64)
16
+ require_relative 'message'
17
+ require_relative 'message_data/pipeline_state'
18
+
19
+ module WinRM
20
+ module PSRP
21
+ # Handles decoding a raw powershell output response
22
+ class PowershellOutputDecoder
23
+ # rubocop:disable Metrics/CyclomaticComplexity
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
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_state]
39
+ if message.parsed_data.pipeline_state == WinRM::PSRP::MessageData::PipelineState::FAILED
40
+ decode_error_record(message)
41
+ end
42
+ end
43
+ end
44
+ # rubocop:enable Metrics/CyclomaticComplexity
45
+
46
+ protected
47
+
48
+ def decode_pipeline_output(message)
49
+ message.parsed_data.output
50
+ end
51
+
52
+ def decode_host_call(message)
53
+ text = begin
54
+ case message.parsed_data.method_identifier
55
+ when /WriteLine/, 'WriteErrorLine'
56
+ "#{message.parsed_data.method_parameters[:s]}\r\n"
57
+ when 'WriteDebugLine'
58
+ "Debug: #{message.parsed_data.method_parameters[:s]}\r\n"
59
+ when 'WriteWarningLine'
60
+ "Warning: #{message.parsed_data.method_parameters[:s]}\r\n"
61
+ when 'WriteVerboseLine'
62
+ "Verbose: #{message.parsed_data.method_parameters[:s]}\r\n"
63
+ when /Write[1-2]/
64
+ message.parsed_data.method_parameters[:s]
65
+ end
66
+ end
67
+
68
+ hex_decode(text)
69
+ end
70
+
71
+ def decode_error_record(message)
72
+ parsed = message.parsed_data
73
+ text = begin
74
+ if message.type == WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_state]
75
+ render_exception_as_error_record(parsed.exception_as_error_record)
76
+ else
77
+ case parsed.fully_qualified_error_id
78
+ when 'Microsoft.PowerShell.Commands.WriteErrorException'
79
+ render_write_error_exception(parsed)
80
+ when 'NativeCommandError'
81
+ render_native_command_error(parsed)
82
+ when 'NativeCommandErrorMessage'
83
+ parsed.exception[:message]
84
+ else
85
+ render_exception(parsed)
86
+ end
87
+ end
88
+ end
89
+
90
+ hex_decode(text)
91
+ end
92
+
93
+ def render_write_error_exception(parsed)
94
+ <<EOH
95
+ #{parsed.invocation_info[:line]} : #{parsed.exception[:message]}
96
+ + CategoryInfo : #{parsed.error_category_message}
97
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
98
+ EOH
99
+ end
100
+
101
+ def render_exception(parsed)
102
+ <<EOH
103
+ #{parsed.exception[:message]}
104
+ #{parsed.invocation_info[:position_message]}
105
+ + CategoryInfo : #{parsed.error_category_message}
106
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
107
+ EOH
108
+ end
109
+
110
+ def render_native_command_error(parsed)
111
+ <<EOH
112
+ #{parsed.invocation_info[:my_command]} : #{parsed.exception[:message]}
113
+ + CategoryInfo : #{parsed.error_category_message}
114
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
115
+ EOH
116
+ end
117
+
118
+ def render_exception_as_error_record(parsed)
119
+ <<EOH
120
+ #{parsed.exception[:message]}
121
+ + CategoryInfo : #{parsed.error_category_message}
122
+ + FullyQualifiedErrorId : #{parsed.fully_qualified_error_id}
123
+ EOH
124
+ end
125
+
126
+ private
127
+
128
+ def hex_decode(text)
129
+ return unless text
130
+
131
+ text.gsub(/_x(\h\h\h\h)_/) do
132
+ decoded_text = Regexp.last_match[1].hex.chr.force_encoding('utf-8')
133
+ if decoded_text.respond_to?(:scrub)
134
+ decoded_text.scrub
135
+ else
136
+ decoded_text.encode('utf-16', invalid: :replace, undef: :replace).encode('utf-8')
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end