I'm trying to do this:
cmd.exe /C "C:\Program Files\Somewhere\SomeProgram.exe" > "C:\temp\Folder Containing Spaces\SomeProgram.out"
However, I have problems which are down to the way cmd.exe works. If you read the help for it, it handles " characters in a special way. See the help at the end of question. So, this doesn't execute correctly... I'm guessing cmd.exe strips some quotes which makes the statement ill-formed.
I can do this successfully:
// quotes not required around folder with no spaces
cmd.exe /C "C:\Program Files\Somewhere\SomeProgram.exe" > C:\temp\FolderWithNoSpaces\SomeProgram.out
But, I really need the first one to work. Is there away around the strange quote processing that cmd.exe uses? I want it to preserve all of the quotes, but there doesn't appear to be an option to make it do that.
Help taken from output of: cmd /?
If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:
1. If all of the following conditions are met, then quote characters
on the command line are preserved:
- no /S switch
- exactly two quote characters
- no special characters between the two quote characters,
where special is one of: &<>()#^|
- there are one or more whitespace characters between the
the two quote characters
- the string between the two quote characters is the name
of an executable file.
2. Otherwise, old behavior is to see if the first character is
a quote character and if so, strip the leading character and
remove the last quote character on the command line, preserving
any text after the last quote character.
Ah. doh. Think I've answered my own question.
If you use /S, and wrap the whole thing in quotes, it just removes those outer quotes.
cmd.exe /S /C " do what you like here, quotes within the outermost quotes will be preserved "
I think you'll find that your example works absolutely fine as it is.
cmd.exe /C "C:\Program Files\Somewhere\SomeProgram.exe" > "C:\temp\Folder Containing Spaces\SomeProgram.out"
I have reproduced your example here
http://pastebin.com/raw.php?i=YtwQXTGN
C:\>cmd /c "c:\Program Files\my folder\my long program.exe" > "c:\temp\spaces are here\a.a"
C:\>type "c:\temp\spaces are here\a.a"
my long program.exe has run
C:\>
further example demonstrating it works with "my long program.exe", removing cmd /c, it operates fine too.
C:\>"c:\Program Files\my folder\my long program.exe" > "c:\temp\spaces are here\
a.a"
C:\>type "c:\temp\spaces are here\a.a"
my long program.exe has run
C:\>
Another example, but with replace. replace with no parameters says "source path required" "no files replaced"
C:\>replace > a.a
Source path required
C:\>type a.a
No files replaced
Exactly the same effect when they're in folders with spaces.
C:\>cmd /c "c:\Program Files\my folder\replace.exe" > "c:\temp\spaces are here\r.r"
Source path required
C:\>type "c:\temp\spaces are here\r.r"
No files replaced
C:\>
further demonstration with replace
without cmd /c works fine too.
C:\>"c:\Program Files\my folder\replace.exe" > "c:\temp\spaces are here\r.r"
Source path required
C:\>type "c:\temp\spaces are here\r.r"
No files replaced
C:\>
The reason why your example works fine
cmd.exe /C "C:\Program Files\Somewhere\SomeProgram.exe" > "C:\temp\Folder Containing Spaces\SomeProgram.out"
and how/why it works the way it does, is because the > is interpreted as special by the host.exe So this part cmd.exe /C "C:\Program Files\Somewhere\SomeProgram.exe" - I think - is evaluated first. i.e. cmd /c does not see the > and after.
cmd /? shows 2 cases
Case 1 and Case 2. Your example fits Case 1
If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:
1. If all of the following conditions are met, then quote characters
on the command line are preserved:
- no /S switch
- exactly two quote characters
- no special characters between the two quote characters,
where special is one of: &<>()#^|
- there are one or more whitespace characters between the
two quote characters
- the string between the two quote characters is the name
of an executable file.
2. Otherwise, old behavior is to see if the first character is
a quote character and if so, strip the leading character and
remove the last quote character on the command line, preserving
any text after the last quote character.
You can test for sure that your example fits case 1, because if you add /s (without adding any more quotes or making any change at all to your example other than adding /s), then you get a different result, because it makes your example hit case 2. So that proves that your example is definitely a case 1. And it clearly meets all the criteria of case 1.
If your example were a case 2, and you added /s, then it'd make no difference.
Your answer is interesting because it shows an alternative way of getting your result, but in case 2. By adding additional outter quotes and adding /s.
But actually, when you add those additional outter quotes, then you've just made it a case 2, and adding a /s on top of that won't make a difference.
C:\>cmd /c "c:\Program Files\my folder\replace.exe"
Source path required
No files replaced
C:\>cmd /s /c "c:\Program Files\my folder\replace.exe"
'c:\Program' is not recognized as an internal or external command,
operable program or batch file.
C:\>cmd /c ""c:\Program Files\my folder\replace.exe""
Source path required
No files replaced
C:\>cmd /s /c ""c:\Program Files\my folder\replace.exe""
Source path required
No files replaced
C:\>
The example in your question worked fine
cmd.exe /C "C:\Program Files\Somewhere\SomeProgram.exe" > "C:\temp\Folder Containing Spaces\SomeProgram.out"
Your alternative (with the /S and outer quotes) you give as an answer to make the example work, works fine too
cmd.exe /S /C ""C:\Program Files\Somewhere\SomeProgram.exe" > "C:\temp\Folder Containing Spaces\SomeProgram.out""
Though your answer which is an alternative, can actually be simplified by removing the /S because it's already a case 2, so adding /s won't make any difference. So this would improve the solution given in your answer
cmd.exe /C ""C:\Program Files\Somewhere\SomeProgram.exe" > "C:\temp\Folder Containing Spaces\SomeProgram.out""
Your example which you described as a problem in your question, and your solution, produce the same good result. But one big difference I suppose, (and I am not sure how to test for it), but one difference in the way your example works, and the way the solution in your answer works, is I think in the case of your example, the hosting/invoking cmd.exe does the redirect to the file. Whereas in your solution's example, the invoked cmd.exe is passed the > by the host cmd.exe, and so the invoked cmd.exe does the redirect. Also of course, your example is a case 1, while your solution is an amendment you made (very well) to make it work in case 2.
I hope I haven't erred here, I may have. But your question and answer did help me wrap my head around how cmd and in particular cmd /c is working!
Perhaps your example was an oversimplification of your actual one, and your actual one did fail and needed your amendment. If your example case, had been a tiny bit more complex, by for example, having a parameter to the program that took quotes, then it'd fail Case 1, and you would indeed need outter quotes (/S would not change the result, so no /S would be necessary, as it'd already be a case 2 once you add those needed outer quotes). But the example you gave in your question actually seems to me to work fine.
Added - A related Q and A What is `cmd /s` for?
Related
In order to ask for the version of a file, the following commandline request can be launched:
wmic datafile where name="C:\\Windows\\System32\\msiexec.exe" get Version, Name /Value
The result looks like:
Name=C:\Windows\System32\msiexec.exe
Version=5.0.19041.1
I would like to launch this request for all specific DLL files in all subdirectories somewhere on my computer.
So I started as follows:
forfiles /S /M specific.dll /C "cmd /c echo #path"
I received the entire list of specific DLL files, mentioned as entire path names, which was exactly what I wanted. Now I would like to combine both approaches and you can guess, this is going wrong:
forfiles /S /M specific.dll /C "cmd /c wmic datafile where name="#path" get Version, Name"
I get following results:
Node - PORT-DDM (this is the name of my computer)
ERROR:
Description = Invalid query
You might think "Obviously, you are using double quotes within double quotes", but replacing the internal double quotes (") by double double quotes (like name=""#path"") does not solve the issue, and using single quotes neither. (Please don't tell me the issue is due to the single backslashes in #path)
Does anybody know how I can solve this, preferably without using a simple for-loop: I'm trying to understand how to replace the UNIX find ... -exec by Windows forfiles ... /C "cmd /c ..." ....
Edit: Maybe the question can be more general: apparently there is not one question on the entire site, containing the tags [forfiles] and [wmic]: is it possible that both commands don't go well together and why?
Edit 3 (SOLUTION):
As michael_heath described in his answer, the issue came down to two things: how exactly windows builds commands when executing something through a shortcut, and the very specific (and frankly ridiculous) consequences of the \C switch on cmd.exe. For future reference, if any other poor soul stumbles into this StackOverflow question, the problem was fixed by changing the "Target" property in the shortcut to a slightly edited version of Michael's answer, specifically C:\Windows\System32\cmd.exe /C #"C:\{path-to-script}\link.bat". Here's a screenshot too, if necessary, although you unfortunately can't see the whole Target line.
Huge thanks again to Michael.
I am attempting to make a personal batch utility to create a symlink on the desktop, much in the same way "Send to... > Desktop" works with shortcuts. I use symbolic links frequently to allow things like my bash configuration files (.bashrc and .bash_profile, etc) to be version controlled elsewhere for portability, and for several other things on my computer.
For ease of use, my idea was to create a simple batch file to do this for me, and place the symlink on the desktop. Then, I would put a shortcut to this file in the Send To folder so it appears in Send To in the context menu (I am aware that mklink requires admin privileges, so the shortcut is set to run as administrator also).
The following is the file I have written:
#echo off
set f=%~1
set switch=
if exist "%f%\*" set switch=/D
for /F "delims=" %%i in ("%f%") do set name=%%~nxi
mklink %switch% "%USERPROFILE%\Desktop\%name%" "%f%"
if not %ERRORLEVEL%==0 pause
Here is the general idea of what I'm trying to do:
Strip the quotes on the input, if any (%~1)
Check if the input is a directory
Get the base name and file extension of the input (%%~nxi)
Make the link
If an error occurred, pause so it can be seen rather than exiting (because the batch file is called from the shortcut)
It works perfectly fine until I give it an input that contains spaces in the name of the file or directory. I actually haven't tested what happens if there is a directory with a space in the path, but not in the base name of the actual file or directory, but I assume the same problem will be present.
I have made several changes to attempt to get it to work with files with spaces, including stripping the quotes on the input in that first line so that the quotes aren't doubled later, and that "delims=" thing on the for loop. Those two solutions I found here, actually.
But despite my best efforts, no matter what I do, the file closes immediately when given an input with a space. I have littered every line with pauses, run the script from the command line with a manually entered input so it would not exit, and run each individual command (where possible) from the command line.
Infuriatingly, when I run it from the command line or run the individual commands, it all works perfectly even with spaces in the input. I even created another batch file that does nothing but output the input it receives and ran that from Send To, and confirmed that the input is the same as I entered from the command line.
What on Earth is going wrong then when called from that shortcut in Send To?
Edit 1: The properties of the shortcut itself are as follows:
Target: C:\Users{username}\vc\git\util-scripts\bat\link.bat
Start in: C:\Users{username}\vc\git\util-scripts\bat
Here is a screenshot as well:
Because I just made my account here I can't embed the picture but here it is
Edit 2: This is the current code I am using, as suggested by Gerhard Barnard, however the problem still persists:
#echo off
set "fname=%~1"
set switch=
if exist "%fname%\.*" set "switch=/D"
for /F "delims=" %%i in ("%fname%") do (
mklink %switch% "%USERPROFILE%\Desktop\%%~nxi" "%fname%"
if errorlevel 1 pause
)
#echo off
setlocal
rem Set the path for the created symlink.
set "linkdir=%USERPROFILE%\Desktop"
for %%A in (%*) do call :link "%%~A"
rem Check results.
echo: & dir /A:L "%linkdir%" & pause
exit /b
:link
set "switch="
if exist "%~1\*" set "switch=/D"
for /F "delims=" %%A in ("%~1") do set "name=%%~nxA"
if not defined name echo Variable "name" not defined.& exit /b 1
mklink %switch% "%linkdir%\%name%" "%~1"
exit /b 0
Your batch-file code is working.
This code does multiple file or folders.
This code also helped to test all at once,
files, folders, symlinked files and symlinked
folders as targets.
If you only want 1 target to be processed,
then just change the %* to "%~1".
The main issue is the command string in the shortcut.
C:\Users\{username}\vc\git\util-scripts\bat\link.bat
The file type of .bat is going to build a command such as:
C:\Windows\System32\cmd.exe /C "C:\Users\{username}\vc\git\util-scripts\bat\link.bat" %*
%* is substituted with the passed arguments.
If you have the command with an argument with double quotes,
it may look like:
C:\Windows\System32\cmd.exe /C "C:\Users\{username}\vc\git\util-scripts\bat\link.bat" "C:\Users\a file.txt"
The command string after /C has 4 double quotes
and double quotes are at both ends.
The behavior changes due to the double quotes.
A quote from cmd /?:
If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:
1. If all of the following conditions are met, then quote characters
on the command line are preserved:
- no /S switch
- exactly two quote characters
- no special characters between the two quote characters,
where special is one of: &<>()#^|
- there are one or more whitespace characters between the
two quote characters
- the string between the two quote characters is the name
of an executable file.
2. Otherwise, old behavior is to see if the first character is
a quote character and if so, strip the leading character and
remove the last quote character on the command line, preserving
any text after the last quote character.
In section 1, "no /S switch" is true, then the next is
"exactly two quote characters" which is false.
This now applies section 2, which can make the
command string after /C with stripped double quotes:
C:\Users\{username}\vc\git\util-scripts\bat\link.bat" "C:\Users\a file.txt
The space is quoted though the rest is exposed,
which is an invalid command string in this case.
Note that C:\Users\a file.txt is an example
passed argument that was double quoted as
"C:\Users\a file.txt".
A change in the command string in the shortcut to:
C:\Windows\System32\cmd.exe /C echo: & "C:\Users\{username}\vc\git\util-scripts\bat\link.cmd"
The command is now C:\Windows\System32\cmd.exe which
gives some more control with the command string.
After the /C, echo: is used to avoid the command
string beginning with a double quote, which helps to
prevent double quotes being stripped at both ends because
the command string no longer starts with a double quote.
After the & is the command that is important and will
now work even if the arguments end with a double quote.
You can replace the echo: & with another initial command.
You can also use # before the command string, so the shortcut
command string would be:
C:\Windows\System32\cmd.exe /C #"C:\Users\{username}\vc\git\util-scripts\bat\link.cmd"
So whatever works the best for your use case.
Try without the /F option which is not needed here, we also do not need to set a variable as it will work perfectly fine with the expanded meta variable:
#echo off
set "fname=%~1"
set switch=
if exist "%fname%\.*" set "switch=/D"
for %%i in ("%fname%") do (
echo mklink %switch% "%USERPROFILE%\Desktop\%%~nxi" "%fname%"
if errorlevel 1 pause
)
I have the same task as Print Windows %PATH% with every entry on new line
I have a task: print all entries of %PATH% variable on new line. For example:
C:\Program Files\
C:\Windows
C:\Windows\System32
and so on...
which is achievable by echo %path:;=&echo.% as explained here
And I want to assign it to a command alias using DOSKEY
But the following script is failing for me
DOSKEY list=echo %path:;=&echo.%
Which just prints all of the paths while I'm defining the DOSKEY, and after that, when I try to invoke list command, it just doesn't work.
As mentioned by #eryksun you'll need to escape special chars.
Update: And to prevent a premature expanding - will use delayed expansion.
doskey list=cmd /v:on /c "for %p in ("!path:;=" "!") do #echo %~p"
An explanation of how this work: we can't use directly solution with echo ^%path:;=^&echo.^% because it's stored in macros already expanded. To prevent this I've tried to use delayed expansion (the part with cmd /v:on /c). It seemed almost working but without splitting on new lines (adding & for some reason doesn't work with delayed expansion).
Then I started to search a way to split !path! and found a neat trick with replacing ; to " " (double quoted space) ("!path:;=" "!"). This splitting was good enough for a plain for (without /f option). The rest is obvious.
I am trying to run the following command:
forfiles /p ..\Schemas /m *.xsd /c "cmd /c ""C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe #path /classes"""
However, it fails with:
ERROR: Invalid argument/option - 'Files'.
Type "FORFILES /?" for usage.
These also don't work:
forfiles /p ..\Schemas /m *.xsd /c "cmd /c \"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe\" #path /classes"
forfiles /p ..\Schemas /m *.xsd /c "cmd /c ^"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe^" #path /classes"
It's definitely failing because the path being passed to cmd contains spaces. You normally solve this by quoting the whole argument. However, there doesn't appear to be a way to pass the double quote.
So, how do you run a command with a full path from the Windows forfiles command?
In your first attempt, you tried to put (additional) quotation marks around the entire command line after cmd /c rather than the path to the executable only, which will fail as cmd tries to find and execute the whole line as a command. Hence you needed to move the closing quotes immediately after xsd.exe instead of at the end of the command line.
Nevertheless, in general, the main problem here is that cmd and forfiles handle quotation marks differently. forfiles uses \" to escape quoting, but cmd does not care about the backslash. In addition, in case your code appears within a parenthesised block of code, the literal parentheses in your path may cause trouble additionally if they do not appear quoted to cmd. Finally, cmd may attempt to strip off quotation marks (unexpectedly), which may cause even more problems.
To solve all this, use another method of providing literal quotation marks for the command line after /C: forfiles supports specifying characters by their hexadecimal code in 0xHH notation; so stating 0x22 hides the quotation marks from the parent cmd instance until the command line is actually executed:
forfiles /p ..\Schemas /m *.xsd /c "cmd /c 0x220x22C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe0x22 #path /classes0x22"
This results in the following command line to be executed (using "D:\Data\Schemas\sample.xsd" as an example value for #path):
cmd /c ""C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe" "D:\Data\Schemas\sample.xsd" /classes"
Finally, after stripping off the outer-most quotes by cmd, the following command line is executed:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe" "D:\Data\Schemas\sample.xsd" /classes
Note that #path, as well as all other file-name- and path-related #-style variables of forfiles expand to already quoted values.
Initially, I completely forgot about the fact that cmd tries to strip off quotes. Klitos Kyriacou's answer reminded me of that; so if you like my answer, please do not forget to give credits to them as well!
It's not forfiles that's at fault here; it's the way cmd.exe treats quotation marks. It's essentially trying to be too clever for its own good. The CMD /C command can take any arbitrary line as a command, including one that contains spaces; however, if the command line given after CMD /C starts with a quotation mark, it removes the first and last quotation marks from the line before it tries to execute it. Thus, these two lines are exactly equivalent:
cmd /c echo hello
cmd /c "echo hello"
Now the problem is that the quotation mark gets stripped off in the following command:
cmd /c "C:\Program Files\xyz\abc.exe" one two
To make it do what you want, you need to enclose the whole thing in quotes (the inner quotes don't need to be escaped):
cmd /c ""C:\Program Files\xyz\abc.exe" one two"
Now, you can put this into a FORFILES command. FORFILES requires quotation marks to be escaped with a \, so we need to write:
forfiles /c "cmd /c \"\"C:\Program Files\xyz\abc.exe\" one two\""
Or in your specific case:
forfiles /p ..\Schemas /m *.xsd /c "cmd /c \"\"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe\" #path /classes\""
(Note that all quotation marks for CMD are escaped, but the outer quotation marks are for FORFILES so are not escaped.)
When I use Windows Run tool to call my exe with argument it work fine
I did below
cmd.exe /C "C:\ex\abc.exe" skk
Then my exe get a hit and i got skk as an argument. BUt I i did
cmd.exe /C "C:\ex\abc.exe" "sk k" then my exe does not call. Why?
But the same thing work fine in cmd line
"C:\ex\abc.exe" "sk k"
This is by design
If more than two quote characters are present after the /C switch, then "behavior is to see if the first character is a quote character and if so, strip the leading character and remove the last quote character on the command line, preserving any text after the last quote character.", unless the following conditions are met:
no /S switch
exactly two quote characters
no special characters between the two quote characters, where special is one of: &<>()#^|
there are one or more whitespace characters between the two quote characters
the string between the two quote characters is the name of an executable file.
So, when you do
cmd.exe /C "C:\ex\abc.exe" "sk k"
it's trying to execute
C:\ex\abc.exe" "sk k
which, obviously, doesn't work. If you want to run your exe with an argument containing the whitespace, try
cmd.exe /C C:\ex\abc.exe "sk k"
Or wrap the the whole command in double quotes like so:
cmd.exe /C ""C:\New Folder\abc.exe" "sk k""