vaguerent/lib/vagrant/util/powershell.rb
Brian Cain 3844a8e9f9
(#9056) Pass ruby block to capture stdout when determining PS version
Prior to this commit, the function used to determine the version of
Powershell would loop forever inside the Subprocess.execute function
because the process would never exit. This commit fixes that by passing
in a ruby block to capture the version from stdout instead of trying to
capture it from the returned process when it exits.
2018-02-22 10:04:16 -08:00

175 lines
5.6 KiB
Ruby

require "tmpdir"
require_relative "subprocess"
require_relative "which"
module Vagrant
module Util
# Executes PowerShell scripts.
#
# This is primarily a convenience wrapper around Subprocess that
# properly sets powershell flags for you.
class PowerShell
# NOTE: Version checks are only on Major
MINIMUM_REQUIRED_VERSION = 3
LOGGER = Log4r::Logger.new("vagrant::util::powershell")
# @return [Boolean] powershell executable available on PATH
def self.available?
if !defined?(@_powershell_available)
@_powershell_available = !!Which.which("powershell")
end
@_powershell_available
end
# Execute a powershell script.
#
# @param [String] path Path to the PowerShell script to execute.
# @return [Subprocess::Result]
def self.execute(path, *args, **opts, &block)
validate_install!
if opts.delete(:sudo) || opts.delete(:runas)
powerup_command(path, args, opts)
else
command = [
"powershell",
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"&('#{path}')",
args
].flatten
# Append on the options hash since Subprocess doesn't use
# Ruby 2.0 style options yet.
command << opts
Subprocess.execute(*command, &block)
end
end
# Execute a powershell command.
#
# @param [String] command PowerShell command to execute.
# @return [nil, String] Returns nil if exit code is non-zero.
# Returns stdout string if exit code is zero.
def self.execute_cmd(command)
validate_install!
c = [
"powershell",
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
command
].flatten.compact
r = Subprocess.execute(*c)
return nil if r.exit_code != 0
return r.stdout.chomp
end
# Returns the version of PowerShell that is installed.
#
# @return [String]
def self.version
if !defined?(@_powershell_version)
command = [
"powershell",
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
"Write-Output $PSVersionTable.PSVersion.Major"
].flatten
version = nil
begin
r = Subprocess.execute(*command, notify: [:stdout, :stderr], timeout: 10) {|io_name,data| version = data}
rescue Vagrant::Util::Subprocess::TimeoutExceeded
LOGGER.debug("Timeout exceeded while attempting to determine version of Powershell.")
end
@_powershell_version = version
end
@_powershell_version
end
# Validates that powershell is installed, available, and
# at or above minimum required version
#
# @return [Boolean]
# @raises []
def self.validate_install!
if !defined?(@_powershell_validation)
raise Errors::PowerShellNotFound if !available?
if version.to_i < MINIMUM_REQUIRED_VERSION
raise Errors::PowerShellInvalidVersion,
minimum_version: MINIMUM_REQUIRED_VERSION,
installed_version: version ? version : "N/A"
end
@_powershell_validation = true
end
@_powershell_validation
end
# Powerup the given command to perform privileged operations.
#
# @param [String] path
# @param [Array<String>] args
# @return [Array<String>]
def self.powerup_command(path, args, opts)
Dir.mktmpdir("vagrant") do |dpath|
all_args = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", path] + args
arg_list = "@('" + all_args.join("', '") + "')"
stdout = File.join(dpath, "stdout.txt")
stderr = File.join(dpath, "stderr.txt")
exitcode = File.join(dpath, "exitcode.txt")
script = "$sp = Start-Process -FilePath powershell -ArgumentList #{arg_list} " \
"-PassThru -Wait -RedirectStandardOutput '#{stdout}' -RedirectStandardError '#{stderr}' -WindowStyle Hidden; " \
"if($sp){ Set-Content -Path '#{exitcode}' -Value $sp.ExitCode;exit $sp.ExitCode; }else{ exit 1 }"
# escape quotes so we can nest our script within a start-process
script.gsub!("'", "''")
cmd = [
"powershell",
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command", "$p = Start-Process -FilePath powershell -ArgumentList " \
"@('-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', '#{script}') " \
"-PassThru -Wait -WindowStyle Hidden -Verb RunAs; if($p){ exit $p.ExitCode; }else{ exit 1 }"
]
result = Subprocess.execute(*cmd.push(opts))
if File.exist?(stdout)
r_stdout = File.read(stdout)
else
r_stdout = result.stdout
end
if File.exist?(stderr)
r_stderr = File.read(stderr)
else
r_stderr = result.stderr
end
code = 1
if File.exist?(exitcode)
code_txt = File.read(exitcode).strip
if code_txt.match(/^\d+$/)
code = code_txt.to_i
end
end
Subprocess::Result.new(code, r_stdout, r_stderr)
end
end
end
end
end