Page 1 of 2
Execute UNC path containing literal %, in command line mode
Posted: 03 Jul 2021 20:52
by noah
Suppose there's a batch file at "\\server\share\foo %Path%bar.bat". That is, the path contains the two percent signs literally. Is there a way to execute this file from command line mode? From what I've tried among techniques from
viewtopic.php?t=9124 and
viewtopic.php?t=9445, there is not. Here's a Perl program exercising those:
Code: Select all
use strict;
use warnings;
use Win32::Process;
my($exe, $cmd);
$exe = q{C:\windows\system32\cmd.exe};
# Phase 7.3 says, "If in command line mode and the command is not quoted and
# does not begin with a volume specification, white-space, `,`, `;`, `=` or `+`
# then break the command token at the first occurrence of `<space>` `,` `;` or
# `=` ..."
$cmd = <<\_EOF;
:: Quoted FAILS: no way to suppress percent expansion (Phase 1)
/c ""c:\subdir\foo %Path%bar.bat""
:: Unquoted w/ volume specification WORKS.
/c "c:\subdir\foo^ ^%Path^%bar.bat"
:: Unquoted w/ UNC, w/o special first byte FAILS: Phase 7 breaks token
/c "\\localhost\c$\subdir\foo^ ^%Path^%bar.bat"
:: Unquoted w/ UNC, w/ special first byte FAILS: first byte corrupts UNC path
/c "^ \\localhost\c$\subdir\foo^ ^%Path^%bar.bat"
_EOF
chomp $cmd;
STDOUT->autoflush;
foreach my $c (split /\n/, $cmd) {
next if $c =~ /^$/ or $c =~ /^::/;
print "==== $c\n";
Win32::Process::Create(my $p, $exe, $c, 1, 0, ".");
$p->Wait(INFINITE) || die;
print "\n";
}
One could write and execute a temporary batch file, thereby gaining access to %% escaping in Phase 1. Is there a way not involving a temporary file?
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 03:59
by aGerman
Is this a question about Perl, noah? I don't get from where you're trying to call the path containing percents.
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 05:10
by noah
No. I found Perl convenient, but here's an equivalent batch file:
Code: Select all
:: Quoted FAILS: no way to suppress percent expansion (Phase 1)
cmd /c ""c:\subdir\foo %%Path%%bar.bat""
:: Unquoted w/ volume specification WORKS.
cmd /c "c:\subdir\foo^ ^%%Path^%%bar.bat"
:: Unquoted w/ UNC, w/o special first byte FAILS: Phase 7 breaks token
cmd /c "\\localhost\c$\subdir\foo^ ^%%Path^%%bar.bat"
:: Unquoted w/ UNC, w/ special first byte FAILS: first byte corrupts UNC path
cmd /c "^ \\localhost\c$\subdir\foo^ ^%%Path^%%bar.bat"
(The underlying use case is essentially a C program that executes an arbitrarily-named batch file via
https://docs.microsoft.com/en-us/window ... teprocessa)
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 07:02
by aGerman
OK I see. First of all don't compare it with the processing in a Batch file. Eventually it's being more difficult because CreateProcessA will execute it in command-line mode. Basically working with percents is not a big deal. It's gonna be difficult if they enclose an existing variable name (like 'path' in your case).
There are a couple more things to consider if another language is involved. In case of C backslashes and quotes in a string have to be escaped. And what I'd like to know before doing some test is:
- Do you want to pass NULL to lpApplicationName and have everything in the lpCommandLine parameter? or
- Do you want to pass the path of cmd.exe to lpApplicationName and only have the cmd arguments in lpCommandLine?
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 07:32
by noah
All else being equal, I prefer a solution that complies with the CreateProcessA() specification. It says, "To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file." That implies:
> - Do you want to pass NULL to lpApplicationName and have everything in the lpCommandLine parameter? or
No.
> - Do you want to pass the path of cmd.exe to lpApplicationName and only have the cmd arguments in lpCommandLine?
Yes. However, if changing that answer changes the goal from unachievable to achievable, I'd consider it.
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 07:39
by aGerman
Haha, I don't know if it would ever help. However, I know that the behavior of CreateProcessA changes, and I just want to reflect your preferences in my tests.
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 09:45
by aGerman
No success
"%path%foo bar.bat" on my desktop:
Code: Select all
@echo off &setlocal
echo Works.
pause
C testcode:
Code: Select all
#include <stddef.h>
#include <windows.h>
int main(void)
{
const char cmd_path[] = "C:\\Windows\\System32\\cmd.exe";
char cmd_line[1024] = " /c \"\"\\\\localhost\\C$\\Users\\%username%\\Desktop\\^%path^%foo bar.bat\"\"";
STARTUPINFOA start_info = { sizeof(STARTUPINFOA) };
PROCESS_INFORMATION proc_info = { 0 };
if (CreateProcessA(cmd_path, cmd_line, NULL, NULL, FALSE, 0UL, NULL, NULL, &start_info, &proc_info))
{
WaitForSingleObject(proc_info.hProcess, INFINITE);
CloseHandle(proc_info.hThread);
CloseHandle(proc_info.hProcess);
return 0;
}
return 1;
}
Result:
Der Befehl ""\\localhost\C$\Users\neffe\Desktop\^%path^%foo bar.bat"" ist entweder falsch geschrieben oder konnte nicht gefunden werden.
It expands %username%. It can't expand %path^% but leaves the carets.
Using
Code: Select all
char cmd_line[1024] = " /v:on /c \"\"\\\\localhost\\C$\\Users\\%username%\\Desktop\\%!!path%foo bar.bat\"\"";
I get
Der Befehl ""\\localhost\C$\Users\neffe\Desktop\%!!path%foo bar.bat"" ist entweder falsch geschrieben oder konnte nicht gefunden werden.
I would have expected that the pair of exclamation points are expanded to an empty string because of the /v:on option. But they are still there.
I have the indication that CreateProcess already performs an ExpandEnvironmentStrings-like operation on the lpCommandLine parameter.
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 11:07
by aGerman
OK defining an additional variable containing the percentage sign did the trick.
Code: Select all
char cmd_line[1024] = " /v:on /c \"set \"perc=%\" & \"\\\\localhost\\C$\\Users\\%username%\\Desktop\\!perc!path!perc!foo bar.bat\"\"";
For those who are lost in the mess of backslashes, it's similar to this command:
Code: Select all
cmd.exe /v:on /c "set "perc=%" & "\\localhost\C$\Users\%username%\Desktop\!perc!path!perc!foo bar.bat""
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 04 Jul 2021 17:35
by noah
That is a nice technique. However, that enables delayed expansion inside the batch file, not just for the command line. Just like I don't control the batch file name, I don't control whether the batch file is compatible with delayed expansion. Is there a variant that allows the batch file to start with delayed expansion set to its default?
Re: Execute UNC path containing literal %, in command line mode
Posted: 05 Jul 2021 00:24
by jeb
Hi,
yes it works without delayed expansion
Code: Select all
cmd.exe /c "for %# in (%) DO "\\localhost\C$\Users\%username%\Desktop\%#path%#foo bar.bat""
It's not bullet proof, but it should work in the most cases.
It could fail, if a variables exist like
Code: Select all
set "# in (=FAIL"
or
set ") DO "\\localhost\C$\Users\=FAIL TOO"
But as said, this should be uncommon
jeb
Re: Execute UNC path containing literal %, in command line mode
Posted: 05 Jul 2021 10:05
by aGerman
Yeah, that works in C too, jeb. Just added the @ to suppress the prompt which would also show up in the console of the C app.
Code: Select all
char cmd_line[1024] = " /c \"for %# in (%) do @\"\\\\localhost\\C$\\Users\\%username%\\Desktop\\%#path%#foo bar.bat\"\"";
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 05 Jul 2021 21:04
by noah
That passed every test I had written. Thank you. I see that an environment variable named "#path" will disrupt this. While the environment and the batch file name are not hostile, I see two ways to achieve robustness against that:
- Before calling CreateProcessA(), simulate percent expansion on cmd_line. If percent expansion does not change the string, proceed to CreateProcessA(). Otherwise, change the loop parameter name (#, A, B, ... Z, ...) and restart the simulation.
- Inject the percent signs via an environment variable:
Code: Select all
char cmd_line[1024] = " /c \"set \"my_percent=\" & \"\\\\localhost\\C$\\Users\\%username%\\Desktop\\%my_percent%path%my_percent%foo bar.bat\"\"";
SetEnvironmentVariable("my_percent", "%")
(For the rest of the program, refer to viewtopic.php?f=3&t=10131#p64681.)
I am inclined to use the second strategy, for its simplicity.
Re: Execute UNC path containing literal %, in command line mode
Posted: 06 Jul 2021 00:26
by aGerman
Let's have some fun. I mean, in C code you can easily include any character into a string that even a cat on the keyboard wouldn't be able to enter as name of an environment variable. E.g. a C0 control character like byte 0x01.
Code: Select all
char cmd_line[1024] = " /c \"for /f %\001 in (\"% \001\") do @\"\\\\localhost\\C$\\Users\\%username%\\Desktop\\%\001path%\001foo bar.bat\"\"";
This uses 0x01 as FOR variable name and inserts an unused 0x01 in the parenthesized clause to address jeb's concerns too.
Steffen
Re: Execute UNC path containing literal %, in command line mode
Posted: 06 Jul 2021 09:32
by jeb
noah wrote: ↑05 Jul 2021 21:04
Inject the percent signs via an environment variable:
Code: Select all
char cmd_line[1024] = " /c \"set \"my_percent=\" & \"\\\\localhost\\C$\\Users\\%username%\\Desktop\\%my_percent%path%my_percent%foo bar.bat\"\"";
SetEnvironmentVariable("my_percent", "%")
(For the rest of the program, refer to viewtopic.php?f=3&t=10131#p64681.)
I am inclined to use the second strategy, for its simplicity.
... can't work, because the expansion of
my_percent is done before the set statement is executed.
But it can be done in a safe way with a simple FOR /F
Code: Select all
FOR /F "tokens=1-4 delims=" %:= in ("1=2=3=%=") do \localhost\\C$\\Users\\%username%\\Desktop\\%=path%=foo bar.bat
This defines four for-meta-variables %: %; %< and %= %= contains a single percent sign.
An expansion of %=<any string> is more or less safe, because it's not possible to define a variable with a leading equal sign.
There exist only a few predefined variables beginning with an equal sign.
Still, this could be fail in situations like
Code: Select all
FOR /F "tokens=1-4 delims=" %:= in ("1=2=3=%=") do echo ...%=C:%=
But even this can be solved by using 0 characters of the =exitcode variable
Code: Select all
FOR /F "tokens=1-4 delims=" %:= in ("1=2=3=%=") do echo ...%=%=exitcode:~,0%c:%=
Re: Execute UNC path containing literal %, in command line mode
Posted: 06 Jul 2021 19:54
by noah
jeb wrote: ↑06 Jul 2021 09:32
... can't work, because the expansion of
my_percent is done before the set statement is executed.
What part can't work? I agree that %my_percent% expands before the "set" takes effect, and that's essential. If events happened in the other order, the implementation would be executing a batch file having literal text "%my_percent%" in its name, which isn't the intent. My test runs show the desired effect, and an "echo %my_percent%" inside the executed batch file confirms that the "set my_percent=" takes effect before the first statement of the batch file.
Your alternatives look robust, though I haven't tested them. Regarding aGerman's alternative, I agree an 0x01 byte should be safe enough in practice.