Merge pull request #8102 from mwrock/winrmv2

Refactor winrm communicator to use latest winrm gems and v2 api
This commit is contained in:
Chris Roberts 2017-01-05 10:48:44 -08:00 committed by GitHub
commit c11534e13c
9 changed files with 143 additions and 234 deletions

View File

@ -142,10 +142,10 @@ module VagrantPlugins
interactive: false, interactive: false,
}.merge(opts || {}) }.merge(opts || {})
opts[:shell] = :elevated if opts[:elevated]
opts[:good_exit] = Array(opts[:good_exit]) opts[:good_exit] = Array(opts[:good_exit])
command = wrap_in_scheduled_task(command, opts[:interactive]) if opts[:elevated]
@logger.debug("#{opts[:shell]} executing:\n#{command}") @logger.debug("#{opts[:shell]} executing:\n#{command}")
output = shell.send(opts[:shell], command, &block) output = shell.send(opts[:shell], command, opts, &block)
execution_output(output, opts) execution_output(output, opts)
end end
alias_method :sudo, :execute alias_method :sudo, :execute
@ -165,7 +165,7 @@ module VagrantPlugins
# If we're passed a *nix command which PS can't parse we get exit code # If we're passed a *nix command which PS can't parse we get exit code
# 0, but output in stderr. We need to check both exit code and stderr. # 0, but output in stderr. We need to check both exit code and stderr.
output = shell.send(:powershell, command) output = shell.send(:powershell, command)
return output[:exitcode] == 0 && flatten_stderr(output).length == 0 return output.exitcode == 0 && output.stderr.length == 0
end end
def upload(from, to) def upload(from, to)
@ -180,7 +180,7 @@ module VagrantPlugins
protected protected
# This creates anew WinRMShell based on the information we know # This creates a new WinRMShell based on the information we know
# about this machine. # about this machine.
def create_shell def create_shell
winrm_info = Helper.winrm_info(@machine) winrm_info = Helper.winrm_info(@machine)
@ -192,55 +192,23 @@ module VagrantPlugins
) )
end end
# Creates and uploads a PowerShell script which wraps a command in a
# scheduled task. The scheduled task allows commands to run on the guest
# as a true local admin without any of the restrictions that WinRM puts
# in place.
#
# @return The wrapper command to execute
def wrap_in_scheduled_task(command, interactive)
path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__)
script = Vagrant::Util::TemplateRenderer.render(path, options: {
interactive: interactive,
})
guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1"
Tempfile.open(["vagrant-elevated-shell", "ps1"]) do |f|
f.binmode
f.write(script)
f.fsync
f.close
upload(f.path, guest_script_path)
end
# Convert to double byte unicode string then base64 encode
# just like PowerShell -EncodedCommand expects.
# Suppress the progress stream from leaking to stderr.
wrapped_encoded_command = Base64.strict_encode64(
"$ProgressPreference='SilentlyContinue'; #{command}; exit $LASTEXITCODE".encode('UTF-16LE', 'UTF-8'))
"powershell -executionpolicy bypass -file '#{guest_script_path}' " +
"-username '#{shell.username}' -password '#{shell.password}' " +
"-encoded_command '#{wrapped_encoded_command}' " +
"-execution_time_limit '#{shell.execution_time_limit}'"
end
# Handles the raw WinRM shell result and converts it to a # Handles the raw WinRM shell result and converts it to a
# standard Vagrant communicator result # standard Vagrant communicator result
def execution_output(output, opts) def execution_output(output, opts)
if opts[:shell] == :wql if opts[:shell] == :wql
return output return output
elsif opts[:error_check] && \ elsif opts[:error_check] && \
!opts[:good_exit].include?(output[:exitcode]) !opts[:good_exit].include?(output.exitcode)
raise_execution_error(output, opts) raise_execution_error(output, opts)
end end
output[:exitcode] output.exitcode
end end
def raise_execution_error(output, opts) def raise_execution_error(output, opts)
# WinRM can return multiple stderr and stdout entries # WinRM can return multiple stderr and stdout entries
error_opts = opts.merge( error_opts = opts.merge(
stdout: flatten_stdout(output), stdout: output.stdout,
stderr: flatten_stderr(output) stderr: output.stderr
) )
# Use a different error message key if the caller gave us one, # Use a different error message key if the caller gave us one,
@ -250,20 +218,6 @@ module VagrantPlugins
# Raise the error, use the type the caller gave us or the comm default # Raise the error, use the type the caller gave us or the comm default
raise opts[:error_class], error_opts raise opts[:error_class], error_opts
end end
# TODO: Replace with WinRM Output class when WinRM 1.3 is released
def flatten_stderr(output)
output[:data].map do | line |
line[:stderr]
end.compact.join
end
def flatten_stdout(output)
output[:data].map do | line |
line[:flatten_stdout]
end.compact.join
end
end #WinRM class end #WinRM class
end end
end end

