class WinRM::Shells::Powershell
Proxy to a remote PowerShell instance
Public Class Methods
# File lib/winrm/shells/power_shell.rb, line 32 def close_shell(connection_opts, transport, shell_id) msg = WinRM::WSMV::CloseShell.new( connection_opts, shell_id: shell_id, shell_uri: WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL ) transport.send_request(msg.build) end
Create a new powershell shell @param connection_opts [ConnectionOpts] The WinRM
connection options @param transport [HttpTransport] The WinRM
SOAP transport @param logger [Logger] The logger to log diagnostic messages to
WinRM::Shells::Base::new
# File lib/winrm/shells/power_shell.rb, line 46 def initialize(connection_opts, transport, logger) super @shell_uri = WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL end
Public Instance Methods
calculate the maimum fragment size so that they will be as large as possible yet no greater than the max_envelope_size_kb
on the end point. To calculate this threshold, we:
-
determine the maximum number of bytes accepted on the endpoint
-
subtract the non-fragment characters in the SOAP envelope
-
determine the number of bytes that could be base64 encded to the above length
-
subtract the fragment header bytes (ids, length, etc)
# File lib/winrm/shells/power_shell.rb, line 69 def max_fragment_blob_size @max_fragment_blob_size ||= begin fragment_header_length = 21 begin max_fragment_bytes = (max_envelope_size_kb * 1024) - empty_pipeline_envelope.length base64_deflated(max_fragment_bytes) - fragment_header_length rescue WinRMWSManFault => e # A non administrator user will encounter an access denied # error attempting to query winrm configuration. # we will assin a small default and adjust to a protocol # appropriate max length when that info is available raise unless e.fault_code == '5' WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH rescue WinRMSoapFault WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH end end end
Runs the specified command @param command [String] The powershell script to run @param block [&block] The optional callback for any realtime output @yield [Message] PSRP
Message in response @yieldreturn [Array<Message>] All messages in response
# File lib/winrm/shells/power_shell.rb, line 56 def send_pipeline_command(command, &block) with_command_shell(command) do |shell, cmd| response_reader.read_message(command_output_message(shell, cmd), true, &block) end end
Protected Instance Methods
# File lib/winrm/shells/power_shell.rb, line 114 def open_shell @runspace_id = SecureRandom.uuid.to_s.upcase runspace_msg = WinRM::WSMV::InitRunspacePool.new( connection_opts, @runspace_id, open_shell_payload(@runspace_id) ) resp_doc = transport.send_request(runspace_msg.build) shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text wait_for_running(shell_id) shell_id end
# File lib/winrm/shells/power_shell.rb, line 127 def out_streams %w[stdout] end
# File lib/winrm/shells/power_shell.rb, line 92 def response_reader @response_reader ||= WinRM::PSRP::ReceiveResponseReader.new(transport, logger) end
# File lib/winrm/shells/power_shell.rb, line 96 def send_command(command, _arguments) command_id = SecureRandom.uuid.to_s.upcase command += "\r\nif (!$?) { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }" message = PSRP::MessageFactory.create_pipeline_message(@runspace_id, command_id, command) fragmenter.fragment(message) do |fragment| command_args = [connection_opts, shell_id, command_id, fragment] if fragment.start_fragment resp_doc = transport.send_request(WinRM::WSMV::CreatePipeline.new(*command_args).build) command_id = REXML::XPath.first(resp_doc, "//*[local-name() = 'CommandId']").text else transport.send_request(WinRM::WSMV::SendData.new(*command_args).build) end end logger.debug("[WinRM] Command created for #{command} with id: #{command_id}") command_id end
Private Instance Methods
# File lib/winrm/shells/power_shell.rb, line 133 def base64_deflated(inflated_length) inflated_length / 4 * 3 end
Powershell
v2.0 has a protocol version of 2.1 which defaults to a 150 MaxEnvelopeSizeKB later versions default to 500
# File lib/winrm/shells/power_shell.rb, line 193 def default_protocol_envelope_size(protocol_version) protocol_version > '2.1' ? 512000 : 153600 end
# File lib/winrm/shells/power_shell.rb, line 137 def empty_pipeline_envelope WinRM::WSMV::CreatePipeline.new( connection_opts, '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000' ).build end
# File lib/winrm/shells/power_shell.rb, line 197 def fragmenter @fragmenter ||= WinRM::PSRP::MessageFragmenter.new(max_fragment_blob_size) end
# File lib/winrm/shells/power_shell.rb, line 145 def max_envelope_size_kb @max_envelope_size_kb ||= begin config_msg = WinRM::WSMV::Configuration.new(connection_opts) msg = config_msg.build resp_doc = transport.send_request(msg) REXML::XPath.first(resp_doc, "//*[local-name() = 'MaxEnvelopeSizekb']").text.to_i rescue REXML::ParseException logger.debug("[WinRM] Endpoint doesn't support config request for MaxEnvelopeSizekb") raise end end
# File lib/winrm/shells/power_shell.rb, line 157 def open_shell_payload(shell_id) [ WinRM::PSRP::MessageFactory.session_capability_message(shell_id), WinRM::PSRP::MessageFactory.init_runspace_pool_message(shell_id) ].map do |message| fragmenter.fragment(message).bytes end.flatten end
# File lib/winrm/shells/power_shell.rb, line 166 def wait_for_running(shell_id) state = WinRM::PSRP::MessageData::RunspacepoolState::OPENING keepalive_msg = WinRM::WSMV::KeepAlive.new(connection_opts, shell_id) # 2 is "openned". if we start issuing commands while in "openning" the runspace # seems to hang until state == WinRM::PSRP::MessageData::RunspacepoolState::OPENED response_reader.read_message(keepalive_msg) do |message| logger.debug("[WinRM] polling for pipeline state. message: #{message.inspect}") parsed = message.parsed_data case parsed when WinRM::PSRP::MessageData::RunspacepoolState state = parsed.runspace_state when WinRM::PSRP::MessageData::SessionCapability # if the user lacks admin privileges, we cannot query the MaxEnvelopeSizeKB # on the server and will assign to a "best effort" default based on protocol version if fragmenter.max_blob_length == WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH fragmenter.max_blob_length = default_protocol_envelope_size(parsed.protocol_version) end end end end end