Perform scp commands with powershell.exe
This commit includes a monkey patch for Net::SCP#start_command so that PowerShell commands are escaped correctly.
This commit is contained in:
parent
85f0fce57a
commit
b6e8262bf2
@ -207,7 +207,7 @@ SCRIPT
|
||||
@logger.debug("Uploading file #{path} to remote #{dest}")
|
||||
upload_file = File.open(path, "rb")
|
||||
begin
|
||||
scp.upload!(upload_file, dest)
|
||||
scp.upload!(upload_file, dest, shell: "powershell.exe")
|
||||
ensure
|
||||
upload_file.close
|
||||
end
|
||||
@ -234,3 +234,53 @@ SCRIPT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This monkey patches Net::SCP#start_command so that we don't apply special
|
||||
# shell escaping rules to the remote path when using powershell.exe as a shell.
|
||||
# PowerShell also needs single quotes around the remote path if the path has
|
||||
# spaces in it.
|
||||
#
|
||||
# Here is an example of a properly formatted scp command for PowerShell:
|
||||
#
|
||||
# scp.exe -t 'c:/destination path'
|
||||
#
|
||||
module Net
|
||||
class SCP
|
||||
def start_command(mode, local, remote, options={}, &callback)
|
||||
session.open_channel do |channel|
|
||||
|
||||
if options[:shell].to_s == "powershell.exe"
|
||||
powershell_escaped = remote.gsub(/'/, "''")
|
||||
command = "#{options[:shell]} -c #{scp_command(mode, options)} '#{powershell_escaped}'"
|
||||
elsif options[:shell]
|
||||
escaped_file = shellescape(remote).gsub(/'/) { |m| "'\\''" }
|
||||
command = "#{options[:shell]} -c #{scp_command(mode, options)} #{escaped_file}"
|
||||
else
|
||||
command = "#{scp_command(mode, options)} #{shellescape(remote)}"
|
||||
end
|
||||
|
||||
channel.exec(command) do |ch, success|
|
||||
if success
|
||||
channel[:local ] = local
|
||||
channel[:remote ] = remote
|
||||
channel[:options ] = options.dup
|
||||
channel[:callback] = callback
|
||||
channel[:buffer ] = Net::SSH::Buffer.new
|
||||
channel[:state ] = "#{mode}_start"
|
||||
channel[:stack ] = []
|
||||
channel[:error_string] = ''
|
||||
|
||||
channel.on_close { |ch| send("#{channel[:state]}_state", channel); raise Net::SCP::Error, "SCP did not finish successfully (#{channel[:exit]}): #{channel[:error_string]}" if channel[:exit] != 0 }
|
||||
channel.on_data { |ch, data| channel[:buffer].append(data) }
|
||||
channel.on_extended_data { |ch, type, data| debug { data.chomp } }
|
||||
channel.on_request("exit-status") { |ch, data| channel[:exit] = data.read_long }
|
||||
channel.on_process { send("#{channel[:state]}_state", channel) }
|
||||
else
|
||||
channel.close
|
||||
raise Net::SCP::Error, "could not exec scp on the remote host"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -274,7 +274,7 @@ describe VagrantPlugins::CommunicatorWinSSH::Communicator do
|
||||
it "uploads a directory if local path is a directory" do
|
||||
Dir.mktmpdir('vagrant-test') do |dir|
|
||||
FileUtils.touch(File.join(dir, "test-file"))
|
||||
expect(scp).to receive(:upload!).with(an_instance_of(File), /test-file/)
|
||||
expect(scp).to receive(:upload!).with(an_instance_of(File), /test-file/, {shell: "powershell.exe"})
|
||||
communicator.upload(dir, 'C:\destination')
|
||||
end
|
||||
end
|
||||
@ -282,7 +282,7 @@ describe VagrantPlugins::CommunicatorWinSSH::Communicator do
|
||||
it "uploads a file if local path is a file" do
|
||||
file = Tempfile.new('vagrant-test')
|
||||
begin
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:/destination/file')
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:/destination/file', {shell: "powershell.exe"})
|
||||
expect(Vagrant::Util::Platform).to receive(:unix_windows_path).with('C:\destination\file').
|
||||
and_call_original
|
||||
communicator.upload(file.path, 'C:\destination\file')
|
||||
@ -294,7 +294,7 @@ describe VagrantPlugins::CommunicatorWinSSH::Communicator do
|
||||
it "raises custom error on permission errors" do
|
||||
file = Tempfile.new('vagrant-test')
|
||||
begin
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:/destination/file').
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:/destination/file', {shell: "powershell.exe"}).
|
||||
and_raise("Permission denied")
|
||||
expect{ communicator.upload(file.path, 'C:\destination\file') }.to(
|
||||
raise_error(Vagrant::Errors::SCPPermissionDenied)
|
||||
@ -307,7 +307,7 @@ describe VagrantPlugins::CommunicatorWinSSH::Communicator do
|
||||
it "does not raise custom error on non-permission errors" do
|
||||
file = Tempfile.new('vagrant-test')
|
||||
begin
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:/destination/file').
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:/destination/file', {shell: "powershell.exe"}).
|
||||
and_raise("Some other error")
|
||||
expect{ communicator.upload(file.path, 'C:\destination\file') }.to raise_error(RuntimeError)
|
||||
ensure
|
||||
@ -585,3 +585,32 @@ describe VagrantPlugins::CommunicatorWinSSH::Communicator do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Tests for Net::SCP#start_command patch
|
||||
describe Net::SCP do
|
||||
include_context "unit"
|
||||
|
||||
let(:session) do
|
||||
double("session",
|
||||
logger: nil)
|
||||
end
|
||||
|
||||
let(:scp){ @scp ||= described_class.new(session) }
|
||||
|
||||
let(:channel) { double("channel") }
|
||||
|
||||
before do
|
||||
allow(session).to receive(:open_channel).and_yield(channel)
|
||||
end
|
||||
|
||||
describe "#start_command" do
|
||||
context "with shell set to powershell.exe" do
|
||||
let(:options) { {:shell => "powershell.exe" } }
|
||||
|
||||
it "escapes single quotes in the destination" do
|
||||
expect(channel).to receive(:exec).with(/C:\/vagrant''s scripts/)
|
||||
scp.start_command("", anything, "C:/vagrant's scripts", options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user