From 37a9c1b7b8072afd2de4871ab9835548d785e3cd Mon Sep 17 00:00:00 2001 From: egasato Date: Wed, 11 Feb 2026 21:58:21 +0100 Subject: [PATCH] Force UTF-8 with BOM for Windows PowerShell shebang recipes Windows PowerShell (`powershell.exe`) is included in all modern Windows installs, and it's usual to leverage it when a recipe is complex enough to require basic control flow constructs (which `cmd.exe` usually lacks or it's unreadable). The default encoding (codepage) used by `conhost.exe` (Console Window Host) may vary depending on the display language and region of the system (e.g. 437 for US English, 850 for Western European languages), but it's certainly never UTF-8 (codepage 65001; still an experimental feature that has to be enabled system-wide). Forcing the UTF-8 Byte Order Mark to be present when the shebang recipe is for Windows PowerShell fixes many output encoding issues. --- src/lib.rs | 1 + src/recipe.rs | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b633a1d65d..453cb511a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ pub(crate) use { expression::Expression, format_string_part::FormatStringPart, fragment::Fragment, + fs::File, function::Function, interpreter::Interpreter, invocation::Invocation, diff --git a/src/recipe.rs b/src/recipe.rs index daa63c910c..751ead13bd 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -467,10 +467,24 @@ impl<'src, D> Recipe<'src, D> { eprintln!("{}", config.color.doc().stderr().paint(&script)); } - fs::write(&path, script).map_err(|error| Error::TempdirIo { - recipe: self.name(), - io_error: error, - })?; + let interpreter = match &executor { + Executor::Shebang(shebang) => shebang.interpreter, + Executor::Command(command) => command.command.as_str(), + }; + let preamble = match interpreter.to_lowercase().as_str() { + "powershell" | "powershell.exe" => "\u{FEFF}", + _ => "", + }; + + File::create(&path) + .and_then(|mut f| { + f.write_all(preamble.as_ref())?; + f.write_all(script.as_ref()) + }) + .map_err(|error| Error::TempdirIo { + recipe: self.name(), + io_error: error, + })?; let mut command = executor.command( config,