Skip to content

Commit 387f5ba

Browse files
authored
Merge pull request #13648 from chrisroberts/pwsh-selection
Prefer pwsh executable over powershell excutable
2 parents f308f79 + 12af53a commit 387f5ba

File tree

3 files changed

+89
-16
lines changed

3 files changed

+89
-16
lines changed

lib/vagrant/util/powershell.rb

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class PowerShell
1919
# Number of seconds to wait while attempting to get powershell version
2020
DEFAULT_VERSION_DETECTION_TIMEOUT = 30
2121
# Names of the powershell executable
22-
POWERSHELL_NAMES = ["powershell", "pwsh"].map(&:freeze).freeze
22+
POWERSHELL_NAMES = ["pwsh", "powershell"].map(&:freeze).freeze
2323
# Paths to powershell executable
2424
POWERSHELL_PATHS = [
2525
"%SYSTEMROOT%/System32/WindowsPowerShell/v1.0",
@@ -33,11 +33,29 @@ class PowerShell
3333
# @return [String|nil] a powershell executable, depending on environment
3434
def self.executable
3535
if !defined?(@_powershell_executable)
36+
prefer_name = ENV["VAGRANT_PREFERRED_POWERSHELL"].to_s.sub(".exe", "")
37+
if !POWERSHELL_NAMES.include?(prefer_name)
38+
prefer_name = POWERSHELL_NAMES.first
39+
end
40+
41+
LOGGER.debug("preferred powershell executable name: #{prefer_name}")
42+
3643
# First start with detecting executable on configured path
37-
POWERSHELL_NAMES.detect do |psh|
38-
return @_powershell_executable = psh if Which.which(psh)
39-
psh += ".exe"
40-
return @_powershell_executable = psh if Which.which(psh)
44+
found_shells = Hash.new.tap do |found|
45+
POWERSHELL_NAMES.each do |psh|
46+
psh_path = Which.which(psh)
47+
psh_path = Which.which(psh + ".exe") if !psh_path
48+
next if !psh_path
49+
50+
LOGGER.debug("detected powershell for #{psh.inspect} - #{psh_path}")
51+
found[psh] = psh_path
52+
end
53+
end
54+
55+
# Done if preferred shell was found
56+
if found_shells.key?(prefer_name)
57+
LOGGER.debug("using preferred powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}")
58+
return @_powershell_executable = found_shells[prefer_name]
4159
end
4260

4361
# Now attempt with paths
@@ -48,16 +66,28 @@ def self.executable
4866

4967
paths.each do |psh_path|
5068
POWERSHELL_NAMES.each do |psh|
69+
next if found_shells.key?(psh)
70+
5171
path = File.join(psh_path, psh)
52-
return @_powershell_executable = path if Which.which(path)
72+
[path, "#{path}.exe", path.sub(/^([A-Za-z]):/, "/mnt/\\1")].each do |full_path|
73+
if File.executable?(full_path)
74+
found_shells[psh] = full_path
75+
break
76+
end
77+
end
78+
end
79+
end
5380

54-
path += ".exe"
55-
return @_powershell_executable = path if Which.which(path)
81+
# Done if preferred shell was found
82+
if found_shells.key?(prefer_name)
83+
LOGGER.debug("using preferred powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}")
84+
return @_powershell_executable = found_shells[prefer_name]
85+
end
5686

57-
# Finally test the msys2 style path
58-
path = path.sub(/^([A-Za-z]):/, "/mnt/\\1")
59-
return @_powershell_executable = path if Which.which(path)
60-
end
87+
# Iterate names and return first found
88+
POWERSHELL_NAMES.each do |psh|
89+
LOGGER.debug("using powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}")
90+
return @_powershell_executable = found_shells[psh] if found_shells.key?(psh)
6191
end
6292
end
6393
@_powershell_executable
@@ -94,6 +124,7 @@ def self.execute(path, *args, **opts, &block)
94124
"-NoProfile",
95125
"-NonInteractive",
96126
"-ExecutionPolicy", "Bypass",
127+
"-Command",
97128
"#{env}&('#{path}')",
98129
args
99130
].flatten

