# encoding: UTF-8
require 'winrm/shells/power_shell'
describe WinRM::Shells::Powershell do
let(:retry_limit) { 1 }
let(:max_envelope_size_kb) { 150 }
let(:shell_id) { 'bc1bfbba-8215-4a04-b2df-7a3ac0310e16' }
let(:output) { 'output' }
let(:command_id) { '4218A578-0F18-4B19-82C3-46B433319126' }
let(:keepalive_payload) { 'keepalive_payload' }
let(:configuration_payload) { 'configuration_payload' }
let(:command_payload) { 'command_payload' }
let(:create_shell_payload) { 'create_shell_payload' }
let(:close_shell_payload) { 'close_shell_payload' }
let(:cleanup_payload) { 'cleanup_payload' }
let(:command) { 'command' }
let(:output_message) { double('output_message', build: 'output_message') }
let(:command_response) { "#{command_id}" }
let(:configuration_response) do
"#{max_envelope_size_kb}"
end
let(:connection_options) { { max_commands: 100, retry_limit: retry_limit, retry_delay: 0 } }
let(:transport) { double('transport', send_request: nil) }
let(:test_data_xml_template) do
ERB.new(stubbed_response('get_powershell_keepalive_response.xml.erb'))
end
let(:protocol_version) { 2.2 }
let(:test_data1) do
<<-EOH
#{protocol_version}
2.0
1.1.0.1
EOH
end
let(:test_data2) { '2' }
let(:message1) do
WinRM::PSRP::Message.new(
shell_id,
WinRM::PSRP::Message::MESSAGE_TYPES[:session_capability],
test_data1,
command_id
)
end
let(:message2) do
WinRM::PSRP::Message.new(
shell_id,
WinRM::PSRP::Message::MESSAGE_TYPES[:runspacepool_state],
test_data2,
command_id
)
end
let(:fragment1) { WinRM::PSRP::Fragment.new(1, message1.bytes) }
let(:fragment2) { WinRM::PSRP::Fragment.new(1, message2.bytes) }
let(:test_data_stdout1) { Base64.strict_encode64(fragment1.bytes.pack('C*')) }
let(:test_data_stdout2) { Base64.strict_encode64(fragment2.bytes.pack('C*')) }
before do
allow(SecureRandom).to receive(:uuid).and_return(command_id)
allow(subject).to receive(:command_output_message).with(shell_id, command_id)
.and_return(output_message)
allow_any_instance_of(WinRM::WSMV::CreatePipeline).to receive(:build)
.and_return(command_payload)
allow_any_instance_of(WinRM::WSMV::CloseShell).to receive(:build)
.and_return(close_shell_payload)
allow_any_instance_of(WinRM::WSMV::InitRunspacePool).to receive(:build)
.and_return(create_shell_payload)
allow_any_instance_of(WinRM::WSMV::Configuration).to receive(:build)
.and_return(configuration_payload)
allow_any_instance_of(WinRM::WSMV::CleanupCommand).to receive(:build)
.and_return(cleanup_payload)
allow_any_instance_of(WinRM::WSMV::KeepAlive).to receive(:build).and_return(keepalive_payload)
allow_any_instance_of(WinRM::PSRP::ReceiveResponseReader).to receive(:read_output)
.with(output_message).and_return(output)
allow(transport).to receive(:send_request).with(configuration_payload).and_return(
REXML::Document.new(configuration_response))
allow(transport).to receive(:send_request).with(create_shell_payload)
.and_return(REXML::Document.new("#{shell_id}"))
allow(transport).to receive(:send_request).with(command_payload)
.and_return(REXML::Document.new(command_response))
allow(transport).to receive(:send_request).with(keepalive_payload)
.and_return(REXML::Document.new(test_data_xml_template.result(binding)))
end
subject { described_class.new(connection_options, transport, Logging.logger['test']) }
describe '#run' do
it 'opens a shell and gets shell id' do
subject.run(command)
expect(subject.shell_id).to eq shell_id
end
it 'sends create shell through transport' do
expect(transport).to receive(:send_request).with(create_shell_payload)
subject.run(command)
end
it 'sends keepalive shell through transport' do
expect(transport).to receive(:send_request).with(keepalive_payload)
subject.run(command)
end
it 'returns output from generated command' do
expect(subject.run(command)).to eq output
end
it 'sends command through transport' do
expect(transport).to receive(:send_request).with(command_payload)
subject.run(command)
end
it 'sends cleanup message through transport' do
expect(transport).to receive(:send_request).with(cleanup_payload)
subject.run(command)
end
context 'non admin user' do
before do
allow(transport).to receive(:send_request).with(configuration_payload)
.and_raise(WinRM::WinRMWSManFault.new('no access for you', '5'))
end
context 'protocol version 2.1' do
let(:protocol_version) { 2.1 }
it 'sets the fragmenter max_blob_length' do
expect_any_instance_of(WinRM::PSRP::MessageFragmenter).to receive(:max_blob_length=)
.with(153600)
subject.run(command)
end
end
context 'protocol version 2.2' do
let(:protocol_version) { 2.2 }
it 'sets the fragmenter max_blob_length' do
expect_any_instance_of(WinRM::PSRP::MessageFragmenter).to receive(:max_blob_length=)
.with(512000)
subject.run(command)
end
end
end
context 'fragment large command' do
let(:command) { 'c' * 200000 }
it 'fragments messages as large as max envelope size' do
allow_any_instance_of(WinRM::WSMV::CreatePipeline).to receive(:build).and_call_original
allow(transport).to receive(:send_request).with(/CommandLine/) do |payload|
expect(payload.length).to be max_envelope_size_kb * 1024
end.and_return(REXML::Document.new(command_response))
subject.run(command)
end
end
end
describe '#close' do
it 'sends close shell through transport' do
subject.run(command)
expect(transport).to receive(:send_request).with(close_shell_payload)
subject.close
end
it 'creates a shell closer with powershell uri' do
allow(WinRM::WSMV::CloseShell).to receive(:new) do |_, opts|
expect(opts[:shell_uri]).to be WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL
end.and_call_original
subject.close
end
end
end