From b85492bd0053c6fd3d020b12cd3fe1e2a4bdf04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Tue, 25 Mar 2025 13:31:11 +0800 Subject: [PATCH] windows: Detect `pwsh` (#25713) Closes #22015 Release Notes: - N/A --- crates/task/src/lib.rs | 4 +- crates/terminal/src/terminal.rs | 14 ++++- crates/util/src/util.rs | 102 ++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index d198514596..573558edf7 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -434,8 +434,10 @@ impl ShellBuilder { // `alacritty_terminal` uses this as default on Windows. See: // https://github.com/alacritty/alacritty/blob/0d4ab7bca43213d96ddfe40048fc0f922543c6f8/alacritty_terminal/src/tty/windows/mod.rs#L130 + // We could use `util::retrieve_system_shell()` here, but we are running tasks here, so leave it to `powershell.exe` + // should be okay. fn system_shell() -> String { - "powershell".to_owned() + "powershell.exe".to_string() } fn to_windows_shell_variable(&self, input: String) -> String { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a6a0440d74..354044706b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -376,7 +376,19 @@ impl TerminalBuilder { let pty_options = { let alac_shell = match shell.clone() { - Shell::System => None, + Shell::System => { + #[cfg(target_os = "windows")] + { + Some(alacritty_terminal::tty::Shell::new( + util::retrieve_system_shell(), + Vec::new(), + )) + } + #[cfg(not(target_os = "windows"))] + { + None + } + } Shell::Program(program) => { Some(alacritty_terminal::tty::Shell::new(program, Vec::new())) } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 3c12c7bdf4..21bd263479 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -416,6 +416,108 @@ pub fn iterate_expanded_and_wrapped_usize_range( } } +#[cfg(target_os = "windows")] +pub fn retrieve_system_shell() -> String { + use std::path::PathBuf; + + fn find_pwsh_in_programfiles(find_alternate: bool, find_preview: bool) -> Option { + #[cfg(target_pointer_width = "64")] + let env_var = if find_alternate { + "ProgramFiles(x86)" + } else { + "ProgramFiles" + }; + + #[cfg(target_pointer_width = "32")] + let env_var = if find_alternate { + "ProgramW6432" + } else { + "ProgramFiles" + }; + + let install_base_dir = PathBuf::from(std::env::var_os(env_var)?).join("PowerShell"); + install_base_dir + .read_dir() + .ok()? + .filter_map(Result::ok) + .filter(|entry| matches!(entry.file_type(), Ok(ft) if ft.is_dir())) + .filter_map(|entry| { + let dir_name = entry.file_name(); + let dir_name = dir_name.to_string_lossy(); + + let version = if find_preview { + let dash_index = dir_name.find('-')?; + if &dir_name[dash_index + 1..] != "preview" { + return None; + }; + dir_name[..dash_index].parse::().ok()? + } else { + dir_name.parse::().ok()? + }; + + let exe_path = entry.path().join("pwsh.exe"); + if exe_path.exists() { + Some((version, exe_path)) + } else { + None + } + }) + .max_by_key(|(version, _)| *version) + .map(|(_, path)| path) + } + + fn find_pwsh_in_msix(find_preview: bool) -> Option { + let msix_app_dir = + PathBuf::from(std::env::var_os("LOCALAPPDATA")?).join("Microsoft\\WindowsApps"); + if !msix_app_dir.exists() { + return None; + } + + let prefix = if find_preview { + "Microsoft.PowerShellPreview_" + } else { + "Microsoft.PowerShell_" + }; + msix_app_dir + .read_dir() + .ok()? + .filter_map(|entry| { + let entry = entry.ok()?; + if !matches!(entry.file_type(), Ok(ft) if ft.is_dir()) { + return None; + } + + if !entry.file_name().to_string_lossy().starts_with(prefix) { + return None; + } + + let exe_path = entry.path().join("pwsh.exe"); + exe_path.exists().then_some(exe_path) + }) + .next() + } + + fn find_pwsh_in_scoop() -> Option { + let pwsh_exe = + PathBuf::from(std::env::var_os("USERPROFILE")?).join("scoop\\shims\\pwsh.exe"); + pwsh_exe.exists().then_some(pwsh_exe) + } + + static SYSTEM_SHELL: LazyLock = LazyLock::new(|| { + find_pwsh_in_programfiles(false, false) + .or_else(|| find_pwsh_in_programfiles(true, false)) + .or_else(|| find_pwsh_in_msix(false)) + .or_else(|| find_pwsh_in_programfiles(false, true)) + .or_else(|| find_pwsh_in_msix(true)) + .or_else(|| find_pwsh_in_programfiles(true, true)) + .or_else(find_pwsh_in_scoop) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or("powershell.exe".to_string()) + }); + + (*SYSTEM_SHELL).clone() +} + pub trait ResultExt { type Ok;