Skip to content

Commit ad370c8

Browse files
bkonyiandrewkolos
andauthored
[ Tool ] Support Powershell v6+ to determine Windows version in flutter doctor (#156476)
Powershell v6+ use the executable name `pwsh.exe` instead of `powershell.exe`. This change adds support for determining the Windows version on systems that solely use `pwsh.exe` and don't have `powershell.exe` on the `PATH`. Fixes #156189. --------- Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
1 parent 2b1e71b commit ad370c8

File tree

2 files changed

+135
-3
lines changed

2 files changed

+135
-3
lines changed

packages/flutter_tools/lib/src/windows/windows_version_validator.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,22 @@ class WindowsVersionValidator extends DoctorValidator {
3737
final ProcessLister _processLister;
3838

3939
Future<ValidationResult> _topazScan() async {
40+
if (!_processLister.canRunPowershell()) {
41+
return const ValidationResult(
42+
ValidationType.missing,
43+
<ValidationMessage>[
44+
ValidationMessage.hint('Failed to find ${ProcessLister.powershell} or ${ProcessLister.pwsh} on PATH'),
45+
],
46+
);
47+
}
4048
final ProcessResult getProcessesResult = await _processLister.getProcessesWithPath();
4149
if (getProcessesResult.exitCode != 0) {
42-
return const ValidationResult(ValidationType.missing, <ValidationMessage>[ValidationMessage.hint('Get-Process failed to complete')]);
50+
return const ValidationResult(
51+
ValidationType.missing,
52+
<ValidationMessage>[
53+
ValidationMessage.hint('Get-Process failed to complete'),
54+
],
55+
);
4356
}
4457
final RegExp topazRegex = RegExp(kCoreProcessPattern, caseSensitive: false, multiLine: true);
4558
final String processes = getProcessesResult.stdout as String;
@@ -106,8 +119,22 @@ class ProcessLister {
106119

107120
final ProcessManager processManager;
108121

122+
static const String powershell = 'powershell';
123+
static const String pwsh = 'pwsh';
124+
125+
bool canRunPowershell() {
126+
return processManager.canRun(powershell) || processManager.canRun(pwsh);
127+
}
128+
109129
Future<ProcessResult> getProcessesWithPath() async {
110130
const String argument = 'Get-Process | Format-List Path';
111-
return processManager.run(<String>['powershell', '-command', argument]);
131+
const List<String> psArgs = <String>['-command', argument];
132+
if (processManager.canRun(powershell)) {
133+
return processManager.run(<String>[powershell, ...psArgs]);
134+
}
135+
if (processManager.canRun(pwsh)) {
136+
return processManager.run(<String>[pwsh, ...psArgs]);
137+
}
138+
throw StateError('Failed to find $powershell or $pwsh on PATH');
112139
}
113140
}

packages/flutter_tools/test/general.shard/windows_version_validator_test.dart

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:flutter_tools/src/windows/windows_version_validator.dart';
99
import 'package:test/fake.dart';
1010

1111
import '../src/common.dart';
12+
import '../src/context.dart';
1213

1314
/// Fake [_WindowsUtils] to use for testing
1415
class FakeValidOperatingSystemUtils extends Fake
@@ -21,14 +22,22 @@ class FakeValidOperatingSystemUtils extends Fake
2122
}
2223

2324
class FakeProcessLister extends Fake implements ProcessLister {
24-
FakeProcessLister({required this.result, this.exitCode = 0});
25+
FakeProcessLister({
26+
required this.result,
27+
this.exitCode = 0,
28+
this.powershellAvailable = true,
29+
});
2530
final String result;
2631
final int exitCode;
32+
final bool powershellAvailable;
2733

2834
@override
2935
Future<ProcessResult> getProcessesWithPath() async {
3036
return ProcessResult(0, exitCode, result, null);
3137
}
38+
39+
@override
40+
bool canRunPowershell() => powershellAvailable;
3241
}
3342

3443
FakeProcessLister ofdRunning() {
@@ -43,6 +52,10 @@ FakeProcessLister failure() {
4352
return FakeProcessLister(result: r'Path: "C:\Program Files\Google\Chrome\Application\chrome.exe', exitCode: 10);
4453
}
4554

55+
FakeProcessLister powershellUnavailable() {
56+
return FakeProcessLister(result: '', powershellAvailable: false);
57+
}
58+
4659
/// The expected validation result object for
4760
/// a passing windows version test
4861
const ValidationResult validWindows10ValidationResult = ValidationResult(
@@ -70,6 +83,15 @@ const ValidationResult ofdFoundRunning = ValidationResult(
7083
statusInfo: 'Problem detected with Windows installation',
7184
);
7285

86+
const ValidationResult powershellUnavailableResult = ValidationResult(
87+
ValidationType.partial,
88+
<ValidationMessage>[
89+
ValidationMessage.hint('Failed to find ${ProcessLister.powershell} or ${ProcessLister.pwsh} on PATH'),
90+
],
91+
statusInfo: 'Problem detected with Windows installation',
92+
);
93+
94+
7395
const ValidationResult getProcessFailed = ValidationResult(
7496
ValidationType.partial,
7597
<ValidationMessage>[
@@ -158,6 +180,18 @@ OS 版本: 10.0.22621 暂缺 Build 22621
158180
expect(result.messages[0].message, ofdFoundRunning.messages[0].message, reason: 'The ValidationMessage message should be the same');
159181
});
160182

183+
testWithoutContext('Reports missing powershell', () async {
184+
final WindowsVersionValidator validator =
185+
WindowsVersionValidator(
186+
operatingSystemUtils: FakeValidOperatingSystemUtils(),
187+
processLister: powershellUnavailable());
188+
final ValidationResult result = await validator.validate();
189+
expect(result.type, powershellUnavailableResult.type, reason: 'The ValidationResult type should be the same (partial)');
190+
expect(result.statusInfo, powershellUnavailableResult.statusInfo, reason: 'The ValidationResult statusInfo should be the same');
191+
expect(result.messages.length, 1, reason: 'The ValidationResult should have precisely 1 message');
192+
expect(result.messages[0].message, powershellUnavailableResult.messages[0].message, reason: 'The ValidationMessage message should be the same');
193+
});
194+
161195
testWithoutContext('Reports failure of Get-Process', () async {
162196
final WindowsVersionValidator validator =
163197
WindowsVersionValidator(
@@ -169,4 +203,75 @@ OS 版本: 10.0.22621 暂缺 Build 22621
169203
expect(result.messages.length, 1, reason: 'The ValidationResult should have precisely 1 message');
170204
expect(result.messages[0].message, getProcessFailed.messages[0].message, reason: 'The ValidationMessage message should be the same');
171205
});
206+
207+
testWithoutContext('getProcessesWithPath successfully runs with powershell', () async {
208+
final ProcessLister processLister = ProcessLister(
209+
FakeProcessManager.list(
210+
<FakeCommand>[
211+
const FakeCommand(
212+
command: <String>[
213+
ProcessLister.powershell,
214+
'-command',
215+
'Get-Process | Format-List Path',
216+
],
217+
stdout: ProcessLister.powershell,
218+
),
219+
],
220+
)..excludedExecutables.add(ProcessLister.pwsh),
221+
);
222+
223+
try {
224+
final ProcessResult result = await processLister.getProcessesWithPath();
225+
expect(result.stdout, ProcessLister.powershell);
226+
// ignore: avoid_catches_without_on_clauses
227+
} catch (e) {
228+
fail('Unexpected exception: $e');
229+
}
230+
});
231+
232+
testWithoutContext('getProcessesWithPath falls back to pwsh when powershell is not on the path', () async {
233+
final ProcessLister processLister = ProcessLister(
234+
FakeProcessManager.list(
235+
<FakeCommand>[
236+
const FakeCommand(
237+
command: <String>[
238+
ProcessLister.pwsh,
239+
'-command',
240+
'Get-Process | Format-List Path',
241+
],
242+
stdout: ProcessLister.pwsh,
243+
),
244+
],
245+
)..excludedExecutables.add(ProcessLister.powershell),
246+
);
247+
248+
try {
249+
final ProcessResult result = await processLister.getProcessesWithPath();
250+
expect(result.stdout, ProcessLister.pwsh);
251+
// ignore: avoid_catches_without_on_clauses
252+
} catch (e) {
253+
fail('Unexpected exception: $e');
254+
}
255+
});
256+
257+
testWithoutContext('getProcessesWithPath throws if both powershell and pwsh are not on PATH', () async {
258+
final ProcessLister processLister = ProcessLister(
259+
FakeProcessManager.empty()..excludedExecutables.addAll(
260+
<String>[
261+
ProcessLister.powershell,
262+
ProcessLister.pwsh,
263+
],
264+
),
265+
);
266+
267+
try {
268+
final ProcessResult result = await processLister.getProcessesWithPath();
269+
fail('Should have thrown, but successfully ran ${result.stdout}');
270+
} on StateError {
271+
// Expected
272+
// ignore: avoid_catches_without_on_clauses
273+
} catch (e) {
274+
fail('Unexpected exception: $e');
275+
}
276+
});
172277
}

0 commit comments

Comments
 (0)