View File

@ -13,6 +13,7 @@ module VagrantPlugins
attr_accessor :ssl_peer_verification attr_accessor :ssl_peer_verification
attr_accessor :execution_time_limit attr_accessor :execution_time_limit
attr_accessor :basic_auth_only attr_accessor :basic_auth_only
attr_accessor :codepage
def initialize def initialize
@username = UNSET_VALUE @username = UNSET_VALUE
@ -27,6 +28,7 @@ module VagrantPlugins
@ssl_peer_verification = UNSET_VALUE @ssl_peer_verification = UNSET_VALUE
@execution_time_limit = UNSET_VALUE @execution_time_limit = UNSET_VALUE
@basic_auth_only = UNSET_VALUE @basic_auth_only = UNSET_VALUE
@codepage = UNSET_VALUE
end end
def finalize! def finalize!
@ -43,6 +45,7 @@ module VagrantPlugins
@ssl_peer_verification = true if @ssl_peer_verification == UNSET_VALUE @ssl_peer_verification = true if @ssl_peer_verification == UNSET_VALUE
@execution_time_limit = "PT2H" if @execution_time_limit == UNSET_VALUE @execution_time_limit = "PT2H" if @execution_time_limit == UNSET_VALUE
@basic_auth_only = false if @basic_auth_only == UNSET_VALUE @basic_auth_only = false if @basic_auth_only == UNSET_VALUE
@codepage = nil if @codepage == UNSET_VALUE
end end
def validate(machine) def validate(machine)

View File

@ -1,101 +0,0 @@
param([String]$username, [String]$password, [String]$encoded_command, [String]$execution_time_limit)
# Try to get the Schedule.Service object. If it fails, we are probably
# on an older version of Windows. On old versions, we can just execute
# directly since priv. escalation isn't a thing.
$schedule = $null
Try {
$schedule = New-Object -ComObject "Schedule.Service"
} Catch [System.Management.Automation.PSArgumentException] {
powershell.exe -EncodedCommand $encoded_command
exit $LASTEXITCODE
}
$ProgressPreference = "SilentlyContinue"
$task_name = "WinRM_Elevated_Shell"
$out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log"
if (Test-Path $out_file) {
del $out_file
}
$task_xml = @'
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Principals>
<Principal id="Author">
<UserId>{username}</UserId>
<LogonType><%= options[:interactive] ? 'InteractiveTokenOrPassword' : 'Password' %></LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>{execution_time_limit}</ExecutionTimeLimit>
<Priority>4</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>cmd</Command>
<Arguments>{arguments}</Arguments>
</Exec>
</Actions>
</Task>
'@
$arguments = "/c powershell.exe -EncodedCommand $encoded_command &gt; $out_file 2&gt;&amp;1"
$task_xml = $task_xml.Replace("{arguments}", $arguments)
$task_xml = $task_xml.Replace("{username}", $username)
$task_xml = $task_xml.Replace("{execution_time_limit}", $execution_time_limit)
$schedule.Connect()
$task = $schedule.NewTask($null)
$task.XmlText = $task_xml
$folder = $schedule.GetFolder("\")
$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, <%= options[:interactive] ? 3 : 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++
}
function SlurpOutput($out_file, $cur_line) {
if (Test-Path $out_file) {
get-content $out_file | select -skip $cur_line | ForEach {
$cur_line += 1
Write-Host "$_"
}
}
return $cur_line
}
$cur_line = 0
do {
Start-Sleep -m 100
$cur_line = SlurpOutput $out_file $cur_line
} while (!($registered_task.state -eq 3))
$exit_code = $registered_task.LastTaskResult
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null
exit $exit_code

