Merge pull request #13648 from chrisroberts/pwsh-selection

Prefer pwsh executable over powershell excutable
This commit is contained in:
Chris Roberts 2025-04-15 14:55:58 -07:00 committed by GitHub
commit 387f5ba87d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 90 additions and 17 deletions

View file

@ -19,7 +19,7 @@ module Vagrant
# Number of seconds to wait while attempting to get powershell version
DEFAULT_VERSION_DETECTION_TIMEOUT = 30
# Names of the powershell executable
POWERSHELL_NAMES = ["powershell", "pwsh"].map(&:freeze).freeze
POWERSHELL_NAMES = ["pwsh", "powershell"].map(&:freeze).freeze
# Paths to powershell executable
POWERSHELL_PATHS = [
"%SYSTEMROOT%/System32/WindowsPowerShell/v1.0",
@ -33,11 +33,29 @@ module Vagrant
# @return [String|nil] a powershell executable, depending on environment
def self.executable
if !defined?(@_powershell_executable)
prefer_name = ENV["VAGRANT_PREFERRED_POWERSHELL"].to_s.sub(".exe", "")
if !POWERSHELL_NAMES.include?(prefer_name)
prefer_name = POWERSHELL_NAMES.first
end
LOGGER.debug("preferred powershell executable name: #{prefer_name}")
# 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)
found_shells = Hash.new.tap do |found|
POWERSHELL_NAMES.each do |psh|
psh_path = Which.which(psh)
psh_path = Which.which(psh + ".exe") if !psh_path
next if !psh_path
LOGGER.debug("detected powershell for #{psh.inspect} - #{psh_path}")
found[psh] = psh_path
end
end
# Done if preferred shell was found
if found_shells.key?(prefer_name)
LOGGER.debug("using preferred powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}")
return @_powershell_executable = found_shells[prefer_name]
end
# Now attempt with paths
@ -48,17 +66,29 @@ module Vagrant
paths.each do |psh_path|
POWERSHELL_NAMES.each do |psh|
next if found_shells.key?(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)
[path, "#{path}.exe", path.sub(/^([A-Za-z]):/, "/mnt/\\1")].each do |full_path|
if File.executable?(full_path)
found_shells[psh] = full_path
break
end
end
end
end
# Done if preferred shell was found
if found_shells.key?(prefer_name)
LOGGER.debug("using preferred powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}")
return @_powershell_executable = found_shells[prefer_name]
end
# Iterate names and return first found
POWERSHELL_NAMES.each do |psh|
LOGGER.debug("using powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}")
return @_powershell_executable = found_shells[psh] if found_shells.key?(psh)
end
end
@_powershell_executable
end
@ -94,6 +124,7 @@ module Vagrant
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
"#{env}&('#{path}')",
args
].flatten

View file

@ -49,6 +49,7 @@ describe Vagrant::Util::PowerShell do
describe ".executable" do
before do
allow(ENV).to receive(:[]).with("VAGRANT_PREFERRED_POWERSHELL").and_return(nil)
allow(Vagrant::Util::Which).to receive(:which).and_return(nil)
allow(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
Vagrant::Util::Subprocess::Result.new(0, args.last.sub("echo ", ""), "")
@ -57,7 +58,7 @@ describe Vagrant::Util::PowerShell do
context "when powershell found in PATH" do
before{ expect(Vagrant::Util::Which).to receive(:which).
with("powershell").and_return(true) }
with("powershell").and_return("powershell") }
it "should return powershell string" do
expect(described_class.executable).to eq("powershell")
@ -66,7 +67,7 @@ describe Vagrant::Util::PowerShell do
context "when pwsh found in PATH" do
before { expect(Vagrant::Util::Which).to receive(:which).
with("pwsh").and_return(true) }
with("pwsh").and_return("pwsh") }
it "should return pwsh string" do
expect(described_class.executable).to eq("pwsh")
@ -74,6 +75,8 @@ describe Vagrant::Util::PowerShell do
end
context "when not found in PATH" do
before { allow(File).to receive(:executable?) }
it "should return nil" do
expect(described_class.executable).to be_nil
end
@ -85,15 +88,46 @@ describe Vagrant::Util::PowerShell do
it "should return powershell.exe when found" do
expect(Vagrant::Util::Which).to receive(:which).
with("powershell.exe").and_return(true)
with("powershell.exe").and_return("powershell.exe")
expect(described_class.executable).to eq("powershell.exe")
end
it "should check for powershell with full path" do
expect(Vagrant::Util::Which).to receive(:which).with(/WindowsPowerShell\/v1.0\/powershell.exe/)
expect(File).to receive(:executable?).with(/WindowsPowerShell\/v1.0\/powershell.exe/)
described_class.executable
end
end
context "powershell preference" do
before do
allow(Vagrant::Util::Which).to receive(:which)
allow(File).to receive(:executable?)
end
it "should prefer pwsh found on in the PATH" do
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("pwsh.exe")
expect(described_class.executable).to eq("pwsh.exe")
end
it "should use powershell.exe when found on PATH and pwsh.exe is not" do
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("powershell.exe")
expect(described_class.executable).to eq("powershell.exe")
end
it "should prefer powershell.exe when env var is set and powershell.exe and pwsh.exe are on PATH" do
expect(ENV).to receive(:[]).with("VAGRANT_PREFERRED_POWERSHELL").and_return("powershell")
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("pwsh.exe")
expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return("powershell.exe")
expect(described_class.executable).to eq("powershell.exe")
end
it "should use pwsh.exe when env var is set to powershell but only pwsh.exe is avaialble" do
expect(ENV).to receive(:[]).with("VAGRANT_PREFERRED_POWERSHELL").and_return("powershell")
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("pwsh.exe")
expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return(nil)
expect(described_class.executable).to eq("pwsh.exe")
end
end
end
describe ".available?" do

View file

@ -268,6 +268,14 @@ detection.
When setting this environment variable, its value will be in seconds. By default,
it will use 30 seconds as a timeout.
## `VAGRANT_PREFERRED_POWERSHELL`
When executing PowerShell commands, Vagrant will prefer to use `pwsh.exe`
over `powershell.exe` by default. This environment variable can be used to
modify this preference and make Vagrant prefer `powershell.exe`. The value
set in this environment variable are any supported PowerShell executables
which currently are: `powershell` and `pwsh`.
## `VAGRANT_PREFERRED_PROVIDERS`
This configures providers that Vagrant should prefer.