winrm 2.0.3 → 2.1.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -10
  3. data/.rubocop.yml +26 -26
  4. data/.travis.yml +11 -11
  5. data/Gemfile +3 -3
  6. data/README.md +260 -233
  7. data/Rakefile +34 -34
  8. data/Vagrantfile +6 -6
  9. data/WinrmAppveyor.psm1 +31 -31
  10. data/appveyor.yml +51 -51
  11. data/changelog.md +104 -101
  12. data/lib/winrm.rb +39 -39
  13. data/lib/winrm/connection.rb +83 -82
  14. data/lib/winrm/connection_opts.rb +91 -91
  15. data/lib/winrm/exceptions.rb +76 -76
  16. data/lib/winrm/http/response_handler.rb +96 -96
  17. data/lib/winrm/http/transport.rb +424 -424
  18. data/lib/winrm/http/transport_factory.rb +68 -68
  19. data/lib/winrm/output.rb +59 -59
  20. data/lib/winrm/psrp/create_pipeline.xml.erb +167 -167
  21. data/lib/winrm/psrp/fragment.rb +70 -70
  22. data/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -224
  23. data/lib/winrm/psrp/message.rb +130 -130
  24. data/lib/winrm/psrp/message_data.rb +42 -42
  25. data/lib/winrm/psrp/message_data/base.rb +49 -49
  26. data/lib/winrm/psrp/message_data/error_record.rb +68 -68
  27. data/lib/winrm/psrp/message_data/pipeline_host_call.rb +32 -32
  28. data/lib/winrm/psrp/message_data/pipeline_output.rb +49 -49
  29. data/lib/winrm/psrp/message_data/pipeline_state.rb +40 -40
  30. data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +32 -32
  31. data/lib/winrm/psrp/message_data/runspacepool_state.rb +39 -39
  32. data/lib/winrm/psrp/message_data/session_capability.rb +36 -36
  33. data/lib/winrm/psrp/message_defragmenter.rb +62 -62
  34. data/lib/winrm/psrp/message_factory.rb +75 -75
  35. data/lib/winrm/psrp/message_fragmenter.rb +60 -60
  36. data/lib/winrm/psrp/powershell_output_decoder.rb +139 -139
  37. data/lib/winrm/psrp/receive_response_reader.rb +97 -97
  38. data/lib/winrm/psrp/session_capability.xml.erb +7 -7
  39. data/lib/winrm/psrp/uuid.rb +40 -40
  40. data/lib/winrm/shells/base.rb +180 -175
  41. data/lib/winrm/shells/cmd.rb +65 -65
  42. data/lib/winrm/shells/power_shell.rb +202 -202
  43. data/lib/winrm/shells/retryable.rb +45 -45
  44. data/lib/winrm/shells/shell_factory.rb +58 -50
  45. data/lib/winrm/version.rb +7 -7
  46. data/lib/winrm/wsmv/base.rb +59 -59
  47. data/lib/winrm/wsmv/cleanup_command.rb +61 -61
  48. data/lib/winrm/wsmv/close_shell.rb +50 -50
  49. data/lib/winrm/wsmv/command.rb +101 -101
  50. data/lib/winrm/wsmv/command_output.rb +76 -76
  51. data/lib/winrm/wsmv/command_output_decoder.rb +55 -55
  52. data/lib/winrm/wsmv/configuration.rb +46 -46
  53. data/lib/winrm/wsmv/create_pipeline.rb +66 -66
  54. data/lib/winrm/wsmv/create_shell.rb +119 -119
  55. data/lib/winrm/wsmv/header.rb +203 -203
  56. data/lib/winrm/wsmv/init_runspace_pool.rb +95 -95
  57. data/lib/winrm/wsmv/iso8601_duration.rb +60 -60
  58. data/lib/winrm/wsmv/keep_alive.rb +68 -68
  59. data/lib/winrm/wsmv/receive_response_reader.rb +126 -126
  60. data/lib/winrm/wsmv/send_data.rb +68 -68
  61. data/lib/winrm/wsmv/soap.rb +51 -51
  62. data/lib/winrm/wsmv/wql_query.rb +79 -79
  63. data/lib/winrm/wsmv/write_stdin.rb +88 -88
  64. data/tests/integration/auth_timeout_spec.rb +18 -18
  65. data/tests/integration/cmd_spec.rb +131 -110
  66. data/tests/integration/config-example.yml +16 -16
  67. data/tests/integration/issue_59_spec.rb +26 -26
  68. data/tests/integration/powershell_spec.rb +165 -165
  69. data/tests/integration/spec_helper.rb +65 -65
  70. data/tests/integration/transport_spec.rb +99 -99
  71. data/tests/integration/wql_spec.rb +16 -16
  72. data/tests/matchers.rb +60 -60
  73. data/tests/spec/configuration_spec.rb +184 -184
  74. data/tests/spec/connection_spec.rb +39 -39
  75. data/tests/spec/exception_spec.rb +50 -50
  76. data/tests/spec/http/transport_factory_spec.rb +68 -68
  77. data/tests/spec/http/transport_spec.rb +44 -44
  78. data/tests/spec/output_spec.rb +127 -127
  79. data/tests/spec/psrp/fragment_spec.rb +62 -62
  80. data/tests/spec/psrp/message_data/base_spec.rb +13 -13
  81. data/tests/spec/psrp/message_data/error_record_spec.rb +41 -41
  82. data/tests/spec/psrp/message_data/pipeline_host_call_spec.rb +25 -25
  83. data/tests/spec/psrp/message_data/pipeline_output_spec.rb +32 -32
  84. data/tests/spec/psrp/message_data/pipeline_state_spec.rb +40 -40
  85. data/tests/spec/psrp/message_data/runspace_pool_host_call_spec.rb +25 -25
  86. data/tests/spec/psrp/message_data/runspacepool_state_spec.rb +16 -16
  87. data/tests/spec/psrp/message_data/session_capability_spec.rb +30 -30
  88. data/tests/spec/psrp/message_data_spec.rb +35 -35
  89. data/tests/spec/psrp/message_defragmenter_spec.rb +47 -47
  90. data/tests/spec/psrp/message_fragmenter_spec.rb +105 -105
  91. data/tests/spec/psrp/powershell_output_decoder_spec.rb +100 -100
  92. data/tests/spec/psrp/psrp_message_spec.rb +70 -70
  93. data/tests/spec/psrp/recieve_response_reader_spec.rb +172 -172
  94. data/tests/spec/psrp/uuid_spec.rb +28 -28
  95. data/tests/spec/response_handler_spec.rb +61 -61
  96. data/tests/spec/shells/base_spec.rb +202 -202
  97. data/tests/spec/shells/cmd_spec.rb +75 -75
  98. data/tests/spec/shells/powershell_spec.rb +175 -175
  99. data/tests/spec/spec_helper.rb +47 -47
  100. data/tests/spec/stubs/clixml/error_record.xml.erb +84 -84
  101. data/tests/spec/stubs/clixml/pipeline_state.xml.erb +88 -88
  102. data/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 -10
  104. data/tests/spec/stubs/responses/get_powershell_keepalive_response.xml.erb +10 -10
  105. data/tests/spec/stubs/responses/get_powershell_output_response.xml.erb +12 -12
  106. data/tests/spec/stubs/responses/get_powershell_output_response_not_done.xml.erb +9 -9
  107. data/tests/spec/stubs/responses/open_shell_v1.xml +19 -19
  108. data/tests/spec/stubs/responses/open_shell_v2.xml +20 -20
  109. data/tests/spec/stubs/responses/soap_fault_v1.xml +36 -36
  110. data/tests/spec/stubs/responses/soap_fault_v2.xml +42 -42
  111. data/tests/spec/stubs/responses/wmi_error_v2.xml +41 -41
  112. data/tests/spec/wsmv/cleanup_command_spec.rb +22 -22
  113. data/tests/spec/wsmv/close_shell_spec.rb +17 -17
  114. data/tests/spec/wsmv/command_output_decoder_spec.rb +37 -37
  115. data/tests/spec/wsmv/command_output_spec.rb +45 -45
  116. data/tests/spec/wsmv/command_spec.rb +19 -19
  117. data/tests/spec/wsmv/configuration_spec.rb +17 -17
  118. data/tests/spec/wsmv/create_pipeline_spec.rb +31 -31
  119. data/tests/spec/wsmv/create_shell_spec.rb +38 -38
  120. data/tests/spec/wsmv/init_runspace_pool_spec.rb +36 -36
  121. data/tests/spec/wsmv/keep_alive_spec.rb +21 -21
  122. data/tests/spec/wsmv/receive_response_reader_spec.rb +123 -123
  123. data/tests/spec/wsmv/send_data_spec.rb +30 -30
  124. data/tests/spec/wsmv/wql_query_spec.rb +13 -13
  125. data/tests/spec/wsmv/write_stdin_spec.rb +22 -22
  126. data/winrm.gemspec +42 -42
  127. metadata +2 -2
