diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb
index c9cfa8327..3bed39f72 100644
--- a/plugins/communicators/winrm/communicator.rb
+++ b/plugins/communicators/winrm/communicator.rb
@@ -57,13 +57,23 @@ module VagrantPlugins
return 0 if command.empty?
opts = {
- :error_check => true,
- :error_class => Errors::ExecutionError,
- :error_key => :execution_error,
- :command => command,
- :shell => :powershell
+ error_check: true,
+ error_class: Errors::ExecutionError,
+ error_key: :execution_error,
+ command: command,
+ shell: :powershell,
+ elevated: false
}.merge(opts || {})
+ if opts[:elevated]
+ path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__)
+ command = Vagrant::Util::TemplateRenderer.render(path, options: {
+ username: shell.username,
+ password: shell.password,
+ command: command,
+ })
+ end
+
output = shell.send(opts[:shell], command, &block)
execution_output(output, opts)
end
@@ -121,7 +131,7 @@ module VagrantPlugins
# The error classes expect the translation key to be _key, but that makes for an ugly
# configuration parameter, so we set it here from `error_key`
msg = "Command execution failed with an exit code of #{output[:exitcode]}"
- error_opts = opts.merge(:_key => opts[:error_key], :message => msg)
+ error_opts = opts.merge(_key: opts[:error_key], message: msg)
raise opts[:error_class], error_opts
end
end #WinRM class
diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb
new file mode 100644
index 000000000..97c5f822a
--- /dev/null
+++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb
@@ -0,0 +1,95 @@
+$command = "<%= options[:command] %>"
+$user = '<%= options[:username] %>'
+$password = '<%= options[:password] %>'
+
+$task_name = "WinRM_Elevated_Shell"
+$out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log"
+
+if (Test-Path $out_file) {
+ del $out_file
+}
+
+$task_xml = @'
+
+
+
+
+ {user}
+ Password
+ HighestAvailable
+
+
+
+ IgnoreNew
+ false
+ false
+ true
+ false
+ false
+
+ true
+ false
+
+ true
+ true
+ false
+ false
+ false
+ PT2H
+ 4
+
+
+
+ cmd
+ {arguments}
+
+
+
+'@
+
+$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
+$encoded_command = [Convert]::ToBase64String($bytes)
+$arguments = "/c powershell.exe -EncodedCommand $encoded_command > $out_file 2>&1"
+
+$task_xml = $task_xml.Replace("{arguments}", $arguments)
+$task_xml = $task_xml.Replace("{user}", $user)
+
+$schedule = New-Object -ComObject "Schedule.Service"
+$schedule.Connect()
+$task = $schedule.NewTask($null)
+$task.XmlText = $task_xml
+$folder = $schedule.GetFolder("\")
+$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, 1, $null) | Out-Null
+
+$registered_task = $folder.GetTask("\$task_name")
+$registered_task.Run($null) | Out-Null
+
+$timeout = 10
+$sec = 0
+while ( (!($registered_task.state -eq 4)) -and ($sec -lt $timeout) ) {
+ Start-Sleep -s 1
+ $sec++
+}
+
+# Read the entire file, but only write out new lines we haven't seen before
+$numLinesRead = 0
+do {
+ Start-Sleep -m 100
+
+ if (Test-Path $out_file) {
+ $text = (get-content $out_file)
+ $numLines = ($text | Measure-Object -line).lines
+ $numLinesToRead = $numLines - $numLinesRead
+
+ if ($numLinesToRead -gt 0) {
+ $text | select -first $numLinesToRead -skip $numLinesRead | ForEach {
+ Write-Host "$_"
+ }
+ $numLinesRead += $numLinesToRead
+ }
+ }
+} while (!($registered_task.state -eq 3))
+
+$exit_code = $registered_task.LastTaskResult
+[System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null
+exit $exit_code
diff --git a/plugins/provisioners/chef/command_builder.rb b/plugins/provisioners/chef/command_builder.rb
index 8a1e26b82..844d48864 100644
--- a/plugins/provisioners/chef/command_builder.rb
+++ b/plugins/provisioners/chef/command_builder.rb
@@ -1,15 +1,53 @@
module VagrantPlugins
module Chef
class CommandBuilder
- def initialize(machine, config, client_type)
- @machine = machine
- @config = config
- @client_type = client_type
+ def initialize(config, client_type, is_windows=false, is_ui_colored=false)
+ @client_type = client_type
+ @config = config
+ @is_windows = is_windows
+ @is_ui_colored = is_ui_colored
if client_type != :solo && client_type != :client
raise 'Invalid client_type, expected solo or client'
end
end
+
+ def build_command
+ "#{command_env}#{chef_binary_path} #{chef_arguments}"
+ end
+
+ protected
+
+ def command_env
+ @config.binary_env ? "#{@config.binary_env} " : ""
+ end
+
+ def chef_binary_path
+ binary_path = "chef-#{@client_type}"
+ if @config.binary_path
+ binary_path = guest_friendly_path(File.join(@config.binary_path, binary_path))
+ end
+ binary_path
+ end
+
+ def chef_arguments
+ chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}"
+ chef_arguments << " -j #{provisioning_path("dna.json")}"
+ chef_arguments << " #{@config.arguments}" if @config.arguments
+ chef_arguments << " --no-color" unless @is_ui_colored
+ chef_arguments.strip
+ end
+
+ def provisioning_path(file)
+ guest_friendly_path(File.join(@config.provisioning_path, file))
+ end
+
+ def guest_friendly_path(path)
+ return path unless @is_windows
+ path.gsub!("/", "\\")
+ path = "c:#{path}" if path.start_with?("\\")
+ path
+ end
end
end
end
diff --git a/plugins/provisioners/chef/command_builder_linux.rb b/plugins/provisioners/chef/command_builder_linux.rb
deleted file mode 100644
index d33ed667a..000000000
--- a/plugins/provisioners/chef/command_builder_linux.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module VagrantPlugins
- module Chef
- class CommandBuilderLinux < CommandBuilder
- def build_command
- if @client_type == :solo
- return build_command_solo
- else
- return build_command_client
- end
- end
-
- protected
-
- def build_command_client
- command_env = @config.binary_env ? "#{@config.binary_env} " : ""
- command_args = @config.arguments ? " #{@config.arguments}" : ""
-
- binary_path = "chef-client"
- binary_path ||= File.join(@config.binary_path, binary_path)
-
- return "#{command_env}#{binary_path} " +
- "-c #{@config.provisioning_path}/client.rb " +
- "-j #{@config.provisioning_path}/dna.json #{command_args}"
- end
-
- def build_command_solo
- options = [
- "-c #{@config.provisioning_path}/solo.rb",
- "-j #{@config.provisioning_path}/dna.json"
- ]
-
- if !@machine.env.ui.is_a?(Vagrant::UI::Colored)
- options << "--no-color"
- end
-
- command_env = @config.binary_env ? "#{@config.binary_env} " : ""
- command_args = @config.arguments ? " #{@config.arguments}" : ""
-
- binary_path = "chef-solo"
- binary_path ||= File.join(@config.binary_path, binary_path)
-
- return "#{command_env}#{binary_path} " +
- "#{options.join(" ")} #{command_args}"
- end
- end
- end
-end
diff --git a/plugins/provisioners/chef/command_builder_windows.rb b/plugins/provisioners/chef/command_builder_windows.rb
deleted file mode 100644
index 8bd4f972e..000000000
--- a/plugins/provisioners/chef/command_builder_windows.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require "tempfile"
-
-require "vagrant/util/template_renderer"
-
-module VagrantPlugins
- module Chef
- class CommandBuilderWindows < CommandBuilder
- def build_command
- binary_path = "chef-#{@client_type}"
- if @config.binary_path
- binary_path = File.join(@config.binary_path, binary_path)
- binary_path.gsub!("/", "\\")
- binary_path = "c:#{binary_path}" if binary_path.start_with?("\\")
- end
-
- chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}"
- chef_arguments << " -j #{provisioning_path("dna.json")}"
- chef_arguments << " #{@config.arguments}" if @config.arguments
-
- command_env = ""
- command_env = "#{@config.binary_env} " if @config.binary_env
-
- task_ps1_path = provisioning_path("cheftask.ps1")
-
- opts = {
- user: @machine.config.winrm.username,
- pass: @machine.config.winrm.password,
- chef_arguments: chef_arguments,
- chef_binary_path: "#{command_env}#{binary_path}",
- chef_stdout_log: provisioning_path("chef-#{@client_type}.log"),
- chef_stderr_log: provisioning_path("chef-#{@client_type}.err.log"),
- chef_task_exitcode: provisioning_path('cheftask.exitcode'),
- chef_task_running: provisioning_path('cheftask.running'),
- chef_task_ps1: task_ps1_path,
- chef_task_run_ps1: provisioning_path('cheftaskrun.ps1'),
- chef_task_xml: provisioning_path('cheftask.xml'),
- }
-
- # Upload the files we'll need
- render_and_upload(
- "cheftaskrun.ps1", opts[:chef_task_run_ps1], opts)
- render_and_upload(
- "cheftask.xml", opts[:chef_task_xml], opts)
- render_and_upload(
- "cheftask.ps1", opts[:chef_task_ps1], opts)
-
- return <<-EOH
- $old = Get-ExecutionPolicy;
- Set-ExecutionPolicy Unrestricted -force;
- #{task_ps1_path};
- Set-ExecutionPolicy $old -force
- EOH
- end
-
- protected
-
- def provisioning_path(file)
- path = "#{@config.provisioning_path}/#{file}"
- path.gsub!("/", "\\")
- path = "c:#{path}" if path.start_with?("\\")
- path
- end
-
- def render_and_upload(template, dest, opts)
- path = File.expand_path("../scripts/#{template}", __FILE__)
- data = Vagrant::Util::TemplateRenderer.render(path, options)
-
- file = Tempfile.new("vagrant-chef")
- file.binmode
- file.write(data)
- file.fsync
- file.close
-
- @machine.communicate.upload(file.path, dest)
- ensure
- if file
- file.close
- file.unlink
- end
- end
- end
- end
-end
diff --git a/plugins/provisioners/chef/plugin.rb b/plugins/provisioners/chef/plugin.rb
index 468f4b242..afe068047 100644
--- a/plugins/provisioners/chef/plugin.rb
+++ b/plugins/provisioners/chef/plugin.rb
@@ -6,8 +6,6 @@ module VagrantPlugins
module Chef
root = Pathname.new(File.expand_path("../", __FILE__))
autoload :CommandBuilder, root.join("command_builder")
- autoload :CommandBuilderLinux, root.join("command_builder_linux")
- autoload :CommandBuilderWindows, root.join("command_builder_windows")
class Plugin < Vagrant.plugin("2")
name "chef"
diff --git a/plugins/provisioners/chef/provisioner/base.rb b/plugins/provisioners/chef/provisioner/base.rb
index b1c071a6a..02a4dd52f 100644
--- a/plugins/provisioners/chef/provisioner/base.rb
+++ b/plugins/provisioners/chef/provisioner/base.rb
@@ -26,9 +26,7 @@ module VagrantPlugins
# This returns the command to run Chef for the given client
# type.
def build_command(client)
- builder_klass = CommandBuilderLinux
- builder_klass = CommandBuilderWindows if windows?
- builder = builder_klass.new(@machine, @config, client)
+ builder = CommandBuilder.new(@config, client, windows?, @machine.env.ui.is_a?(Vagrant::UI::Colored))
return builder.build_command
end
diff --git a/plugins/provisioners/chef/provisioner/chef_client.rb b/plugins/provisioners/chef/provisioner/chef_client.rb
index 1de5f5d16..935cec6bf 100644
--- a/plugins/provisioners/chef/provisioner/chef_client.rb
+++ b/plugins/provisioners/chef/provisioner/chef_client.rb
@@ -58,6 +58,10 @@ module VagrantPlugins
@machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
end
+ if @machine.guest.capability?(:wait_for_reboot)
+ @machine.guest.capability(:wait_for_reboot)
+ end
+
if windows?
# This re-establishes our symbolic links if they were
# created between now and a reboot
@@ -75,7 +79,8 @@ module VagrantPlugins
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_client_again")
end
- exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data|
+ opts = { error_check: false, elevated: true }
+ exit_status = @machine.communicate.sudo(command, opts) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
diff --git a/plugins/provisioners/chef/provisioner/chef_solo.rb b/plugins/provisioners/chef/provisioner/chef_solo.rb
index 99037c1f0..db926e11c 100644
--- a/plugins/provisioners/chef/provisioner/chef_solo.rb
+++ b/plugins/provisioners/chef/provisioner/chef_solo.rb
@@ -134,6 +134,10 @@ module VagrantPlugins
@machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
end
+ if @machine.guest.capability?(:wait_for_reboot)
+ @machine.guest.capability(:wait_for_reboot)
+ end
+
if windows?
# This re-establishes our symbolic links if they were
# created between now and a reboot
@@ -151,7 +155,8 @@ module VagrantPlugins
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again")
end
- exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data|
+ opts = { error_check: false, elevated: true }
+ exit_status = @machine.communicate.sudo(command, opts) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
diff --git a/plugins/provisioners/chef/scripts/cheftask.ps1.erb b/plugins/provisioners/chef/scripts/cheftask.ps1.erb
deleted file mode 100644
index f025425ff..000000000
--- a/plugins/provisioners/chef/scripts/cheftask.ps1.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-# kill the task so we can recreate it
-schtasks /delete /tn "chef-solo" /f 2>&1 | out-null
-
-# Ensure the chef task running file doesn't exist from a previous failure
-if (Test-Path "<%= options[:chef_task_running] %>") {
- del "<%= options[:chef_task_running] %>"
-}
-
-# schedule the task to run once in the far distant future
-schtasks /create /tn 'chef-solo' /xml '<%= options[:chef_task_xml] %>' /ru '<%= options[:user] %>' /rp '<%= options[:pass] %>' | Out-Null
-
-# start the scheduled task right now
-schtasks /run /tn "chef-solo" | Out-Null
-
-# wait for run_chef.ps1 to start or timeout after 1 minute
-$timeoutSeconds = 60
-$elapsedSeconds = 0
-while ( (!(Test-Path "<%= options[:chef_task_running] %>")) -and ($elapsedSeconds -lt $timeoutSeconds) ) {
- Start-Sleep -s 1
- $elapsedSeconds++
-}
-
-if ($elapsedSeconds -ge $timeoutSeconds) {
- Write-Error "Timed out waiting for chef scheduled task to start"
- exit -2
-}
-
-# read the entire file, but only write out new lines we haven't seen before
-$numLinesRead = 0
-$success = $TRUE
-while (Test-Path "<%= options[:chef_task_running] %>") {
- Start-Sleep -m 100
-
- if (Test-Path "<%= options[:chef_stdout_log] %>") {
- $text = (get-content "<%= options[:chef_stdout_log] %>")
- $numLines = ($text | Measure-Object -line).lines
- $numLinesToRead = $numLines - $numLinesRead
-
- if ($numLinesToRead -gt 0) {
- $text | select -first $numLinesToRead -skip $numLinesRead | ForEach {
- Write-Host "$_"
- }
- $numLinesRead += $numLinesToRead
- }
- }
-}
-
-exit Get-Content "<%= options[:chef_task_exitcode] %>"
diff --git a/plugins/provisioners/chef/scripts/cheftask.xml.erb b/plugins/provisioners/chef/scripts/cheftask.xml.erb
deleted file mode 100644
index 6b598ec4c..000000000
--- a/plugins/provisioners/chef/scripts/cheftask.xml.erb
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
- 2013-06-21T22:41:43
- Administrator
-
-
-
- 2045-01-01T12:00:00
- true
-
-
-
-
- vagrant
- Password
- HighestAvailable
-
-
-
- IgnoreNew
- false
- false
- true
- false
- false
-
- true
- false
-
- true
- true
- false
- false
- false
- PT2H
- 4
-
-
-
- powershell
- -file <%= options[:chef_task_run_ps1] %>
-
-
-
diff --git a/plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb b/plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb
deleted file mode 100644
index 0d4a43bf4..000000000
--- a/plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb
+++ /dev/null
@@ -1,18 +0,0 @@
-$exitCode = -1
-Set-ExecutionPolicy Unrestricted -force;
-
-Try
-{
- "running" | Out-File "<%= options[:chef_task_running] %>"
- $process = (Start-Process "<%= options[:chef_binary_path] %>" -ArgumentList "<%= options[:chef_arguments] %>" -NoNewWindow -PassThru -Wait -RedirectStandardOutput "<%= options[:chef_stdout_log] %>" -RedirectStandardError "<%= options[:chef_stderr_log] %>")
- $exitCode = $process.ExitCode
-}
-Finally
-{
- $exitCode | Out-File "<%= options[:chef_task_exitcode] %>"
- if (Test-Path "<%= options[:chef_task_running] %>") {
- del "<%= options[:chef_task_running] %>"
- }
-}
-
-exit $exitCode
diff --git a/test/unit/plugins/communicators/winrm/communicator_test.rb b/test/unit/plugins/communicators/winrm/communicator_test.rb
index 84a4e8bd2..d6013be7c 100644
--- a/test/unit/plugins/communicators/winrm/communicator_test.rb
+++ b/test/unit/plugins/communicators/winrm/communicator_test.rb
@@ -17,6 +17,11 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
end
end
+ before do
+ allow(shell).to receive(:username).and_return('vagrant')
+ allow(shell).to receive(:password).and_return('password')
+ end
+
describe ".ready?" do
it "returns true if hostname command executes without error" do
expect(shell).to receive(:powershell).with("hostname").and_return({ exitcode: 0 })
@@ -42,6 +47,16 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
expect(subject.execute("dir")).to eq(0)
end
+ it "wraps command in elevated shell script when elevated is true" do
+ expect(shell).to receive(:powershell) do |cmd|
+ expect(cmd).to include("$command = \"dir\"")
+ expect(cmd).to include("$user = 'vagrant'")
+ expect(cmd).to include("$password = 'password'")
+ expect(cmd).to include("New-Object -ComObject \"Schedule.Service\"")
+ end.and_return({ exitcode: 0 })
+ expect(subject.execute("dir", { elevated: true })).to eq(0)
+ end
+
it "can use cmd shell" do
expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 })
expect(subject.execute("dir", { :shell => :cmd })).to eq(0)
diff --git a/test/unit/plugins/provisioners/chef/command_builder_spec.rb b/test/unit/plugins/provisioners/chef/command_builder_spec.rb
new file mode 100644
index 000000000..3fb53693b
--- /dev/null
+++ b/test/unit/plugins/provisioners/chef/command_builder_spec.rb
@@ -0,0 +1,105 @@
+require_relative "../../../base"
+
+require Vagrant.source_root.join("plugins/provisioners/chef/command_builder")
+
+describe VagrantPlugins::Chef::CommandBuilder do
+
+ let(:machine) { double("machine") }
+ let(:chef_config) { double("chef_config") }
+
+ before(:each) do
+ allow(chef_config).to receive(:provisioning_path).and_return('/tmp/vagrant-chef-1')
+ allow(chef_config).to receive(:arguments).and_return(nil)
+ allow(chef_config).to receive(:binary_env).and_return(nil)
+ allow(chef_config).to receive(:binary_path).and_return(nil)
+ allow(chef_config).to receive(:binary_env).and_return(nil)
+ end
+
+ describe '.initialize' do
+ it 'should raise when chef type is not client or solo' do
+ expect { VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client_bad) }.
+ to raise_error
+ end
+ end
+
+ describe 'build_command' do
+ describe 'windows' do
+ subject do
+ VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client, true)
+ end
+
+ it "executes the chef-client in PATH by default" do
+ expect(subject.build_command()).to match(/^chef-client/)
+ end
+
+ it "executes the chef-client using full path if binary_path is specified" do
+ allow(chef_config).to receive(:binary_path).and_return(
+ "c:\\opscode\\chef\\bin\\chef-client")
+ expect(subject.build_command()).to match(/^c:\\opscode\\chef\\bin\\chef-client\\chef-client/)
+ end
+
+ it "builds a guest friendly client.rb path" do
+ expect(subject.build_command()).to include(
+ '-c c:\\tmp\\vagrant-chef-1\\client.rb')
+ end
+
+ it "builds a guest friendly solo.json path" do
+ expect(subject.build_command()).to include(
+ '-j c:\\tmp\\vagrant-chef-1\\dna.json')
+ end
+
+ it 'includes Chef arguments if specified' do
+ allow(chef_config).to receive(:arguments).and_return("-l DEBUG")
+ expect(subject.build_command()).to include(
+ '-l DEBUG')
+ end
+
+ it 'includes --no-color if UI is not colored' do
+ expect(subject.build_command()).to include(
+ ' --no-color')
+ end
+ end
+
+ describe 'linux' do
+ subject do
+ VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client, false)
+ end
+
+ it "executes the chef-client in PATH by default" do
+ expect(subject.build_command()).to match(/^chef-client/)
+ end
+
+ it "executes the chef-client using full path if binary_path is specified" do
+ allow(chef_config).to receive(:binary_path).and_return(
+ "/opt/chef/chef-client")
+ expect(subject.build_command()).to match(/^\/opt\/chef\/chef-client/)
+ end
+
+ it "builds a guest friendly client.rb path" do
+ expect(subject.build_command()).to include(
+ '-c /tmp/vagrant-chef-1/client.rb')
+ end
+
+ it "builds a guest friendly solo.json path" do
+ expect(subject.build_command()).to include(
+ '-j /tmp/vagrant-chef-1/dna.json')
+ end
+
+ it 'includes Chef arguments if specified' do
+ allow(chef_config).to receive(:arguments).and_return("-l DEBUG")
+ expect(subject.build_command()).to include(
+ '-l DEBUG')
+ end
+
+ it 'includes --no-color if UI is not colored' do
+ expect(subject.build_command()).to include(
+ ' --no-color')
+ end
+
+ it 'includes environment variables if specified' do
+ allow(chef_config).to receive(:binary_env).and_return("ENVVAR=VAL")
+ expect(subject.build_command()).to match(/^ENVVAR=VAL /)
+ end
+ end
+ end
+end