View File

@ -9,6 +9,7 @@ Vagrant::Util::SilenceWarnings.silence! do
require "winrm" require "winrm"
end end
require "winrm-elevated"
require "winrm-fs" require "winrm-fs"
module VagrantPlugins module VagrantPlugins
@ -53,48 +54,55 @@ module VagrantPlugins
@config = config @config = config
end end
def powershell(command, &block) def powershell(command, opts = {}, &block)
# Ensure an exit code connection.shell(:powershell) do |shell|
command += "\r\nif ($?) { exit 0 } else { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }" execute_with_rescue(shell, command, &block)
session.create_executor do |executor|
execute_with_rescue(executor.method("run_powershell_script"), command, &block)
end end
end end
def cmd(command, &block) def cmd(command, opts = {}, &block)
session.create_executor do |executor| shell_opts = {}
execute_with_rescue(executor.method("run_cmd"), command, &block) shell_opts[:codepage] = @config.codepage if @config.codepage
connection.shell(:cmd, shell_opts) do |shell|
execute_with_rescue(shell, command, &block)
end end
end end
def wql(query, &block) def elevated(command, opts = {}, &block)
connection.shell(:elevated) do |shell|
shell.interactive_logon = opts[:interactive] || false
execute_with_rescue(shell, command, &block)
end
end
def wql(query, opts = {}, &block)
retryable(tries: @config.max_tries, on: @@exceptions_to_retry_on, sleep: @config.retry_delay) do retryable(tries: @config.max_tries, on: @@exceptions_to_retry_on, sleep: @config.retry_delay) do
handle_output(session.method("run_wql"), query, &block) connection.run_wql(query)
end end
rescue => e rescue => e
raise_winrm_exception(e, "run_wql", query) raise_winrm_exception(e, "run_wql", query)
end end
def upload(from, to) def upload(from, to)
file_manager = WinRM::FS::FileManager.new(session) file_manager = WinRM::FS::FileManager.new(connection)
file_manager.upload(from, to) file_manager.upload(from, to)
end end
def download(from, to) def download(from, to)
file_manager = WinRM::FS::FileManager.new(session) file_manager = WinRM::FS::FileManager.new(connection)
file_manager.download(from, to) file_manager.download(from, to)
end end
protected protected
def execute_with_rescue(method, command, &block) def execute_with_rescue(shell, command, &block)
handle_output(method, command, &block) handle_output(shell, command, &block)
rescue => e rescue => e
raise_winrm_exception(e, method.name, command) raise_winrm_exception(e, shell.class.name.split("::").last, command)
end end
def handle_output(execute_method, command, &block) def handle_output(shell, command, &block)
output = execute_method.call(command) do |out, err| output = shell.run(command) do |out, err|
block.call(:stdout, out) if block_given? && out block.call(:stdout, out) if block_given? && out
block.call(:stderr, err) if block_given? && err block.call(:stderr, err) if block_given? && err
end end
@ -104,14 +112,10 @@ module VagrantPlugins
# Verify that we didn't get a parser error, and if so we should # Verify that we didn't get a parser error, and if so we should
# set the exit code to 1. Parse errors return exit code 0 so we # set the exit code to 1. Parse errors return exit code 0 so we
# need to do this. # need to do this.
if output[:exitcode] == 0 if output.exitcode == 0
(output[:data] || []).each do |data| if output.stderr.include?("ParserError")
next if !data[:stderr] @logger.warn("Detected ParserError, setting exit code to 1")
if data[:stderr].include?("ParserError") output.exitcode = 1
@logger.warn("Detected ParserError, setting exit code to 1")
output[:exitcode] = 1
break
end
end end
end end
@ -159,21 +163,20 @@ module VagrantPlugins
end end
end end
def new_session def new_connection
@logger.info("Attempting to connect to WinRM...") @logger.info("Attempting to connect to WinRM...")
@logger.info(" - Host: #{@host}") @logger.info(" - Host: #{@host}")
@logger.info(" - Port: #{@port}") @logger.info(" - Port: #{@port}")
@logger.info(" - Username: #{@config.username}") @logger.info(" - Username: #{@config.username}")
@logger.info(" - Transport: #{@config.transport}") @logger.info(" - Transport: #{@config.transport}")
client = ::WinRM::WinRMWebService.new(endpoint, @config.transport.to_sym, endpoint_options) client = ::WinRM::Connection.new(endpoint_options)
client.set_timeout(@config.timeout)
client.logger = @logger client.logger = @logger
client client
end end
def session def connection
@session ||= new_session @connection ||= new_connection
end end
def endpoint def endpoint
@ -188,8 +191,11 @@ module VagrantPlugins
end end
def endpoint_options def endpoint_options
{ user: @username, { endpoint: endpoint,
pass: @password, transport: @config.transport,
operation_timeout: @config.timeout,
user: @username,
password: @password,
host: @host, host: @host,
port: @port, port: @port,
basic_auth_only: @config.basic_auth_only, basic_auth_only: @config.basic_auth_only,

View File

@ -28,7 +28,7 @@ module VagrantPlugins
args << "Bypass" args << "Bypass"
args << "-NoExit" args << "-NoExit"
args << "-EncodedCommand" args << "-EncodedCommand"
args << ::WinRM::PowershellScript.new(command).encoded args << encoded(command)
if ps_info[:extra_args] if ps_info[:extra_args]
args << ps_info[:extra_args] args << ps_info[:extra_args]
end end
@ -36,6 +36,11 @@ module VagrantPlugins
# Launch it # Launch it
Vagrant::Util::SafeExec.exec("powershell", *args) Vagrant::Util::SafeExec.exec("powershell", *args)
end end
def self.encoded(script)
encoded_script = script.encode('UTF-16LE', 'UTF-8')
Base64.strict_encode64(encoded_script)
end
end end
end end
end end

View File

@ -11,7 +11,9 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
let(:ui) { double("ui") } let(:ui) { double("ui") }
let(:machine) { double("machine", config: config, provider: provider, ui: ui) } let(:machine) { double("machine", config: config, provider: provider, ui: ui) }
let(:shell) { double("shell") } let(:shell) { double("shell") }
let(:good_output) { WinRM::Output.new.tap { |out| out.exitcode = 0 } }
let(:bad_output) { WinRM::Output.new.tap { |out| out.exitcode = 1 } }
subject do subject do
described_class.new(machine).tap do |comm| described_class.new(machine).tap do |comm|
allow(comm).to receive(:create_shell).and_return(shell) allow(comm).to receive(:create_shell).and_return(shell)
@ -81,61 +83,47 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
describe ".execute" do describe ".execute" do
it "defaults to running in powershell" do it "defaults to running in powershell" do
expect(shell).to receive(:powershell).with(kind_of(String)).and_return({ exitcode: 0 }) expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(good_output)
expect(subject.execute("dir")).to eq(0) expect(subject.execute("dir")).to eq(0)
end end
it "wraps command in elevated shell script when elevated is true" do it "use elevated shell script when elevated is true" do
expect(shell).to receive(:upload).with(kind_of(String), "c:/tmp/vagrant-elevated-shell.ps1") expect(shell).to receive(:elevated).and_return(good_output)
expect(shell).to receive(:powershell) do |cmd|
expect(cmd).to eq("powershell -executionpolicy bypass -file 'c:/tmp/vagrant-elevated-shell.ps1' " +
"-username 'vagrant' -password 'password' -encoded_command 'JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQA9ACcAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAnADsAIABkAGkAcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA' -execution_time_limit 'PT2H'")
end.and_return({ exitcode: 0 })
expect(subject.execute("dir", { elevated: true })).to eq(0) expect(subject.execute("dir", { elevated: true })).to eq(0)
end end
it "wraps command in elevated and interactive shell script when elevated and interactive are true" do
expect(shell).to receive(:upload).with(kind_of(String), "c:/tmp/vagrant-elevated-shell.ps1")
expect(shell).to receive(:powershell) do |cmd|
expect(cmd).to eq("powershell -executionpolicy bypass -file 'c:/tmp/vagrant-elevated-shell.ps1' " +
"-username 'vagrant' -password 'password' -encoded_command 'JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQA9ACcAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAnADsAIABkAGkAcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA' -execution_time_limit 'PT2H'")
end.and_return({ exitcode: 0 })
expect(subject.execute("dir", { elevated: true, interactive: true })).to eq(0)
end
it "can use cmd shell" do it "can use cmd shell" do
expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 }) expect(shell).to receive(:cmd).with(kind_of(String), kind_of(Hash)).and_return(good_output)
expect(subject.execute("dir", { shell: :cmd })).to eq(0) expect(subject.execute("dir", { shell: :cmd })).to eq(0)
end end
it "raises error when error_check is true and exit code is non-zero" do it "raises error when error_check is true and exit code is non-zero" do
expect(shell).to receive(:powershell).with(kind_of(String)).and_return( expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(bad_output)
{ exitcode: 1, data: [{ stdout: '', stderr: '' }] })
expect { subject.execute("dir") }.to raise_error( expect { subject.execute("dir") }.to raise_error(
VagrantPlugins::CommunicatorWinRM::Errors::WinRMBadExitStatus) VagrantPlugins::CommunicatorWinRM::Errors::WinRMBadExitStatus)
end end
it "does not raise error when error_check is false and exit code is non-zero" do it "does not raise error when error_check is false and exit code is non-zero" do
expect(shell).to receive(:powershell).with(kind_of(String)).and_return({ exitcode: 1 }) expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(bad_output)
expect(subject.execute("dir", { error_check: false })).to eq(1) expect(subject.execute("dir", { error_check: false })).to eq(1)
end end
end end
describe ".test" do describe ".test" do
it "returns true when exit code is zero" do it "returns true when exit code is zero" do
output = { exitcode: 0, data:[{ stderr: '' }] } expect(shell).to receive(:powershell).with(kind_of(String)).and_return(good_output)
expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output)
expect(subject.test("test -d c:/windows")).to be_true expect(subject.test("test -d c:/windows")).to be_true
end end
it "returns false when exit code is non-zero" do it "returns false when exit code is non-zero" do
output = { exitcode: 1, data:[{ stderr: '' }] } expect(shell).to receive(:powershell).with(kind_of(String)).and_return(bad_output)
expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output)
expect(subject.test("test -d /tmp/foobar")).to be_false expect(subject.test("test -d /tmp/foobar")).to be_false
end end
it "returns false when stderr contains output" do it "returns false when stderr contains output" do
output = { exitcode: 0, data:[{ stderr: 'this is an error' }] } output = WinRM::Output.new
output.exitcode = 1
output << { stderr: 'this is an error' }
expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output) expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output)
expect(subject.test("[-x stuff] && foo")).to be_false expect(subject.test("[-x stuff] && foo")).to be_false
end end

