Merge pull request #8102 from mwrock/winrmv2
Refactor winrm communicator to use latest winrm gems and v2 api
This commit is contained in:
commit
c11534e13c
|
@ -142,10 +142,10 @@ module VagrantPlugins
|
|||
interactive: false,
|
||||
}.merge(opts || {})
|
||||
|
||||
opts[:shell] = :elevated if opts[:elevated]
|
||||
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}")
|
||||
output = shell.send(opts[:shell], command, &block)
|
||||
output = shell.send(opts[:shell], command, opts, &block)
|
||||
execution_output(output, opts)
|
||||
end
|
||||
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
|
||||
# 0, but output in stderr. We need to check both exit code and stderr.
|
||||
output = shell.send(:powershell, command)
|
||||
return output[:exitcode] == 0 && flatten_stderr(output).length == 0
|
||||
return output.exitcode == 0 && output.stderr.length == 0
|
||||
end
|
||||
|
||||
def upload(from, to)
|
||||
|
@ -180,7 +180,7 @@ module VagrantPlugins
|
|||
|
||||
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.
|
||||
def create_shell
|
||||
winrm_info = Helper.winrm_info(@machine)
|
||||
|
@ -192,55 +192,23 @@ module VagrantPlugins
|
|||
)
|
||||
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
|
||||
# standard Vagrant communicator result
|
||||
def execution_output(output, opts)
|
||||
if opts[:shell] == :wql
|
||||
return output
|
||||
elsif opts[:error_check] && \
|
||||
!opts[:good_exit].include?(output[:exitcode])
|
||||
!opts[:good_exit].include?(output.exitcode)
|
||||
raise_execution_error(output, opts)
|
||||
end
|
||||
output[:exitcode]
|
||||
output.exitcode
|
||||
end
|
||||
|
||||
def raise_execution_error(output, opts)
|
||||
# WinRM can return multiple stderr and stdout entries
|
||||
error_opts = opts.merge(
|
||||
stdout: flatten_stdout(output),
|
||||
stderr: flatten_stderr(output)
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr
|
||||
)
|
||||
|
||||
# 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 opts[:error_class], error_opts
|
||||
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
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ module VagrantPlugins
|
|||
attr_accessor :ssl_peer_verification
|
||||
attr_accessor :execution_time_limit
|
||||
attr_accessor :basic_auth_only
|
||||
attr_accessor :codepage
|
||||
|
||||
def initialize
|
||||
@username = UNSET_VALUE
|
||||
|
@ -27,6 +28,7 @@ module VagrantPlugins
|
|||
@ssl_peer_verification = UNSET_VALUE
|
||||
@execution_time_limit = UNSET_VALUE
|
||||
@basic_auth_only = UNSET_VALUE
|
||||
@codepage = UNSET_VALUE
|
||||
end
|
||||
|
||||
def finalize!
|
||||
|
@ -43,6 +45,7 @@ module VagrantPlugins
|
|||
@ssl_peer_verification = true if @ssl_peer_verification == UNSET_VALUE
|
||||
@execution_time_limit = "PT2H" if @execution_time_limit == UNSET_VALUE
|
||||
@basic_auth_only = false if @basic_auth_only == UNSET_VALUE
|
||||
@codepage = nil if @codepage == UNSET_VALUE
|
||||
end
|
||||
|
||||
def validate(machine)
|
||||
|
|
|
@ -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 > $out_file 2>&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
|
|
@ -9,6 +9,7 @@ Vagrant::Util::SilenceWarnings.silence! do
|
|||
require "winrm"
|
||||
end
|
||||
|
||||
require "winrm-elevated"
|
||||
require "winrm-fs"
|
||||
|
||||
module VagrantPlugins
|
||||
|
@ -53,48 +54,55 @@ module VagrantPlugins
|
|||
@config = config
|
||||
end
|
||||
|
||||
def powershell(command, &block)
|
||||
# Ensure an exit code
|
||||
command += "\r\nif ($?) { exit 0 } else { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
|
||||
session.create_executor do |executor|
|
||||
execute_with_rescue(executor.method("run_powershell_script"), command, &block)
|
||||
def powershell(command, opts = {}, &block)
|
||||
connection.shell(:powershell) do |shell|
|
||||
execute_with_rescue(shell, command, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd(command, &block)
|
||||
session.create_executor do |executor|
|
||||
execute_with_rescue(executor.method("run_cmd"), command, &block)
|
||||
def cmd(command, opts = {}, &block)
|
||||
shell_opts = {}
|
||||
shell_opts[:codepage] = @config.codepage if @config.codepage
|
||||
connection.shell(:cmd, shell_opts) do |shell|
|
||||
execute_with_rescue(shell, command, &block)
|
||||
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
|
||||
handle_output(session.method("run_wql"), query, &block)
|
||||
connection.run_wql(query)
|
||||
end
|
||||
rescue => e
|
||||
raise_winrm_exception(e, "run_wql", query)
|
||||
end
|
||||
|
||||
def upload(from, to)
|
||||
file_manager = WinRM::FS::FileManager.new(session)
|
||||
file_manager = WinRM::FS::FileManager.new(connection)
|
||||
file_manager.upload(from, to)
|
||||
end
|
||||
|
||||
def download(from, to)
|
||||
file_manager = WinRM::FS::FileManager.new(session)
|
||||
file_manager = WinRM::FS::FileManager.new(connection)
|
||||
file_manager.download(from, to)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def execute_with_rescue(method, command, &block)
|
||||
handle_output(method, command, &block)
|
||||
def execute_with_rescue(shell, command, &block)
|
||||
handle_output(shell, command, &block)
|
||||
rescue => e
|
||||
raise_winrm_exception(e, method.name, command)
|
||||
raise_winrm_exception(e, shell.class.name.split("::").last, command)
|
||||
end
|
||||
|
||||
def handle_output(execute_method, command, &block)
|
||||
output = execute_method.call(command) do |out, err|
|
||||
def handle_output(shell, command, &block)
|
||||
output = shell.run(command) do |out, err|
|
||||
block.call(:stdout, out) if block_given? && out
|
||||
block.call(:stderr, err) if block_given? && err
|
||||
end
|
||||
|
@ -104,14 +112,10 @@ module VagrantPlugins
|
|||
# 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
|
||||
# need to do this.
|
||||
if output[:exitcode] == 0
|
||||
(output[:data] || []).each do |data|
|
||||
next if !data[:stderr]
|
||||
if data[:stderr].include?("ParserError")
|
||||
@logger.warn("Detected ParserError, setting exit code to 1")
|
||||
output[:exitcode] = 1
|
||||
break
|
||||
end
|
||||
if output.exitcode == 0
|
||||
if output.stderr.include?("ParserError")
|
||||
@logger.warn("Detected ParserError, setting exit code to 1")
|
||||
output.exitcode = 1
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,21 +163,20 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
def new_session
|
||||
def new_connection
|
||||
@logger.info("Attempting to connect to WinRM...")
|
||||
@logger.info(" - Host: #{@host}")
|
||||
@logger.info(" - Port: #{@port}")
|
||||
@logger.info(" - Username: #{@config.username}")
|
||||
@logger.info(" - Transport: #{@config.transport}")
|
||||
|
||||
client = ::WinRM::WinRMWebService.new(endpoint, @config.transport.to_sym, endpoint_options)
|
||||
client.set_timeout(@config.timeout)
|
||||
client = ::WinRM::Connection.new(endpoint_options)
|
||||
client.logger = @logger
|
||||
client
|
||||
end
|
||||
|
||||
def session
|
||||
@session ||= new_session
|
||||
def connection
|
||||
@connection ||= new_connection
|
||||
end
|
||||
|
||||
def endpoint
|
||||
|
@ -188,8 +191,11 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def endpoint_options
|
||||
{ user: @username,
|
||||
pass: @password,
|
||||
{ endpoint: endpoint,
|
||||
transport: @config.transport,
|
||||
operation_timeout: @config.timeout,
|
||||
user: @username,
|
||||
password: @password,
|
||||
host: @host,
|
||||
port: @port,
|
||||
basic_auth_only: @config.basic_auth_only,
|
||||
|
|
|
@ -28,7 +28,7 @@ module VagrantPlugins
|
|||
args << "Bypass"
|
||||
args << "-NoExit"
|
||||
args << "-EncodedCommand"
|
||||
args << ::WinRM::PowershellScript.new(command).encoded
|
||||
args << encoded(command)
|
||||
if ps_info[:extra_args]
|
||||
args << ps_info[:extra_args]
|
||||
end
|
||||
|
@ -36,6 +36,11 @@ module VagrantPlugins
|
|||
# Launch it
|
||||
Vagrant::Util::SafeExec.exec("powershell", *args)
|
||||
end
|
||||
|
||||
def self.encoded(script)
|
||||
encoded_script = script.encode('UTF-16LE', 'UTF-8')
|
||||
Base64.strict_encode64(encoded_script)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,8 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
|
|||
let(:ui) { double("ui") }
|
||||
let(:machine) { double("machine", config: config, provider: provider, ui: ui) }
|
||||
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
|
||||
described_class.new(machine).tap do |comm|
|
||||
|
@ -81,61 +83,47 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do
|
|||
|
||||
describe ".execute" 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)
|
||||
end
|
||||
|
||||
it "wraps command in 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(: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 })
|
||||
it "use elevated shell script when elevated is true" do
|
||||
expect(shell).to receive(:elevated).and_return(good_output)
|
||||
expect(subject.execute("dir", { elevated: true })).to eq(0)
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
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(
|
||||
{ exitcode: 1, data: [{ stdout: '', stderr: '' }] })
|
||||
expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(bad_output)
|
||||
expect { subject.execute("dir") }.to raise_error(
|
||||
VagrantPlugins::CommunicatorWinRM::Errors::WinRMBadExitStatus)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".test" 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(output)
|
||||
expect(shell).to receive(:powershell).with(kind_of(String)).and_return(good_output)
|
||||
expect(subject.test("test -d c:/windows")).to be_true
|
||||
end
|
||||
|
||||
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(output)
|
||||
expect(shell).to receive(:powershell).with(kind_of(String)).and_return(bad_output)
|
||||
expect(subject.test("test -d /tmp/foobar")).to be_false
|
||||
end
|
||||
|
||||
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(subject.test("[-x stuff] && foo")).to be_false
|
||||
end
|
||||
|
|
|
@ -6,8 +6,8 @@ require Vagrant.source_root.join("plugins/communicators/winrm/config")
|
|||
describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
|
||||
include_context "unit"
|
||||
|
||||
let(:session) { double("winrm_session") }
|
||||
let(:executor) { double("command_executor") }
|
||||
let(:connection) { double("winrm_connection") }
|
||||
let(:shell) { double("winrm_shell") }
|
||||
let(:port) { config.transport == :ssl ? 5986 : 5985 }
|
||||
let(:config) {
|
||||
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
|
||||
|
@ -21,23 +21,45 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
|
|||
c.finalize!
|
||||
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
|
||||
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
|
||||
|
||||
describe ".powershell" do
|
||||
it "should call winrm powershell" do
|
||||
expect(executor).to receive(:run_powershell_script).with(/^dir.+/).and_return({ exitcode: 0 })
|
||||
expect(subject.powershell("dir")[:exitcode]).to eq(0)
|
||||
expect(shell).to receive(:run).with("dir").and_return(output)
|
||||
expect(subject.powershell("dir").exitcode).to eq(0)
|
||||
end
|
||||
|
||||
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!"))
|
||||
expect { subject.powershell("dir") }.to raise_error(
|
||||
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
|
@ -46,19 +68,35 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
|
|||
|
||||
describe ".cmd" do
|
||||
it "should call winrm cmd" do
|
||||
expect(executor).to receive(:run_cmd).with("dir").and_return({ exitcode: 0 })
|
||||
expect(subject.cmd("dir")[:exitcode]).to eq(0)
|
||||
expect(connection).to receive(:shell).with(:cmd, { })
|
||||
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
|
||||
|
||||
describe ".wql" do
|
||||
it "should call winrm wql" do
|
||||
expect(session).to receive(:run_wql).with("select * from Win32_OperatingSystem").and_return({ exitcode: 0 })
|
||||
expect(subject.wql("select * from Win32_OperatingSystem")[:exitcode]).to eq(0)
|
||||
expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem")
|
||||
subject.wql("select * from Win32_OperatingSystem")
|
||||
end
|
||||
|
||||
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
|
||||
# the second argument in a future WinRM release. Currently it doesn't track the
|
||||
# status code.
|
||||
|
@ -104,9 +142,10 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
|
|||
describe ".endpoint_options" do
|
||||
it "should create endpoint options" do
|
||||
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,
|
||||
retry_delay: 1, retry_limit: 2 })
|
||||
retry_delay: 1, retry_limit: 2, transport: :negotiate })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -28,8 +28,9 @@ Gem::Specification.new do |s|
|
|||
s.add_dependency "rb-kqueue", "~> 0.2.0"
|
||||
s.add_dependency "rest-client", ">= 1.6.0", "< 3.0"
|
||||
s.add_dependency "wdm", "~> 0.1.0"
|
||||
s.add_dependency "winrm", "~> 1.6"
|
||||
s.add_dependency "winrm-fs", "~> 0.3.0"
|
||||
s.add_dependency "winrm", "~> 2.1"
|
||||
s.add_dependency "winrm-fs", "~> 1.0"
|
||||
s.add_dependency "winrm-elevated", "~> 1.1"
|
||||
|
||||
# We lock this down to avoid compilation issues.
|
||||
s.add_dependency "nokogiri", "= 1.6.7.1"
|
||||
|
|
|
@ -59,6 +59,20 @@ to use port 4567 to talk to the guest if there is no other option.
|
|||
|
||||
<hr>
|
||||
|
||||
`config.winrm.execution_time_limit` - The maximum duration that a WinRM
|
||||
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).
|
||||
`config.winrm.ssl_peer_verification` - When set to `false` ssl certificate validation is not performed.
|
||||
|
||||
<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.
|
||||
|
|
Loading…
Reference in New Issue