diff --git a/lib/vagrant/action/builtin/ssh_run.rb b/lib/vagrant/action/builtin/ssh_run.rb index 9ea58ca50..2ac41dffa 100644 --- a/lib/vagrant/action/builtin/ssh_run.rb +++ b/lib/vagrant/action/builtin/ssh_run.rb @@ -36,17 +36,35 @@ module Vagrant # Get the command and wrap it in a login shell command = ShellQuote.escape(env[:ssh_run_command], "'") - command = "#{env[:machine].config.ssh.shell} -c '#{command}'" + + if env[:machine].config.vm.communicator == :winssh + shell = env[:machine].config.winssh.shell + else + shell = env[:machine].config.ssh.shell + end + + if shell == "cmd" + # Add an extra space to the command so cmd.exe quoting works + # properly + command = "#{shell} /C #{command} " + elsif shell == "powershell" + command = "$ProgressPreference = \"SilentlyContinue\"; #{command}" + command = Base64.strict_encode64(command.encode("UTF-16LE", "UTF-8")) + command = "#{shell} -encodedCommand #{command}" + else + command = "#{shell} -c '#{command}'" + end # Execute! opts = env[:ssh_opts] || {} opts[:extra_args] ||= [] # Allow the user to specify a tty or non-tty manually, but if they - # don't then we default to a TTY + # don't then we default to a TTY unless they are using WinSSH if !opts[:extra_args].include?("-t") && !opts[:extra_args].include?("-T") && - env[:tty] + env[:tty] && + env[:machine].config.vm.communicator != :winssh opts[:extra_args] << "-t" end diff --git a/test/unit/vagrant/action/builtin/ssh_run_test.rb b/test/unit/vagrant/action/builtin/ssh_run_test.rb index 56609607d..49053afeb 100644 --- a/test/unit/vagrant/action/builtin/ssh_run_test.rb +++ b/test/unit/vagrant/action/builtin/ssh_run_test.rb @@ -18,8 +18,14 @@ describe Vagrant::Action::Builtin::SSHRun do ) end + let(:vm) do + double("vm", + communicator: nil + ) + end + # Configuration mock - let(:config) { double("config", ssh: ssh) } + let(:config) { double("config", ssh: ssh, vm: vm) } let(:machine) do double("machine", @@ -80,4 +86,63 @@ describe Vagrant::Action::Builtin::SSHRun do env[:ssh_run_command] = "echo test" described_class.new(app, env).call(env) end + + context "when using the WinSSH communicator" do + let(:winssh) { double("winssh", shell: "foo") } + + before do + expect(vm).to receive(:communicator).and_return(:winssh) + expect(config).to receive(:winssh).and_return(winssh) + env[:tty] = nil + end + + it "should use the WinSSH shell for running ssh commands" do + ssh_info = { foo: :bar } + opts = {:extra_args=>["foo -c 'dir'"], :subprocess=>true} + + expect(ssh_klass).to receive(:exec). + with(ssh_info, opts) + + env[:ssh_info] = ssh_info + env[:ssh_run_command] = "dir" + described_class.new(app, env).call(env) + end + + context "when shell is cmd" do + before do + expect(winssh).to receive(:shell).and_return('cmd') + end + + it "should use appropriate options for cmd" do + ssh_info = { foo: :bar } + opts = {:extra_args=>["cmd /C dir "], :subprocess=>true} + + expect(ssh_klass).to receive(:exec). + with(ssh_info, opts) + + env[:ssh_info] = ssh_info + env[:ssh_run_command] = "dir" + described_class.new(app, env).call(env) + end + end + + context "when shell is powershell" do + before do + expect(winssh).to receive(:shell).and_return('powershell') + end + + it "should base64 encode the command" do + ssh_info = { foo: :bar } + encoded_command = "JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgA7ACAAZABpAHIA" + opts = {:extra_args=>["powershell -encodedCommand #{encoded_command}"], :subprocess=>true} + + expect(ssh_klass).to receive(:exec). + with(ssh_info, opts) + + env[:ssh_info] = ssh_info + env[:ssh_run_command] = "dir" + described_class.new(app, env).call(env) + end + end + end end diff --git a/website/source/docs/vagrantfile/ssh_settings.html.md b/website/source/docs/vagrantfile/ssh_settings.html.md index a478959a3..e856b3c0c 100644 --- a/website/source/docs/vagrantfile/ssh_settings.html.md +++ b/website/source/docs/vagrantfile/ssh_settings.html.md @@ -126,9 +126,7 @@ net-ssh library (ignored by the `ssh` executable) and should not be used in gene This defaults to the value of `config.ssh.username`. * `config.ssh.shell` (string) - The shell to use when executing SSH commands from -Vagrant. By default this is `bash -l`. Note that this has no effect on -the shell you get when you run `vagrant ssh`. This configuration option -only affects the shell to use when executing commands internally in Vagrant. +Vagrant. By default this is `bash -l`. * `config.ssh.sudo_command` (string) - The command to use when executing a command with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by diff --git a/website/source/docs/vagrantfile/winssh_settings.html.md b/website/source/docs/vagrantfile/winssh_settings.html.md index 3c41fb527..98b0ca103 100644 --- a/website/source/docs/vagrantfile/winssh_settings.html.md +++ b/website/source/docs/vagrantfile/winssh_settings.html.md @@ -48,9 +48,7 @@ packets every 5 seconds by default to keep connections alive. * `config.winssh.shell` (string) - The shell to use when executing SSH commands from Vagrant. By default this is `cmd`. Valid values are `"cmd"` or `"powershell"`. -Note that this has no effect on the shell you get when you run `vagrant ssh`. -This configuration option only affects the shell to use when executing commands -internally in Vagrant. +When the WinSSH provider is enabled, this shell will be used when you run `vagrant ssh`. * `config.winssh.export_command_template` (string) - The template used to generate exported environment variables in the active session. This can be useful