diff --git a/plugins/commands/powershell/command.rb b/plugins/commands/powershell/command.rb index ad1f1612f..06c2b7dc5 100644 --- a/plugins/commands/powershell/command.rb +++ b/plugins/commands/powershell/command.rb @@ -26,6 +26,10 @@ module VagrantPlugins o.on("-c", "--command COMMAND", "Execute a powershell command directly") do |c| options[:command] = c end + + o.on("-e", "--elevated", "Execute a powershell command with elevated permissions") do |c| + options[:elevated] = true + end end # Parse out the extra args to send to the ps session, which @@ -40,8 +44,8 @@ module VagrantPlugins argv = parse_options(opts) return if !argv - # Check if the host even supports ps remoting - raise Errors::HostUnsupported if !@env.host.capability?(:ps_client) + # Elevated option enabled means we can only execute commands + raise Errors::ElevatedNoCommand if !options[:command] && options[:elevated] # Execute ps session if we can with_target_vms(argv, single_target: true) do |machine| @@ -49,12 +53,12 @@ module VagrantPlugins raise Vagrant::Errors::VMNotCreatedError end - if machine.config.vm.communicator != :winrm - raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady - end + if options[:command] + if machine.config.vm.communicator != :winrm + raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady + end - if !options[:command].nil? - out_code = machine.communicate.execute(options[:command].dup) do |type,data| + out_code = machine.communicate.execute(options[:command].dup, elevated: options[:elevated]) do |type,data| machine.ui.detail(data) if type == :stdout end if out_code == 0 @@ -63,6 +67,9 @@ module VagrantPlugins next end + # Check if the host even supports ps remoting + raise Errors::HostUnsupported if !@env.host.capability?(:ps_client) + ps_info = VagrantPlugins::CommunicatorWinRM::Helper.winrm_info(machine) ps_info[:username] = machine.config.winrm.username ps_info[:password] = machine.config.winrm.password @@ -78,7 +85,7 @@ module VagrantPlugins begin @env.host.capability(:ps_client, ps_info) ensure - if !result["PreviousTrustedHosts"].nil? + if result["PreviousTrustedHosts"] reset_ps_remoting_for(machine, ps_info) end end diff --git a/plugins/commands/powershell/errors.rb b/plugins/commands/powershell/errors.rb index 4be70551a..769ea6b2e 100644 --- a/plugins/commands/powershell/errors.rb +++ b/plugins/commands/powershell/errors.rb @@ -17,6 +17,10 @@ module VagrantPlugins class PowerShellError < PSCommandError error_key(:powershell_error) end + + class ElevatedNoCommand < PSCommandError + error_key(:elevated_no_command) + end end end end diff --git a/templates/locales/command_ps.yml b/templates/locales/command_ps.yml index 0c61964ed..f0eef16f9 100644 --- a/templates/locales/command_ps.yml +++ b/templates/locales/command_ps.yml @@ -6,13 +6,18 @@ en: Resetting WinRM TrustedHosts to their original value. errors: + elevated_no_command: |- + A command must be provided when the --elevated flag is provided for + the powershell command. Please provide a command when using the + --elevated flag and try again. + host_unsupported: |- Your host does not support PowerShell. A remote PowerShell connection can only be made from a windows host. ps_remoting_undetected: |- - Unable to establish a remote PowerShell connection with the guest. - Check if the firewall rules on the guest allow connections to the + Unable to establish a remote PowerShell connection with the guest. + Check if the firewall rules on the guest allow connections to the Windows remote management service. powershell_error: |- diff --git a/test/unit/plugins/commands/powershell/command_test.rb b/test/unit/plugins/commands/powershell/command_test.rb new file mode 100644 index 000000000..9ed5e3a56 --- /dev/null +++ b/test/unit/plugins/commands/powershell/command_test.rb @@ -0,0 +1,142 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/powershell/command") + +describe VagrantPlugins::CommandPS::Command do + include_context "unit" + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:guest) { double("guest") } + let(:host) { double("host") } + let(:config) { + double("config", + vm: double("vm", communicator: communicator_name), + winrm: double("winrm", username: winrm_username, password: winrm_password) + ) + } + let(:communicator_name) { :winrm } + let(:winrm_info) { {host: winrm_host, port: winrm_port} } + let(:winrm_username) { double("winrm_username") } + let(:winrm_password) { double("winrm_password") } + let(:winrm_host) { double("winrm_host") } + let(:winrm_port) { double("winrm_port") } + + let(:remoting_ready_result) { {} } + + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + + let(:argv) { [] } + + subject { described_class.new(argv, iso_env) } + + before do + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + allow(iso_env).to receive(:host).and_return(host) + allow(host).to receive(:capability?).with(:ps_client).and_return(true) + + allow(machine.communicate).to receive(:ready?).and_return(true) + allow(machine).to receive(:config).and_return(config) + + allow(VagrantPlugins::CommunicatorWinRM::Helper).to receive(:winrm_info).and_return(winrm_info) + allow(subject).to receive(:ready_ps_remoting_for).and_return(remoting_ready_result) + allow(host).to receive(:capability).with(:ps_client, any_args) + + # Ignore loading up translations + allow_any_instance_of(Vagrant::Errors::VagrantError).to receive(:translate_error) + end + + describe "#execute" do + context "when communicator is not ready" do + before { expect(machine.communicate).to receive(:ready?).and_return(false) } + + it "should raise error that machine is not created" do + expect { subject.execute }.to raise_error(Vagrant::Errors::VMNotCreatedError) + end + end + + context "when communicator is not winrm" do + let(:communicator_name) { :ssh } + + context "when command is provided" do + let(:argv) { ["-c", "command"] } + + it "should raise an error that winrm is not ready" do + expect { subject.execute }.to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady) + end + end + + context "when no command is provided" do + it "should create a powershell session" do + expect(host).to receive(:capability).with(:ps_client, any_args) + subject.execute + end + end + end + + context "when host does not support ps_client" do + before { allow(host).to receive(:capability?).with(:ps_client).and_return(false) } + + context "when no command is provided" do + it "should raise an error for unsupported host" do + expect { subject.execute }.to raise_error(VagrantPlugins::CommandPS::Errors::HostUnsupported) + end + end + + context "when command is provided" do + let(:argv) { ["-c", "command"] } + + it "should execute command when command is provided" do + expect(machine.communicate).to receive(:execute).with("command", any_args).and_return(0) + subject.execute + end + end + end + + context "with command provided" do + let(:argv) { ["-c", "command"] } + + it "executes the command on the guest" do + expect(machine.communicate).to receive(:execute).with("command", any_args).and_return(0) + subject.execute + end + + context "with elevated flag" do + let(:argv) { ["-e", "-c", "command"] } + + it "should execute the command with elevated option" do + expect(machine.communicate).to receive(:execute). + with("command", hash_including(elevated: true)).and_return(0) + subject.execute + end + end + end + + context "with elevated flag and no command" do + let(:argv) { ["-e"] } + + it "should raise error that command must be provided" do + expect { subject.execute }.to raise_error(VagrantPlugins::CommandPS::Errors::ElevatedNoCommand) + end + end + + it "should start a new session" do + expect(host).to receive(:capability).with(:ps_client, any_args) + subject.execute + end + + context "when setup returns PreviousTrustedHosts" do + let(:remoting_ready_result) { {"PreviousTrustedHosts" => true} } + + it "should reset the powershell remoting" do + expect(subject).to receive(:reset_ps_remoting_for) + subject.execute + end + end + end +end