class Vagrant::Util::PowerShell
Executes PowerShell
scripts.
This is primarily a convenience wrapper around Subprocess
that properly sets powershell flags for you.
Constants
- DEFAULT_VERSION_DETECTION_TIMEOUT
Number of seconds to wait while attempting to get powershell version
- LOGGER
- MINIMUM_REQUIRED_VERSION
NOTE: Version checks are only on Major
- POWERSHELL_NAMES
Names of the powershell executable
- POWERSHELL_PATHS
Paths to powershell executable
Public Class Methods
@return [Boolean] powershell executable available on PATH
# File lib/vagrant/util/powershell.rb, line 63 def self.available? !executable.nil? end
@return [String|nil] a powershell executable, depending on environment
# File lib/vagrant/util/powershell.rb, line 30 def self.executable if !defined?(@_powershell_executable) # First start with detecting executable on configured path POWERSHELL_NAMES.detect do |psh| return @_powershell_executable = psh if Which.which(psh) psh += ".exe" return @_powershell_executable = psh if Which.which(psh) end # Now attempt with paths paths = POWERSHELL_PATHS.map do |ppath| result = Util::Subprocess.execute("cmd.exe", "/c", "echo #{ppath}") result.stdout.gsub("\"", "").strip if result.exit_code == 0 end.compact paths.each do |psh_path| POWERSHELL_NAMES.each do |psh| path = File.join(psh_path, psh) return @_powershell_executable = path if Which.which(path) path += ".exe" return @_powershell_executable = path if Which.which(path) # Finally test the msys2 style path path = path.sub(/^([A-Za-z]):/, "/mnt/\\1") return @_powershell_executable = path if Which.which(path) end end end @_powershell_executable end
Execute a powershell script.
@param [String] path Path to the PowerShell
script to execute. @param [Array<String>] args Command arguments @param [Hash] opts Options passed to execute @option opts [Hash] :env Custom environment variables @return [Subprocess::Result]
# File lib/vagrant/util/powershell.rb, line 74 def self.execute(path, *args, **opts, &block) validate_install! if opts.delete(:sudo) || opts.delete(:runas) powerup_command(path, args, opts) else if mpath = opts.delete(:module_path) m_env = opts.fetch(:env, {}) m_env["PSModulePath"] = "$env:PSModulePath+';#{mpath}'" opts[:env] = m_env end if env = opts.delete(:env) env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " end command = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "#{env}&('#{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. @param [Hash] opts Extra options @option opts [Hash] :env Custom environment variables @return [nil, String] Returns nil if exit code is non-zero.
Returns stdout string if exit code is zero.
# File lib/vagrant/util/powershell.rb, line 112 def self.execute_cmd(command, **opts) validate_install! if mpath = opts.delete(:module_path) m_env = opts.fetch(:env, {}) m_env["PSModulePath"] = "$env:PSModulePath+';#{mpath}'" opts[:env] = m_env end if env = opts.delete(:env) env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " end c = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "#{env}#{command}" ].flatten.compact r = Subprocess.execute(*c) return nil if r.exit_code != 0 return r.stdout.chomp end
Execute a powershell command and return a result
@param [String] command PowerShell
command to execute. @param [Hash] opts A collection of options for subprocess::execute @option opts [Hash] :env Custom environment variables @param [Block] block Ruby block
# File lib/vagrant/util/powershell.rb, line 143 def self.execute_inline(*command, **opts, &block) validate_install! if mpath = opts.delete(:module_path) m_env = opts.fetch(:env, {}) m_env["PSModulePath"] = "$env:PSModulePath+';#{mpath}'" opts[:env] = m_env end if env = opts.delete(:env) env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " end command = command.join(' ') c = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "#{env}#{command}" ].flatten.compact c << opts Subprocess.execute(*c, &block) end
Powerup the given command to perform privileged operations.
@param [String] path @param [Array<String>] args @return [Array<String>]
# File lib/vagrant/util/powershell.rb, line 227 def self.powerup_command(path, args, opts) Dir.mktmpdir("vagrant") do |dpath| all_args = [path] + args.flatten.map{ |a| a.gsub(/^['"](.+)['"]$/, "\\1") } arg_list = "\"" + all_args.join("\" \"") + "\"" stdout = File.join(dpath, "stdout.txt") stderr = File.join(dpath, "stderr.txt") script = "& #{arg_list} ; exit $LASTEXITCODE;" script_content = Base64.strict_encode64(script.encode("UTF-16LE", "UTF-8")) # Wrap so we can redirect output to read later wrapper = "$p = Start-Process -FilePath powershell -ArgumentList @('-NoLogo', '-NoProfile', " \ "'-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', '#{script_content}') " \ "-PassThru -WindowStyle Hidden -Wait -RedirectStandardOutput '#{stdout}' -RedirectStandardError '#{stderr}'; " \ "if($p){ exit $p.ExitCode; }else{ exit 1 }" wrapper_content = Base64.strict_encode64(wrapper.encode("UTF-16LE", "UTF-8")) powerup = "$p = Start-Process -FilePath powershell -ArgumentList @('-NoLogo', '-NoProfile', " \ "'-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', '#{wrapper_content}') " \ "-PassThru -WindowStyle Hidden -Wait -Verb RunAs; if($p){ exit $p.ExitCode; }else{ exit 1 }" cmd = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", powerup ] result = Subprocess.execute(*cmd.push(opts)) r_stdout = result.stdout if File.exist?(stdout) r_stdout += File.read(stdout) end r_stderr = result.stderr if File.exist?(stderr) r_stderr += File.read(stderr) end Subprocess::Result.new(result.exit_code, r_stdout, r_stderr) end end
@private Reset the cached values for platform. This is not considered a public API and should only be used for testing.
# File lib/vagrant/util/powershell.rb, line 276 def self.reset! instance_variables.each(&method(:remove_instance_variable)) end
Validates that powershell is installed, available, and at or above minimum required version
@return [Boolean] @raises []
# File lib/vagrant/util/powershell.rb, line 209 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
Returns the version of PowerShell
that is installed.
@return [String]
# File lib/vagrant/util/powershell.rb, line 173 def self.version if !defined?(@_powershell_version) command = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "Write-Output $PSVersionTable.PSVersion.Major" ].flatten version = nil timeout = ENV["VAGRANT_POWERSHELL_VERSION_DETECTION_TIMEOUT"].to_i if timeout < 1 timeout = DEFAULT_VERSION_DETECTION_TIMEOUT end begin r = Subprocess.execute(*command, notify: [:stdout, :stderr], timeout: timeout, ) {|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