allow powershell scripts in the path to be executed (#15760)

# Description

This PR fixes a bug where powershell scripts were only allowed to be
executed if they were in the directory that you executed them from. This
fix allows the scripts to be anywhere in the path.

closes #15759

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2025-05-14 13:21:02 -05:00 committed by GitHub
parent 3d62753e80
commit 8c2b1a22d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -110,46 +110,47 @@ impl Command for External {
};
// let's make sure it's a .ps1 script, but only on Windows
let potential_powershell_script = if cfg!(windows) {
if let Some(executable) = which(&expanded_name, "", cwd.as_ref()) {
let (potential_powershell_script, path_to_ps1_executable) = if cfg!(windows) {
if let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) {
let ext = executable
.extension()
.unwrap_or_default()
.to_string_lossy()
.to_uppercase();
ext == "PS1"
(ext == "PS1", Some(executable))
} else {
false
(false, None)
}
} else {
false
(false, None)
};
// Find the absolute path to the executable. On Windows, set the
// executable to "cmd.exe" if it's a CMD internal command. If the
// command is not found, display a helpful error message.
let executable =
if cfg!(windows) && (is_cmd_internal_command(&name_str) || pathext_script_in_windows) {
PathBuf::from("cmd.exe")
} else if cfg!(windows) && potential_powershell_script {
// If we're on Windows and we're trying to run a PowerShell script, we'll use
// `powershell.exe` to run it. We shouldn't have to check for powershell.exe because
// it's automatically installed on all modern windows systems.
PathBuf::from("powershell.exe")
} else {
// Determine the PATH to be used and then use `which` to find it - though this has no
// effect if it's an absolute path already
let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else {
return Err(command_not_found(
&name_str,
call.head,
engine_state,
stack,
&cwd,
));
};
executable
let executable = if cfg!(windows)
&& (is_cmd_internal_command(&name_str) || pathext_script_in_windows)
{
PathBuf::from("cmd.exe")
} else if cfg!(windows) && potential_powershell_script && path_to_ps1_executable.is_some() {
// If we're on Windows and we're trying to run a PowerShell script, we'll use
// `powershell.exe` to run it. We shouldn't have to check for powershell.exe because
// it's automatically installed on all modern windows systems.
PathBuf::from("powershell.exe")
} else {
// Determine the PATH to be used and then use `which` to find it - though this has no
// effect if it's an absolute path already
let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else {
return Err(command_not_found(
&name_str,
call.head,
engine_state,
stack,
&cwd,
));
};
executable
};
// Create the command.
let mut command = std::process::Command::new(&executable);
@ -174,20 +175,10 @@ impl Command for External {
command.raw_arg(escape_cmd_argument(arg)?);
}
} else if potential_powershell_script {
use nu_path::canonicalize_with;
// canonicalize the path to the script so that tests pass
let canon_path = if let Ok(cwd) = engine_state.cwd_as_string(None) {
canonicalize_with(&expanded_name, cwd).map_err(|err| {
IoError::new(err.kind(), call.head, PathBuf::from(&expanded_name))
})?
} else {
// If we can't get the current working directory, just provide the expanded name
expanded_name
};
// The -Command flag followed by a script name instructs PowerShell to
// execute that script and quit.
command.args(["-Command", &canon_path.to_string_lossy()]);
command.args([
"-Command",
&path_to_ps1_executable.unwrap_or_default().to_string_lossy(),
]);
for arg in &args {
command.raw_arg(arg.item.clone());
}