Here’s a test example using a cmd /c
+ env
workaround. I leave it here for anyone who needs a temp workaround until we work out a long term answer
NOTE: I have reworked this to use powershell
because cmd
complains about network paths. This was more difficult that I had anticipated so I’ve put the updated version here for people who aren’t as versed with powershell’s eccentricities
//mod windows_runner;
fn main() {
let quote_test: &str = r#" one" two"" three""" four"""" five""""" "#;
{ // to show you can collect stdout
let stdout_test: String = windows_runner::run("write-host", quote_test, "");
println!("{}",stdout_test);
}
{ // to check it passes stdin correctly, also shows you can call without argument
let stdin_test: String = windows_runner::run("nslookup",r#""#,"google.com\nexit\n");
println!("{}",stdin_test);
}
{ // to check (with procexp) that the arguments actually pass exactly as given (including without surrounding quotes), though it does add one extra space between program and arguments
windows_runner::run("notepad", quote_test, "");
}
}
mod windows_runner{
use std::{thread, time, str};
use std::process::{Command, Stdio};
use std::io::Write;
pub fn run (program:&str,arguments:&str,stdin:&str) -> String /*(String,String)*/ {
let launcher = "powershell.exe";
let build_string: String;
{
if arguments.trim() == "" { // no arguments (powershell gets confused if you try to execute a program with an empty array as the argument set)
build_string = format!(r#"& '{}'"#,program);
}
else {
let mut arguments_reformatting: Vec<&str> = Vec::new();
for argument in arguments.split(" ") {
arguments_reformatting.push(argument);
}
let arguments_reformatted = arguments_reformatting.join("','");
build_string = format!(r#"& '{}' @('{}')"#,program,arguments_reformatted); // powershell digests: & 'pro gram' @('argument1','argument2') => "pro gram" argument1 argument2
}
}
let launch_command: &[String] = &[build_string];
let mut child = Command::new(launcher)
.args(launch_command)
.stdout(Stdio::piped())
.stdin(Stdio::piped()) // disable this if you want the user to be able to speak with the child instead of doing it yourself
/*.stderr(Stdio::piped())*/ // if you want to collect stderr instead of displaying to user
.spawn()
.expect("failed to run child program");
{ // send stdin, disable this if you want the user to be able to speak with the child instead of doing it yourself
let stdin_handle = child.stdin.as_mut().expect("Failed to get stdin");
stdin_handle.write_all(stdin.as_bytes()).expect("Failed to write to stdin");
}
// would you kindly wait for the child to finish
let check_every = time::Duration::from_millis(10);
loop {
match child.try_wait() {
Ok(Some(_status)) => {break;}, // finished running
Ok(None) => {} // still running
Err(e) => {panic!("error attempting to wait: {}", e)},
}
thread::sleep(check_every);
}
let output = child
.wait_with_output()
.expect("failed to wait on child");
let stdout: String = String::from_utf8_lossy(&output.stdout).to_string();
/*{ // if you want to collect stderr instead of displaying to user
let stderr: String = String::from_utf8_lossy(&output.stderr).to_string();
(stdout,stderr)
}*/
stdout
}
}