Merge pull request #10528 from chrisroberts/e-ps-elevated

Add support for running elevated commands using the powershell command
This commit is contained in:
Chris Roberts 2019-01-03 15:25:56 -08:00 committed by GitHub
commit 023238b3d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 168 additions and 10 deletions

View File

@ -26,6 +26,10 @@ module VagrantPlugins
o.on("-c", "--command COMMAND", "Execute a powershell command directly") do |c| o.on("-c", "--command COMMAND", "Execute a powershell command directly") do |c|
options[:command] = c options[:command] = c
end end
o.on("-e", "--elevated", "Execute a powershell command with elevated permissions") do |c|
options[:elevated] = true
end
end end
# Parse out the extra args to send to the ps session, which # Parse out the extra args to send to the ps session, which
@ -40,8 +44,8 @@ module VagrantPlugins
argv = parse_options(opts) argv = parse_options(opts)
return if !argv return if !argv
# Check if the host even supports ps remoting # Elevated option enabled means we can only execute commands
raise Errors::HostUnsupported if !@env.host.capability?(:ps_client) raise Errors::ElevatedNoCommand if !options[:command] && options[:elevated]
# Execute ps session if we can # Execute ps session if we can
with_target_vms(argv, single_target: true) do |machine| with_target_vms(argv, single_target: true) do |machine|
@ -49,12 +53,12 @@ module VagrantPlugins
raise Vagrant::Errors::VMNotCreatedError raise Vagrant::Errors::VMNotCreatedError
end end
if machine.config.vm.communicator != :winrm if options[:command]
raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady if machine.config.vm.communicator != :winrm
end raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady
end
if !options[:command].nil? out_code = machine.communicate.execute(options[:command].dup, elevated: options[:elevated]) do |type,data|
out_code = machine.communicate.execute(options[:command].dup) do |type,data|
machine.ui.detail(data) if type == :stdout machine.ui.detail(data) if type == :stdout
end end
if out_code == 0 if out_code == 0
@ -63,6 +67,9 @@ module VagrantPlugins
next next
end 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 = VagrantPlugins::CommunicatorWinRM::Helper.winrm_info(machine)
ps_info[:username] = machine.config.winrm.username ps_info[:username] = machine.config.winrm.username
ps_info[:password] = machine.config.winrm.password ps_info[:password] = machine.config.winrm.password
@ -78,7 +85,7 @@ module VagrantPlugins
begin begin
@env.host.capability(:ps_client, ps_info) @env.host.capability(:ps_client, ps_info)
ensure ensure
if !result["PreviousTrustedHosts"].nil? if result["PreviousTrustedHosts"]
reset_ps_remoting_for(machine, ps_info) reset_ps_remoting_for(machine, ps_info)
end end
end end

View File

@ -17,6 +17,10 @@ module VagrantPlugins
class PowerShellError < PSCommandError class PowerShellError < PSCommandError
error_key(:powershell_error) error_key(:powershell_error)
end end
class ElevatedNoCommand < PSCommandError
error_key(:elevated_no_command)
end
end end
end end
end end

View File

@ -6,13 +6,18 @@ en:
Resetting WinRM TrustedHosts to their original value. Resetting WinRM TrustedHosts to their original value.
errors: 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: |- host_unsupported: |-
Your host does not support PowerShell. A remote PowerShell connection Your host does not support PowerShell. A remote PowerShell connection
can only be made from a windows host. can only be made from a windows host.
ps_remoting_undetected: |- ps_remoting_undetected: |-
Unable to establish a remote PowerShell connection with the guest. Unable to establish a remote PowerShell connection with the guest.
Check if the firewall rules on the guest allow connections to the Check if the firewall rules on the guest allow connections to the
Windows remote management service. Windows remote management service.
powershell_error: |- powershell_error: |-

View File

@ -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