@@ -1,97 +1,97 @@
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
- when WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_state]
82
- if message.parsed_data.pipeline_state == WinRM::PSRP::MessageData::PipelineState::FAILED
83
- type = :stderr
84
- end
85
- end
86
- type
87
- end
88
-
89
- def find_exit_code(message)
90
- parsed = message.parsed_data
91
- return nil unless parsed.is_a?(MessageData::PipelineHostCall)
92
-
93
- parsed.method_parameters[:i32].to_i if parsed.method_identifier == 'SetShouldExit'
94
- end
95
- end
96
- end
97
- end
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
+ when WinRM::PSRP::Message::MESSAGE_TYPES[:pipeline_state]
82
+ if message.parsed_data.pipeline_state == WinRM::PSRP::MessageData::PipelineState::FAILED
83
+ type = :stderr
84
+ end
85
+ end
86
+ type
87
+ end
88
+
89
+ def find_exit_code(message)
90
+ parsed = message.parsed_data
91
+ return nil unless parsed.is_a?(MessageData::PipelineHostCall)
92
+
93
+ parsed.method_parameters[:i32].to_i if parsed.method_identifier == 'SetShouldExit'
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,7 +1,7 @@
1
- <Obj RefId="0">
2
- <MS>
3
- <Version N="protocolversion">2.3</Version>
4
- <Version N="PSVersion">2.0</Version>
5
- <Version N="SerializationVersion">1.1.0.1</Version>
6
- </MS>
7
- </Obj>
1
+ <Obj RefId="0">
2
+ <MS>
3
+ <Version N="protocolversion">2.3</Version>
4
+ <Version N="PSVersion">2.0</Version>
5
+ <Version N="SerializationVersion">1.1.0.1</Version>
6
+ </MS>
7
+ </Obj>
@@ -1,40 +1,40 @@
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
- module WinRM
18
- module PSRP
19
- # UUID helper methods
20
- module UUID
21
- # Format a UUID into a GUID compatible byte array for Windows
22
- #
23
- # https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
24
- # typedef struct _GUID {
25
- # DWORD Data1;
26
- # WORD Data2;
27
- # WORD Data3;
28
- # BYTE Data4[8];
29
- # } GUID;
30
- #
31
- # @param uuid [String] Canonical hex format with hypens.
32
- # @return [Array<byte>] UUID in a Windows GUID compatible byte array layout.
33
- def uuid_to_windows_guid_bytes(uuid)
34
- return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] unless uuid
35
- b = uuid.scan(/[0-9a-fA-F]{2}/).map { |x| x.to_i(16) }
36
- b[0..3].reverse + b[4..5].reverse + b[6..7].reverse + b[8..15]
37
- end
38
- end
39
- end
40
- end
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
+ module WinRM
18
+ module PSRP
19
+ # UUID helper methods
20
+ module UUID
21
+ # Format a UUID into a GUID compatible byte array for Windows
22
+ #
23
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
24
+ # typedef struct _GUID {
25
+ # DWORD Data1;
26
+ # WORD Data2;
27
+ # WORD Data3;
28
+ # BYTE Data4[8];
29
+ # } GUID;
30
+ #
31
+ # @param uuid [String] Canonical hex format with hypens.
32
+ # @return [Array<byte>] UUID in a Windows GUID compatible byte array layout.
33
+ def uuid_to_windows_guid_bytes(uuid)
34
+ return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] unless uuid
35
+ b = uuid.scan(/[0-9a-fA-F]{2}/).map { |x| x.to_i(16) }
36
+ b[0..3].reverse + b[4..5].reverse + b[6..7].reverse + b[8..15]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,175 +1,180 @@
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_relative 'retryable'
18
- require_relative '../http/transport'
19
- require_relative '../wsmv/cleanup_command'
20
- require_relative '../wsmv/close_shell'
21
- require_relative '../wsmv/command'
22
- require_relative '../wsmv/command_output'
23
- require_relative '../wsmv/receive_response_reader'
24
- require_relative '../wsmv/create_shell'
25
- require_relative '../wsmv/soap'
26
-
27
- module WinRM
28
- module Shells
29
- # Base class for remote shell
30
- class Base
31
- TOO_MANY_COMMANDS = '2150859174'.freeze
32
- ERROR_OPERATION_ABORTED = '995'.freeze
33
-
34
- FAULTS_FOR_RESET = [
35
- '2150858843', # Shell has been closed
36
- '2147943418', # Error reading registry key
37
- TOO_MANY_COMMANDS, # Maximum commands per user exceeded
38
- ].freeze
39
-
40
- include Retryable
41
-
42
- # Create a new Cmd shell
43
- # @param connection_opts [ConnectionOpts] The WinRM connection options
44
- # @param transport [HttpTransport] The WinRM SOAP transport
45
- # @param logger [Logger] The logger to log diagnostic messages to
46
- def initialize(connection_opts, transport, logger)
47
- @connection_opts = connection_opts
48
- @transport = transport
49
- @logger = logger
50
- end
51
-
52
- # @return [String] shell id of the currently opn shell or nil if shell is closed
53
- attr_reader :shell_id
54
-
55
- # @return [String] uri that SOAP calls use to identify shell type
56
- attr_reader :shell_uri
57
-
58
- # @return [ConnectionOpts] connection options of the shell
59
- attr_reader :connection_opts
60
-
61
- # @return [WinRM::HTTP::HttpTransport] transport used to talk with endpoint
62
- attr_reader :transport
63
-
64
- # @return [Logger] logger used for diagnostic messages
65
- attr_reader :logger
66
-
67
- # Runs the specified command with optional arguments
68
- # @param command [String] The command or executable to run
69
- # @param arguments [Array] The optional command arguments
70
- # @param block [&block] The optional callback for any realtime output
71
- # @yieldparam [string] standard out response text
72
- # @yieldparam [string] standard error response text
73
- # @yieldreturn [WinRM::Output] The command output
74
- def run(command, arguments = [], &block)
75
- with_command_shell(command, arguments) do |shell, cmd|
76
- response_reader.read_output(command_output_message(shell, cmd), &block)
77
- end
78
- end
79
-
80
- # Closes the shell if one is open
81
- def close
82
- return unless shell_id
83
- self.class.close_shell(connection_opts, transport, shell_id)
84
- remove_finalizer
85
- @shell_id = nil
86
- end
87
-
88
- protected
89
-
90
- def send_command(_command, _arguments)
91
- raise NotImplementedError
92
- end
93
-
94
- def response_reader
95
- raise NotImplementedError
96
- end
97
-
98
- def open_shell
99
- raise NotImplementedError
100
- end
101
-
102
- def out_streams
103
- raise NotImplementedError
104
- end
105
-
106
- def command_output_message(shell_id, command_id)
107
- cmd_out_opts = {
108
- shell_id: shell_id,
109
- command_id: command_id,
110
- shell_uri: shell_uri,
111
- out_streams: out_streams
112
- }
113
- WinRM::WSMV::CommandOutput.new(connection_opts, cmd_out_opts)
114
- end
115
-
116
- def with_command_shell(command, arguments = [])
117
- tries ||= 2
118
-
119
- open unless shell_id
120
- command_id = send_command(command, arguments)
121
- logger.debug("[WinRM] creating command_id: #{command_id} on shell_id #{shell_id}")
122
- yield shell_id, command_id
123
- rescue WinRMWSManFault => e
124
- raise unless FAULTS_FOR_RESET.include?(e.fault_code) && (tries -= 1) > 0
125
- reset_on_error(e)
126
- retry
127
- ensure
128
- cleanup_command(command_id) if command_id
129
- end
130
-
131
- private
132
-
133
- def reset_on_error(error)
134
- close if error.fault_code == TOO_MANY_COMMANDS
135
- logger.debug('[WinRM] opening new shell since the current one was deleted')
136
- @shell_id = nil
137
- end
138
-
139
- def cleanup_command(command_id)
140
- logger.debug("[WinRM] cleaning up command_id: #{command_id} on shell_id #{shell_id}")
141
- cleanup_msg = WinRM::WSMV::CleanupCommand.new(
142
- connection_opts,
143
- shell_uri: shell_uri,
144
- shell_id: shell_id,
145
- command_id: command_id)
146
- transport.send_request(cleanup_msg.build)
147
- rescue WinRMWSManFault => e
148
- raise unless e.fault_code == ERROR_OPERATION_ABORTED
149
- rescue WinRMHTTPTransportError => t
150
- # dont let the cleanup raise so we dont lose any errors from the command
151
- logger.info("[WinRM] #{t.status_code} returned in cleanup with error: #{t.message}")
152
- end
153
-
154
- def open
155
- close
156
- retryable(connection_opts[:retry_limit], connection_opts[:retry_delay]) do
157
- logger.debug("[WinRM] opening remote shell on #{connection_opts[:endpoint]}")
158
- @shell_id = open_shell
159
- end
160
- logger.debug("[WinRM] remote shell created with shell_id: #{shell_id}")
161
- add_finalizer
162
- end
163
-
164
- def add_finalizer
165
- ObjectSpace.define_finalizer(
166
- self,
167
- self.class.finalize(connection_opts, transport, shell_id))
168
- end
169
-
170
- def remove_finalizer
171
- ObjectSpace.undefine_finalizer(self)
172
- end
173
- end
174
- end
175
- end
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_relative 'retryable'
18
+ require_relative '../http/transport'
19
+ require_relative '../wsmv/cleanup_command'
20
+ require_relative '../wsmv/close_shell'
21
+ require_relative '../wsmv/command'
22
+ require_relative '../wsmv/command_output'
23
+ require_relative '../wsmv/receive_response_reader'
24
+ require_relative '../wsmv/create_shell'
25
+ require_relative '../wsmv/soap'
26
+
27
+ module WinRM
28
+ module Shells
29
+ # Base class for remote shell
30
+ class Base
31
+ TOO_MANY_COMMANDS = '2150859174'.freeze
32
+ ERROR_OPERATION_ABORTED = '995'.freeze
33
+
34
+ FAULTS_FOR_RESET = [
35
+ '2150858843', # Shell has been closed
36
+ '2147943418', # Error reading registry key
37
+ TOO_MANY_COMMANDS, # Maximum commands per user exceeded
38
+ ].freeze
39
+
40
+ include Retryable
41
+
42
+ # Create a new Cmd shell
43
+ # @param connection_opts [ConnectionOpts] The WinRM connection options
44
+ # @param transport [HttpTransport] The WinRM SOAP transport
45
+ # @param logger [Logger] The logger to log diagnostic messages to
46
+ # @param shell_opts [Hash] Options targeted for the created shell
47
+ def initialize(connection_opts, transport, logger, shell_opts = {})
48
+ @connection_opts = connection_opts
49
+ @transport = transport
50
+ @logger = logger
51
+ @shell_opts = shell_opts
52
+ end
53
+
54
+ # @return [String] shell id of the currently opn shell or nil if shell is closed
55
+ attr_reader :shell_id
56
+
57
+ # @return [String] uri that SOAP calls use to identify shell type
58
+ attr_reader :shell_uri
59
+
60
+ # @return [ConnectionOpts] connection options of the shell
61
+ attr_reader :connection_opts
62
+
63
+ # @return [WinRM::HTTP::HttpTransport] transport used to talk with endpoint
64
+ attr_reader :transport
65
+
66
+ # @return [Logger] logger used for diagnostic messages
67
+ attr_reader :logger
68
+
69
+ # @return [Hash] Options targeted for the created shell
70
+ attr_reader :shell_opts
71
+
72
+ # Runs the specified command with optional arguments
73
+ # @param command [String] The command or executable to run
74
+ # @param arguments [Array] The optional command arguments
75
+ # @param block [&block] The optional callback for any realtime output
76
+ # @yieldparam [string] standard out response text
77
+ # @yieldparam [string] standard error response text
78
+ # @yieldreturn [WinRM::Output] The command output
79
+ def run(command, arguments = [], &block)
80
+ with_command_shell(command, arguments) do |shell, cmd|
81
+ response_reader.read_output(command_output_message(shell, cmd), &block)
82
+ end
83
+ end
84
+
85
+ # Closes the shell if one is open
86
+ def close
87
+ return unless shell_id
88
+ self.class.close_shell(connection_opts, transport, shell_id)
89
+ remove_finalizer
90
+ @shell_id = nil
91
+ end
92
+
93
+ protected
94
+
95
+ def send_command(_command, _arguments)
96
+ raise NotImplementedError
97
+ end
98
+
99
+ def response_reader
100
+ raise NotImplementedError
101
+ end
102
+
103
+ def open_shell
104
+ raise NotImplementedError
105
+ end
106
+
107
+ def out_streams
108
+ raise NotImplementedError
109
+ end
110
+
111
+ def command_output_message(shell_id, command_id)
112
+ cmd_out_opts = {
113
+ shell_id: shell_id,
114
+ command_id: command_id,
115
+ shell_uri: shell_uri,
116
+ out_streams: out_streams
117
+ }
118
+ WinRM::WSMV::CommandOutput.new(connection_opts, cmd_out_opts)
119
+ end
120
+
121
+ def with_command_shell(command, arguments = [])
122
+ tries ||= 2
123
+
124
+ open unless shell_id
125
+ command_id = send_command(command, arguments)
126
+ logger.debug("[WinRM] creating command_id: #{command_id} on shell_id #{shell_id}")
127
+ yield shell_id, command_id
128
+ rescue WinRMWSManFault => e
129
+ raise unless FAULTS_FOR_RESET.include?(e.fault_code) && (tries -= 1) > 0
130
+ reset_on_error(e)
131
+ retry
132
+ ensure
133
+ cleanup_command(command_id) if command_id
134
+ end
135
+
136
+ private
137
+
138
+ def reset_on_error(error)
139
+ close if error.fault_code == TOO_MANY_COMMANDS
140
+ logger.debug('[WinRM] opening new shell since the current one was deleted')
141
+ @shell_id = nil
142
+ end
143
+
144
+ def cleanup_command(command_id)
145
+ logger.debug("[WinRM] cleaning up command_id: #{command_id} on shell_id #{shell_id}")
146
+ cleanup_msg = WinRM::WSMV::CleanupCommand.new(
147
+ connection_opts,
148
+ shell_uri: shell_uri,
149
+ shell_id: shell_id,
150
+ command_id: command_id)
151
+ transport.send_request(cleanup_msg.build)
152
+ rescue WinRMWSManFault => e
153
+ raise unless e.fault_code == ERROR_OPERATION_ABORTED
154
+ rescue WinRMHTTPTransportError => t
155
+ # dont let the cleanup raise so we dont lose any errors from the command
156
+ logger.info("[WinRM] #{t.status_code} returned in cleanup with error: #{t.message}")
157
+ end
158
+
159
+ def open
160
+ close
161
+ retryable(connection_opts[:retry_limit], connection_opts[:retry_delay]) do
162
+ logger.debug("[WinRM] opening remote shell on #{connection_opts[:endpoint]}")
163
+ @shell_id = open_shell
164
+ end
165
+ logger.debug("[WinRM] remote shell created with shell_id: #{shell_id}")
166
+ add_finalizer
167
+ end
168
+
169
+ def add_finalizer
170
+ ObjectSpace.define_finalizer(
171
+ self,
172
+ self.class.finalize(connection_opts, transport, shell_id))
173
+ end
174
+
175
+ def remove_finalizer
176
+ ObjectSpace.undefine_finalizer(self)
177
+ end
178
+ end
179
+ end
180
+ end