View File

@ -6,8 +6,8 @@ require Vagrant.source_root.join("plugins/communicators/winrm/config")
describe VagrantPlugins::CommunicatorWinRM::WinRMShell do describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
include_context "unit" include_context "unit"
let(:session) { double("winrm_session") } let(:connection) { double("winrm_connection") }
let(:executor) { double("command_executor") } let(:shell) { double("winrm_shell") }
let(:port) { config.transport == :ssl ? 5986 : 5985 } let(:port) { config.transport == :ssl ? 5986 : 5985 }
let(:config) { let(:config) {
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c| VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
@ -21,44 +21,82 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
c.finalize! c.finalize!
end end
} }
let(:output) { WinRM::Output.new.tap { |out| out.exitcode = 0 } }
before { allow(session).to receive(:create_executor).and_yield(executor) } before { allow(connection).to receive(:shell).and_yield(shell) }
subject do subject do
described_class.new('localhost', port, config).tap do |comm| described_class.new('localhost', port, config).tap do |comm|
allow(comm).to receive(:new_session).and_return(session) allow(comm).to receive(:new_connection).and_return(connection)
end end
end end
describe ".powershell" do describe ".powershell" do
it "should call winrm powershell" do it "should call winrm powershell" do
expect(executor).to receive(:run_powershell_script).with(/^dir.+/).and_return({ exitcode: 0 }) expect(shell).to receive(:run).with("dir").and_return(output)
expect(subject.powershell("dir")[:exitcode]).to eq(0) expect(subject.powershell("dir").exitcode).to eq(0)
end end
it "should raise an execution error when an exception occurs" do it "should raise an execution error when an exception occurs" do
expect(executor).to receive(:run_powershell_script).with(/^dir.+/).and_raise( expect(shell).to receive(:run).with("dir").and_raise(
StandardError.new("Oh no! a 500 SOAP error!")) StandardError.new("Oh no! a 500 SOAP error!"))
expect { subject.powershell("dir") }.to raise_error( expect { subject.powershell("dir") }.to raise_error(
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError) VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
end end
end end
describe ".cmd" do describe ".elevated" do
it "should call winrm elevated" do
expect(shell).to receive(:run).with("dir").and_return(output)
expect(shell).to receive(:interactive_logon=).with(false)
expect(subject.elevated("dir").exitcode).to eq(0)
end
it "should set interactive_logon when interactive is true" do
expect(shell).to receive(:run).with("dir").and_return(output)
expect(shell).to receive(:interactive_logon=).with(true)
expect(subject.elevated("dir", { interactive: true }).exitcode).to eq(0)
end
it "should raise an execution error when an exception occurs" do
expect(shell).to receive(:run).with("dir").and_raise(
StandardError.new("Oh no! a 500 SOAP error!"))
expect { subject.powershell("dir") }.to raise_error(
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
end
end
describe ".cmd" do
it "should call winrm cmd" do it "should call winrm cmd" do
expect(executor).to receive(:run_cmd).with("dir").and_return({ exitcode: 0 }) expect(connection).to receive(:shell).with(:cmd, { })
expect(subject.cmd("dir")[:exitcode]).to eq(0) expect(shell).to receive(:run).with("dir").and_return(output)
expect(subject.cmd("dir").exitcode).to eq(0)
end
context "when codepage is given" do
let(:config) {
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
c.codepage = 800
c.finalize!
end
}
it "creates shell with the given codepage when set" do
expect(connection).to receive(:shell).with(:cmd, { codepage: 800 })
expect(shell).to receive(:run).with("dir").and_return(output)
expect(subject.cmd("dir").exitcode).to eq(0)
end
end end
end end
describe ".wql" do describe ".wql" do
it "should call winrm wql" do it "should call winrm wql" do
expect(session).to receive(:run_wql).with("select * from Win32_OperatingSystem").and_return({ exitcode: 0 }) expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem")
expect(subject.wql("select * from Win32_OperatingSystem")[:exitcode]).to eq(0) subject.wql("select * from Win32_OperatingSystem")
end end
it "should retry when a WinRMAuthorizationError is received" do it "should retry when a WinRMAuthorizationError is received" do
expect(session).to receive(:run_wql).with("select * from Win32_OperatingSystem").exactly(2).times.and_raise( expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem").exactly(2).times.and_raise(
# Note: The initialize for WinRMAuthorizationError may require a status_code as # Note: The initialize for WinRMAuthorizationError may require a status_code as
# the second argument in a future WinRM release. Currently it doesn't track the # the second argument in a future WinRM release. Currently it doesn't track the
# status code. # status code.
@ -104,9 +142,10 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
describe ".endpoint_options" do describe ".endpoint_options" do
it "should create endpoint options" do it "should create endpoint options" do
expect(subject.send(:endpoint_options)).to eq( expect(subject.send(:endpoint_options)).to eq(
{ user: "username", pass: "password", host: "localhost", port: 5985, { endpoint: "http://localhost:5985/wsman", operation_timeout: 1800,
user: "username", password: "password", host: "localhost", port: 5985,
basic_auth_only: false, no_ssl_peer_verification: false, basic_auth_only: false, no_ssl_peer_verification: false,
retry_delay: 1, retry_limit: 2 }) retry_delay: 1, retry_limit: 2, transport: :negotiate })
end end
end end

View File

@ -28,8 +28,9 @@ Gem::Specification.new do |s|
s.add_dependency "rb-kqueue", "~> 0.2.0" s.add_dependency "rb-kqueue", "~> 0.2.0"
s.add_dependency "rest-client", ">= 1.6.0", "< 3.0" s.add_dependency "rest-client", ">= 1.6.0", "< 3.0"
s.add_dependency "wdm", "~> 0.1.0" s.add_dependency "wdm", "~> 0.1.0"
s.add_dependency "winrm", "~> 1.6" s.add_dependency "winrm", "~> 2.1"
s.add_dependency "winrm-fs", "~> 0.3.0" s.add_dependency "winrm-fs", "~> 1.0"
s.add_dependency "winrm-elevated", "~> 1.1"
# We lock this down to avoid compilation issues. # We lock this down to avoid compilation issues.
s.add_dependency "nokogiri", "= 1.6.7.1" s.add_dependency "nokogiri", "= 1.6.7.1"

View File

@ -59,6 +59,20 @@ to use port 4567 to talk to the guest if there is no other option.
<hr> <hr>
`config.winrm.execution_time_limit` - The maximum duration that a WinRM `config.winrm.ssl_peer_verification` - When set to `false` ssl certificate validation is not performed.
task can execute for. This defaults to two hours. The format of this value
must be in this [Microsoft-documented format](https://msdn.microsoft.com/en-us/library/aa382678.aspx). <hr>
`config.winrm.timeout` - The maximum amount of time to wait for a response from the endpoint. This defaults to 60 seconds. Note that this will not "timeout" commands that exceed this amount of time to process, it just requires the endpoint to report the status of the command before the given amount of time passes.
<hr>
`config.winrm.retry_limit` - The maximum number of times to retry opening a shell after failure. This defaults to 3.
<hr>
`config.winrm.retry_delay` - The amount of time to wait between retries and defaults to 10 seconds.
<hr>
`config.winrm.codepage` - The WINRS_CODEPAGE which is the client's console output code page. The default is 65001 (UTF-8). <strong>Note:</strong> Versions of Windows older than Windows 7/Server 2008 R2 may exhibit undesirable behavior using the default UTF-8 codepage. When using these older versions of Windows, its best to use the native code page of the server's locale. For example, en-US servers will have a codepage of 437. The Windows `chcp` command can be used to determine the value of the native codepage.