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,65 +1,65 @@
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 'base'
18
-
19
- module WinRM
20
- module Shells
21
- # Proxy to a remote cmd.exe shell
22
- class Cmd < Base
23
- include WinRM::WSMV::SOAP
24
- class << self
25
- def finalize(connection_opts, transport, shell_id)
26
- proc { Cmd.close_shell(connection_opts, transport, shell_id) }
27
- end
28
-
29
- def close_shell(connection_opts, transport, shell_id)
30
- msg = WinRM::WSMV::CloseShell.new(connection_opts, shell_id: shell_id)
31
- transport.send_request(msg.build)
32
- end
33
- end
34
-
35
- protected
36
-
37
- def send_command(command, arguments)
38
- cmd_msg = WinRM::WSMV::Command.new(
39
- connection_opts,
40
- shell_id: shell_id,
41
- command: command,
42
- arguments: arguments
43
- )
44
- resp_doc = transport.send_request(cmd_msg.build)
45
- command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
46
- logger.debug("[WinRM] Command created for #{command} with id: #{command_id}")
47
- command_id
48
- end
49
-
50
- def response_reader
51
- @response_reader ||= WinRM::WSMV::ReceiveResponseReader.new(transport, logger)
52
- end
53
-
54
- def open_shell
55
- msg = WinRM::WSMV::CreateShell.new(connection_opts)
56
- resp_doc = transport.send_request(msg.build)
57
- REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
58
- end
59
-
60
- def out_streams
61
- %w(stdout stderr)
62
- end
63
- end
64
- end
65
- 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 'base'
18
+
19
+ module WinRM
20
+ module Shells
21
+ # Proxy to a remote cmd.exe shell
22
+ class Cmd < Base
23
+ include WinRM::WSMV::SOAP
24
+ class << self
25
+ def finalize(connection_opts, transport, shell_id)
26
+ proc { Cmd.close_shell(connection_opts, transport, shell_id) }
27
+ end
28
+
29
+ def close_shell(connection_opts, transport, shell_id)
30
+ msg = WinRM::WSMV::CloseShell.new(connection_opts, shell_id: shell_id)
31
+ transport.send_request(msg.build)
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def send_command(command, arguments)
38
+ cmd_msg = WinRM::WSMV::Command.new(
39
+ connection_opts,
40
+ shell_id: shell_id,
41
+ command: command,
42
+ arguments: arguments
43
+ )
44
+ resp_doc = transport.send_request(cmd_msg.build)
45
+ command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
46
+ logger.debug("[WinRM] Command created for #{command} with id: #{command_id}")
47
+ command_id
48
+ end
49
+
50
+ def response_reader
51
+ @response_reader ||= WinRM::WSMV::ReceiveResponseReader.new(transport, logger)
52
+ end
53
+
54
+ def open_shell
55
+ msg = WinRM::WSMV::CreateShell.new(connection_opts, shell_opts)
56
+ resp_doc = transport.send_request(msg.build)
57
+ REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
58
+ end
59
+
60
+ def out_streams
61
+ %w(stdout stderr)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,202 +1,202 @@
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 'securerandom'
18
- require_relative 'base'
19
- require_relative '../psrp/message_fragmenter'
20
- require_relative '../psrp/receive_response_reader'
21
- require_relative '../wsmv/configuration'
22
- require_relative '../wsmv/create_pipeline'
23
- require_relative '../wsmv/send_data'
24
- require_relative '../wsmv/init_runspace_pool'
25
- require_relative '../wsmv/keep_alive'
26
-
27
- module WinRM
28
- module Shells
29
- # Proxy to a remote PowerShell instance
30
- class Powershell < Base
31
- include WinRM::WSMV::SOAP
32
-
33
- class << self
34
- def finalize(connection_opts, transport, shell_id)
35
- proc { Powershell.close_shell(connection_opts, transport, shell_id) }
36
- end
37
-
38
- def close_shell(connection_opts, transport, shell_id)
39
- msg = WinRM::WSMV::CloseShell.new(
40
- connection_opts,
41
- shell_id: shell_id,
42
- shell_uri: WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL
43
- )
44
- transport.send_request(msg.build)
45
- end
46
- end
47
-
48
- # Create a new powershell shell
49
- # @param connection_opts [ConnectionOpts] The WinRM connection options
50
- # @param transport [HttpTransport] The WinRM SOAP transport
51
- # @param logger [Logger] The logger to log diagnostic messages to
52
- def initialize(connection_opts, transport, logger)
53
- super
54
- @shell_uri = WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL
55
- end
56
-
57
- # Runs the specified command
58
- # @param command [String] The powershell script to run
59
- # @param block [&block] The optional callback for any realtime output
60
- # @yield [Message] PSRP Message in response
61
- # @yieldreturn [Array<Message>] All messages in response
62
- def send_pipeline_command(command, &block)
63
- with_command_shell(command) do |shell, cmd|
64
- response_reader.read_message(command_output_message(shell, cmd), true, &block)
65
- end
66
- end
67
-
68
- # calculate the maimum fragment size so that they will be as large as possible yet
69
- # no greater than the max_envelope_size_kb on the end point. To calculate this
70
- # threshold, we:
71
- # - determine the maximum number of bytes accepted on the endpoint
72
- # - subtract the non-fragment characters in the SOAP envelope
73
- # - determine the number of bytes that could be base64 encded to the above length
74
- # - subtract the fragment header bytes (ids, length, etc)
75
- def max_fragment_blob_size
76
- @max_fragment_blob_size ||= begin
77
- fragment_header_length = 21
78
-
79
- begin
80
- max_fragment_bytes = (max_envelope_size_kb * 1024) - empty_pipeline_envelope.length
81
- base64_deflated(max_fragment_bytes) - fragment_header_length
82
- rescue WinRMWSManFault => e
83
- # A non administrator user will encounter an access denied
84
- # error attempting to query winrm configuration.
85
- # we will assin a small default and adjust to a protocol
86
- # appropriate max length when that info is available
87
- raise unless e.fault_code == '5'
88
- WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH
89
- end
90
- end
91
- end
92
-
93
- protected
94
-
95
- def response_reader
96
- @response_reader ||= WinRM::PSRP::ReceiveResponseReader.new(transport, logger)
97
- end
98
-
99
- def send_command(command, _arguments)
100
- command_id = SecureRandom.uuid.to_s.upcase
101
- command += "\r\nif (!$?) { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
102
- message = PSRP::MessageFactory.create_pipeline_message(@runspace_id, command_id, command)
103
- fragmenter.fragment(message) do |fragment|
104
- command_args = [connection_opts, shell_id, command_id, fragment]
105
- if fragment.start_fragment
106
- resp_doc = transport.send_request(WinRM::WSMV::CreatePipeline.new(*command_args).build)
107
- command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
108
- else
109
- transport.send_request(WinRM::WSMV::SendData.new(*command_args).build)
110
- end
111
- end
112
-
113
- logger.debug("[WinRM] Command created for #{command} with id: #{command_id}")
114
- command_id
115
- end
116
-
117
- def open_shell
118
- @runspace_id = SecureRandom.uuid.to_s.upcase
119
- runspace_msg = WinRM::WSMV::InitRunspacePool.new(
120
- connection_opts,
121
- @runspace_id,
122
- open_shell_payload(@runspace_id)
123
- )
124
- resp_doc = transport.send_request(runspace_msg.build)
125
- shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
126
- wait_for_running(shell_id)
127
- shell_id
128
- end
129
-
130
- def out_streams
131
- %w(stdout)
132
- end
133
-
134
- private
135
-
136
- def base64_deflated(inflated_length)
137
- inflated_length / 4 * 3
138
- end
139
-
140
- def empty_pipeline_envelope
141
- WinRM::WSMV::CreatePipeline.new(
142
- connection_opts,
143
- '00000000-0000-0000-0000-000000000000',
144
- '00000000-0000-0000-0000-000000000000'
145
- ).build
146
- end
147
-
148
- def max_envelope_size_kb
149
- @max_envelope_size_kb ||= begin
150
- config_msg = WinRM::WSMV::Configuration.new(connection_opts)
151
- msg = config_msg.build
152
- resp_doc = transport.send_request(msg)
153
- REXML::XPath.first(resp_doc, "//#{NS_WSMAN_CONF}:MaxEnvelopeSizekb").text.to_i
154
- end
155
- end
156
-
157
- def open_shell_payload(shell_id)
158
- [
159
- WinRM::PSRP::MessageFactory.session_capability_message(shell_id),
160
- WinRM::PSRP::MessageFactory.init_runspace_pool_message(shell_id)
161
- ].map do |message|
162
- fragmenter.fragment(message).bytes
163
- end.flatten
164
- end
165
-
166
- def wait_for_running(shell_id)
167
- state = WinRM::PSRP::MessageData::RunspacepoolState::OPENING
168
- keepalive_msg = WinRM::WSMV::KeepAlive.new(connection_opts, shell_id)
169
-
170
- # 2 is "openned". if we start issuing commands while in "openning" the runspace
171
- # seems to hang
172
- until state == WinRM::PSRP::MessageData::RunspacepoolState::OPENED
173
- response_reader.read_message(keepalive_msg) do |message|
174
- logger.debug("[WinRM] polling for pipeline state. message: #{message.inspect}")
175
- parsed = message.parsed_data
176
- case parsed
177
- when WinRM::PSRP::MessageData::RunspacepoolState
178
- state = parsed.runspace_state
179
- when WinRM::PSRP::MessageData::SessionCapability
180
- # if the user lacks admin privileges, we cannot query the MaxEnvelopeSizeKB
181
- # on the server and will assign to a "best effort" default based on protocol version
182
- if fragmenter.max_blob_length == WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH
183
- fragmenter.max_blob_length = default_protocol_envelope_size(parsed.protocol_version)
184
- end
185
- end
186
- end
187
- end
188
- end
189
-
190
- # Powershell v2.0 has a protocol version of 2.1
191
- # which defaults to a 150 MaxEnvelopeSizeKB
192
- # later versions default to 500
193
- def default_protocol_envelope_size(protocol_version)
194
- protocol_version > '2.1' ? 512000 : 153600
195
- end
196
-
197
- def fragmenter
198
- @fragmenter ||= WinRM::PSRP::MessageFragmenter.new(max_fragment_blob_size)
199
- end
200
- end
201
- end
202
- 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 'securerandom'
18
+ require_relative 'base'
19
+ require_relative '../psrp/message_fragmenter'
20
+ require_relative '../psrp/receive_response_reader'
21
+ require_relative '../wsmv/configuration'
22
+ require_relative '../wsmv/create_pipeline'
23
+ require_relative '../wsmv/send_data'
24
+ require_relative '../wsmv/init_runspace_pool'
25
+ require_relative '../wsmv/keep_alive'
26
+
27
+ module WinRM
28
+ module Shells
29
+ # Proxy to a remote PowerShell instance
30
+ class Powershell < Base
31
+ include WinRM::WSMV::SOAP
32
+
33
+ class << self
34
+ def finalize(connection_opts, transport, shell_id)
35
+ proc { Powershell.close_shell(connection_opts, transport, shell_id) }
36
+ end
37
+
38
+ def close_shell(connection_opts, transport, shell_id)
39
+ msg = WinRM::WSMV::CloseShell.new(
40
+ connection_opts,
41
+ shell_id: shell_id,
42
+ shell_uri: WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL
43
+ )
44
+ transport.send_request(msg.build)
45
+ end
46
+ end
47
+
48
+ # Create a new powershell shell
49
+ # @param connection_opts [ConnectionOpts] The WinRM connection options
50
+ # @param transport [HttpTransport] The WinRM SOAP transport
51
+ # @param logger [Logger] The logger to log diagnostic messages to
52
+ def initialize(connection_opts, transport, logger)
53
+ super
54
+ @shell_uri = WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL
55
+ end
56
+
57
+ # Runs the specified command
58
+ # @param command [String] The powershell script to run
59
+ # @param block [&block] The optional callback for any realtime output
60
+ # @yield [Message] PSRP Message in response
61
+ # @yieldreturn [Array<Message>] All messages in response
62
+ def send_pipeline_command(command, &block)
63
+ with_command_shell(command) do |shell, cmd|
64
+ response_reader.read_message(command_output_message(shell, cmd), true, &block)
65
+ end
66
+ end
67
+
68
+ # calculate the maimum fragment size so that they will be as large as possible yet
69
+ # no greater than the max_envelope_size_kb on the end point. To calculate this
70
+ # threshold, we:
71
+ # - determine the maximum number of bytes accepted on the endpoint
72
+ # - subtract the non-fragment characters in the SOAP envelope
73
+ # - determine the number of bytes that could be base64 encded to the above length
74
+ # - subtract the fragment header bytes (ids, length, etc)
75
+ def max_fragment_blob_size
76
+ @max_fragment_blob_size ||= begin
77
+ fragment_header_length = 21
78
+
79
+ begin
80
+ max_fragment_bytes = (max_envelope_size_kb * 1024) - empty_pipeline_envelope.length
81
+ base64_deflated(max_fragment_bytes) - fragment_header_length
82
+ rescue WinRMWSManFault => e
83
+ # A non administrator user will encounter an access denied
84
+ # error attempting to query winrm configuration.
85
+ # we will assin a small default and adjust to a protocol
86
+ # appropriate max length when that info is available
87
+ raise unless e.fault_code == '5'
88
+ WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH
89
+ end
90
+ end
91
+ end
92
+
93
+ protected
94
+
95
+ def response_reader
96
+ @response_reader ||= WinRM::PSRP::ReceiveResponseReader.new(transport, logger)
97
+ end
98
+
99
+ def send_command(command, _arguments)
100
+ command_id = SecureRandom.uuid.to_s.upcase
101
+ command += "\r\nif (!$?) { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
102
+ message = PSRP::MessageFactory.create_pipeline_message(@runspace_id, command_id, command)
103
+ fragmenter.fragment(message) do |fragment|
104
+ command_args = [connection_opts, shell_id, command_id, fragment]
105
+ if fragment.start_fragment
106
+ resp_doc = transport.send_request(WinRM::WSMV::CreatePipeline.new(*command_args).build)
107
+ command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
108
+ else
109
+ transport.send_request(WinRM::WSMV::SendData.new(*command_args).build)
110
+ end
111
+ end
112
+
113
+ logger.debug("[WinRM] Command created for #{command} with id: #{command_id}")
114
+ command_id
115
+ end
116
+
117
+ def open_shell
118
+ @runspace_id = SecureRandom.uuid.to_s.upcase
119
+ runspace_msg = WinRM::WSMV::InitRunspacePool.new(
120
+ connection_opts,
121
+ @runspace_id,
122
+ open_shell_payload(@runspace_id)
123
+ )
124
+ resp_doc = transport.send_request(runspace_msg.build)
125
+ shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
126
+ wait_for_running(shell_id)
127
+ shell_id
128
+ end
129
+
130
+ def out_streams
131
+ %w(stdout)
132
+ end
133
+
134
+ private
135
+
136
+ def base64_deflated(inflated_length)
137
+ inflated_length / 4 * 3
138
+ end
139
+
140
+ def empty_pipeline_envelope
141
+ WinRM::WSMV::CreatePipeline.new(
142
+ connection_opts,
143
+ '00000000-0000-0000-0000-000000000000',
144
+ '00000000-0000-0000-0000-000000000000'
145
+ ).build
146
+ end
147
+
148
+ def max_envelope_size_kb
149
+ @max_envelope_size_kb ||= begin
150
+ config_msg = WinRM::WSMV::Configuration.new(connection_opts)
151
+ msg = config_msg.build
152
+ resp_doc = transport.send_request(msg)
153
+ REXML::XPath.first(resp_doc, "//#{NS_WSMAN_CONF}:MaxEnvelopeSizekb").text.to_i
154
+ end
155
+ end
156
+
157
+ def open_shell_payload(shell_id)
158
+ [
159
+ WinRM::PSRP::MessageFactory.session_capability_message(shell_id),
160
+ WinRM::PSRP::MessageFactory.init_runspace_pool_message(shell_id)
161
+ ].map do |message|
162
+ fragmenter.fragment(message).bytes
163
+ end.flatten
164
+ end
165
+
166
+ def wait_for_running(shell_id)
167
+ state = WinRM::PSRP::MessageData::RunspacepoolState::OPENING
168
+ keepalive_msg = WinRM::WSMV::KeepAlive.new(connection_opts, shell_id)
169
+
170
+ # 2 is "openned". if we start issuing commands while in "openning" the runspace
171
+ # seems to hang
172
+ until state == WinRM::PSRP::MessageData::RunspacepoolState::OPENED
173
+ response_reader.read_message(keepalive_msg) do |message|
174
+ logger.debug("[WinRM] polling for pipeline state. message: #{message.inspect}")
175
+ parsed = message.parsed_data
176
+ case parsed
177
+ when WinRM::PSRP::MessageData::RunspacepoolState
178
+ state = parsed.runspace_state
179
+ when WinRM::PSRP::MessageData::SessionCapability
180
+ # if the user lacks admin privileges, we cannot query the MaxEnvelopeSizeKB
181
+ # on the server and will assign to a "best effort" default based on protocol version
182
+ if fragmenter.max_blob_length == WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH
183
+ fragmenter.max_blob_length = default_protocol_envelope_size(parsed.protocol_version)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ # Powershell v2.0 has a protocol version of 2.1
191
+ # which defaults to a 150 MaxEnvelopeSizeKB
192
+ # later versions default to 500
193
+ def default_protocol_envelope_size(protocol_version)
194
+ protocol_version > '2.1' ? 512000 : 153600
195
+ end
196
+
197
+ def fragmenter
198
+ @fragmenter ||= WinRM::PSRP::MessageFragmenter.new(max_fragment_blob_size)
199
+ end
200
+ end
201
+ end
202
+ end