test/unit/vagrant/util/powershell_test.rb

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
describe ".executable" do
5151
before do
52+
allow(ENV).to receive(:[]).with("VAGRANT_PREFERRED_POWERSHELL").and_return(nil)
5253
allow(Vagrant::Util::Which).to receive(:which).and_return(nil)
5354
allow(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
5455
Vagrant::Util::Subprocess::Result.new(0, args.last.sub("echo ", ""), "")
@@ -57,7 +58,7 @@
5758

5859
context "when powershell found in PATH" do
5960
before{ expect(Vagrant::Util::Which).to receive(:which).
60-
with("powershell").and_return(true) }
61+
with("powershell").and_return("powershell") }
6162

6263
it "should return powershell string" do
6364
expect(described_class.executable).to eq("powershell")
@@ -66,14 +67,16 @@
6667

6768
context "when pwsh found in PATH" do
6869
before { expect(Vagrant::Util::Which).to receive(:which).
69-
with("pwsh").and_return(true) }
70+
with("pwsh").and_return("pwsh") }
7071

7172
it "should return pwsh string" do
7273
expect(described_class.executable).to eq("pwsh")
7374
end
7475
end
7576

7677
context "when not found in PATH" do
78+
before { allow(File).to receive(:executable?) }
79+
7780
it "should return nil" do
7881
expect(described_class.executable).to be_nil
7982
end
@@ -85,15 +88,46 @@
8588

8689
it "should return powershell.exe when found" do
8790
expect(Vagrant::Util::Which).to receive(:which).
88-
with("powershell.exe").and_return(true)
91+
with("powershell.exe").and_return("powershell.exe")
8992
expect(described_class.executable).to eq("powershell.exe")
9093
end
9194

9295
it "should check for powershell with full path" do
93-
expect(Vagrant::Util::Which).to receive(:which).with(/WindowsPowerShell\/v1.0\/powershell.exe/)
96+
expect(File).to receive(:executable?).with(/WindowsPowerShell\/v1.0\/powershell.exe/)
9497
described_class.executable
9598
end
9699
end
100+
101+
context "powershell preference" do
102+
before do
103+
allow(Vagrant::Util::Which).to receive(:which)
104+
allow(File).to receive(:executable?)
105+
end
106+
107+
it "should prefer pwsh found on in the PATH" do
108+
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("pwsh.exe")
109+
expect(described_class.executable).to eq("pwsh.exe")
110+
end
111+
112+
it "should use powershell.exe when found on PATH and pwsh.exe is not" do
113+
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("powershell.exe")
114+
expect(described_class.executable).to eq("powershell.exe")
115+
end
116+
117+
it "should prefer powershell.exe when env var is set and powershell.exe and pwsh.exe are on PATH" do
118+
expect(ENV).to receive(:[]).with("VAGRANT_PREFERRED_POWERSHELL").and_return("powershell")
119+
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("pwsh.exe")
120+
expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return("powershell.exe")
121+
expect(described_class.executable).to eq("powershell.exe")
122+
end
123+
124+
it "should use pwsh.exe when env var is set to powershell but only pwsh.exe is avaialble" do
125+
expect(ENV).to receive(:[]).with("VAGRANT_PREFERRED_POWERSHELL").and_return("powershell")
126+
expect(Vagrant::Util::Which).to receive(:which).with("pwsh.exe").and_return("pwsh.exe")
127+
expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return(nil)
128+
expect(described_class.executable).to eq("pwsh.exe")
129+
end
130+
end
97131
end
98132

99133
describe ".available?" do

website/content/docs/other/environmental-variables.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,14 @@ detection.
268268
When setting this environment variable, its value will be in seconds. By default,
269269
it will use 30 seconds as a timeout.
270270

271+
## `VAGRANT_PREFERRED_POWERSHELL`
272+
273+
When executing PowerShell commands, Vagrant will prefer to use `pwsh.exe`
274+
over `powershell.exe` by default. This environment variable can be used to
275+
modify this preference and make Vagrant prefer `powershell.exe`. The value
276+
set in this environment variable are any supported PowerShell executables
277+
which currently are: `powershell` and `pwsh`.
278+
271279
## `VAGRANT_PREFERRED_PROVIDERS`
272280

273281
This configures providers that Vagrant should prefer.

0 commit comments

Comments
 (0)