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

View File

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

View File

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

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