diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index dc9015e..d1373b3 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -4407,15 +4407,8 @@ fn latest_release_tag(repo: &str) -> Result> { } let latest_url = format!("{web}/releases/latest"); let output = if cfg!(windows) { - Command::new("powershell.exe") - .arg("-NoProfile") - .arg("-ExecutionPolicy") - .arg("Bypass") - .arg("-Command") - .arg(windows_effective_url_script(&latest_url)) - .stdin(Stdio::null()) - .output() - .with_context(|| format!("resolve latest release for {web}"))? + let script = windows_effective_url_script(&latest_url); + windows_powershell_output(&script, &format!("resolve latest release for {web}"))? } else { Command::new("curl") .arg("-fsSL") @@ -4449,17 +4442,8 @@ fn release_tag_download_url(repo: &str, tag: &str, artifact: &str) -> Option Result { let status = if cfg!(windows) { - Command::new("powershell.exe") - .arg("-NoProfile") - .arg("-ExecutionPolicy") - .arg("Bypass") - .arg("-Command") - .arg(windows_url_reachable_script(url)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .with_context(|| format!("check {url}"))? + let script = windows_url_reachable_script(url); + windows_powershell_status(&script, &format!("check {url}"))? } else { Command::new("curl") .arg("-fsIL") @@ -4591,15 +4575,7 @@ fn run_update_installer( binary_version, ); let script = windows_deferred_update_script(std::process::id(), &script); - Command::new("powershell.exe") - .arg("-NoProfile") - .arg("-ExecutionPolicy") - .arg("Bypass") - .arg("-Command") - .arg(script) - .stdin(Stdio::null()) - .spawn() - .context("start deferred dosh update installer")?; + windows_powershell_spawn(&script, "start deferred dosh update installer")?; println!("dosh update started; the installer will run after this process exits"); return Ok(()); } @@ -4790,6 +4766,77 @@ fn windows_deferred_update_script(parent_pid: u32, installer_script: &str) -> St ) } +fn windows_powershell_command_candidates() -> &'static [&'static str] { + &["powershell.exe", "powershell", "pwsh.exe", "pwsh"] +} + +fn windows_powershell_command(candidate: &str, script: &str) -> Command { + let mut command = Command::new(candidate); + command + .arg("-NoProfile") + .arg("-ExecutionPolicy") + .arg("Bypass") + .arg("-Command") + .arg(script) + .stdin(Stdio::null()); + command +} + +fn windows_powershell_output(script: &str, context: &str) -> Result { + let mut missing = Vec::new(); + for candidate in windows_powershell_command_candidates() { + match windows_powershell_command(candidate, script).output() { + Ok(output) => return Ok(output), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + missing.push(*candidate); + } + Err(err) => return Err(err).with_context(|| format!("{context} using {candidate}")), + } + } + Err(anyhow!( + "{context}: no PowerShell executable found; tried {}", + missing.join(", ") + )) +} + +fn windows_powershell_status(script: &str, context: &str) -> Result { + let mut missing = Vec::new(); + for candidate in windows_powershell_command_candidates() { + match windows_powershell_command(candidate, script) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + Ok(status) => return Ok(status), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + missing.push(*candidate); + } + Err(err) => return Err(err).with_context(|| format!("{context} using {candidate}")), + } + } + Err(anyhow!( + "{context}: no PowerShell executable found; tried {}", + missing.join(", ") + )) +} + +fn windows_powershell_spawn(script: &str, context: &str) -> Result { + let mut missing = Vec::new(); + for candidate in windows_powershell_command_candidates() { + match windows_powershell_command(candidate, script).spawn() { + Ok(child) => return Ok(child), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + missing.push(*candidate); + } + Err(err) => return Err(err).with_context(|| format!("{context} using {candidate}")), + } + } + Err(anyhow!( + "{context}: no PowerShell executable found; tried {}", + missing.join(", ") + )) +} + fn powershell_string(value: &str) -> String { format!("'{}'", value.replace('\'', "''")) } @@ -10588,8 +10635,9 @@ mod tests { update_binary_version_for_installer, update_installer_url, update_version_status, upsert_managed_block, valid_forward_host, vscode_command_candidates, vscode_safe_alias, wake_repaint_retry_deadline, windows_command_word, windows_deferred_update_script, - windows_effective_url_script, windows_mode_from_readonly, windows_readonly_from_mode, - windows_update_script, windows_url_reachable_script, windows_vt_output_mode, + windows_effective_url_script, windows_mode_from_readonly, + windows_powershell_command_candidates, windows_readonly_from_mode, windows_update_script, + windows_url_reachable_script, windows_vt_output_mode, }; use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::native::EnvVar; @@ -12978,6 +13026,14 @@ mod tests { assert!(!reachable.contains("curl")); } + #[test] + fn windows_update_tries_classic_and_core_powershell_hosts() { + assert_eq!( + windows_powershell_command_candidates(), + &["powershell.exe", "powershell", "pwsh.exe", "pwsh"] + ); + } + #[test] fn update_uses_local_tag_when_release_latest_is_older() { assert_eq!(