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,
}.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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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