I'm using Rust's std:process:Command
to call robocopy
in Windows. Unfortunately, it seems that somewhere along the execution of robocopy
, the relative path from the .exe is inserted before each directory.
Additionally, simple calls like net use
work with the same method, though I've only tested ones that don't rely on any directories.
Edit: updated the test folders to have spaces in their names.
Edit 2: the code has been updated with the solution. The solution was to call robocopy directly and .arg()
in everything individually. Old attempts are still commented in.
Instead of sending
cmd.exe /c robocopy "C:\Test\Test 1" "C:\Test\Test 2" /MIR /copy:DAT /MT:32 /Z /R:2 /W:03 /v /LOG:"C:\Test\Test 3\logfile.log"
and it running successfully, the output has a lot of errors:
ERROR 123 (0x0000007B) Opening Log File C:\relative\path\where\exe\is\located\"C:\Test\Test 3\logfile.log"
The filename, directory name, or volume label syntax is incorrect.
-------------------------------------------------------------------------------
ROBOCOPY :: Robust File Copy for Windows
-------------------------------------------------------------------------------
Started : Tuesday, January 8, 2019 7:35:41 AM
Source - C:\relative\path\where\exe\is\located\"C:\Test\Test 1"\
Dest - C:\relative\path\where\exe\is\located\"C:\Test\Test 2"\
Files :
Options : /V /S /E /DCOPY:DA /COPY:DAT /PURGE /MIR /Z /MT:32 /R:2 /W:3
------------------------------------------------------------------------------
ERROR : Invalid Parameter #10 : "/LOG:"C:\Test\Test 3\logfile.log""
This issue occurs using:
Rust version 1.3.1
Windows 10
Code that causes this for me is shown below. To use it you'll need to put a Test folder in your C:\ (or change it to wherever) and put inside that directory a Test 1 folder with some Testfile.txt to help show copying took place, as well as a Test 3 folder for the log to be in (not sure if the log can make its own folder - so just to be safe, premake one for it!). Basically you want:
C:\Test\Test 1\Testfile.txt (name or type of file doesn't matter)
C:\Test\Test 3\
robocopy
should run and make a C:\Test\Test 2 folder with Testfile.txt in it and put a logfile.log in C:\Test\Test 3 that details the transaction. In the end, the directories should look like this:
C:\Test\Test 1\Testfile.txt
C:\Test\Test 2\Testfile.txt
C:\Test\Test 3\logfile.log
The code follows:
//-----Import Built-in Libraries (not called crates)-----
use std::process::Command; //use cmd.exe
fn main()
{
let commandOpt1 = "/MIR"; //mirror directories
let commandOpt2 = "/copy:DAT"; //copy attributes
let commandOpt3 = "/MT:32"; //use 32 I/O threads (low CPU still, but better bandwidth utilization)
let commandOpt4 = "/Z"; //idr
let commandOpt5 = "/R:2"; //Retry twice
let commandOpt6 = "/W:03"; //Wait 3 sec between tries
let commandOpt7 = "/v"; //verbose logging
let commandLogStr = "/LOG:\"C:\\Test\\Test 3\\logfile.log\""; //record where the log file goes
let commandLogNoParenthStr = "/LOG:C:\\Test\\Test 3\\logfile.log"; //record where the log file goes
let command = format!("robocopy \"C:\\Test\\Test 1\" \"C:\\Test\\Test 2\" {} {} {} {} {} {} {} {}",
commandOpt1,commandOpt2,commandOpt3,commandOpt4,commandOpt5,commandOpt6,
commandOpt7,commandLogStr); //build the command
let commandStr: &str = &*command; //these two types of strings are
println!("TEST-Command for robocopy:{}",command);
//let m = Command::new("cmd.exe").arg("/c").arg(commandStr).output().unwrap(); //run cmd.exe net use
/*let m = Command::new("cmd.exe")
.arg("/c")
.arg("robocopy")
.arg("\"C:\\Test\\Test 1\"")
.arg("\"C:\\Test\\Test 2\"")
.arg(commandOpt1)
.arg(commandOpt2)
.arg(commandOpt3)
.arg(commandOpt4)
.arg(commandOpt5)
.arg(commandOpt6)
.arg(commandOpt7)
.arg(commandLogStr)
.output().unwrap(); //run cmd.exe net use */
let m = Command::new("robocopy")
.arg("C:\\Test\\Test 1")
.arg("C:\\Test\\Test 2")
.arg(commandOpt1)
.arg(commandOpt2)
.arg(commandOpt3)
.arg(commandOpt4)
.arg(commandOpt5)
.arg(commandOpt6)
.arg(commandOpt7)
.arg(commandLogNoParenthStr )
.output().unwrap(); //run cmd.exe net use */
let mOutput = String::from_utf8_lossy(&m.stdout); //get the output
println!("TEST-cmd output: {}",mOutput);
println!("TEST-cmd status: {}", m.status.success()); //reports success (true or false) of command
println!("TEST-cmd stderr: {}", String::from_utf8_lossy(&m.stderr)); //reports error words of command
let _ = Command::new("cmd.exe").arg("/c").arg("pause").status(); // wait for user input
}
The code gives two options, one to call robocopy
in one .arg()
addition and one with the .arg()
s split up. For me, they give the same result - but options are good!
Also, as a note, I send everything into Command
as a &str
but that seems to not be required - Strings
can be .arg()
s too.
I do not have a Windows machine nearby but I'm quite sure that the problem is in the quote characters "
.
Escape rules in CMD.exe are quite weird: sometimes you need quotes, sometimes you do not, and sometime you must not use them.
In your case, the log file, for example is chosen with the following option (removing Rust escapes): /LOG:"C:\Test\Test3\logfile.log"
. This is a file name that starts and ends with a quote. And since it does not start with a drive letter or a \
, the OS thinks: surely it is a relative path, let's look for C:\...\"C:\Test..."
!
The solution is easy: just remove all those quote.
What I don't understand is why are you calling cmd.exe /c
instead of just calling robocopy
directly. Quotes in the command line are used to guide the command line parser and properly manage filenames with spaces and so on. But if you do Command::new()
of robocopy
directly, then there is no need for quoting because the arguments are already passed separatedly.
User contributions licensed under CC BY-SA 3.0