Execute UNC path containing literal %, in command line mode

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
noah
Posts: 6
Joined: 03 Jul 2021 20:33

Execute UNC path containing literal %, in command line mode

#1 Post by noah » 03 Jul 2021 20:52

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?

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#2 Post by aGerman » 04 Jul 2021 03:59

Is this a question about Perl, noah? I don't get from where you're trying to call the path containing percents.

Steffen

noah
Posts: 6
Joined: 03 Jul 2021 20:33

Re: Execute UNC path containing literal %, in command line mode

#3 Post by noah » 04 Jul 2021 05:10

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)

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#4 Post by aGerman » 04 Jul 2021 07:02

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

noah
Posts: 6
Joined: 03 Jul 2021 20:33

Re: Execute UNC path containing literal %, in command line mode

#5 Post by noah » 04 Jul 2021 07:32

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.

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#6 Post by aGerman » 04 Jul 2021 07:39

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

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#7 Post by aGerman » 04 Jul 2021 09:45

No success :lol:

"%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

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#8 Post by aGerman » 04 Jul 2021 11:07

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

noah
Posts: 6
Joined: 03 Jul 2021 20:33

Re: Execute UNC path containing literal %, in command line mode

#9 Post by noah » 04 Jul 2021 17:35

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?

jeb
Expert
Posts: 987
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Execute UNC path containing literal %, in command line mode

#10 Post by jeb » 05 Jul 2021 00:24

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 :D

jeb

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#11 Post by aGerman » 05 Jul 2021 10:05

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

noah
Posts: 6
Joined: 03 Jul 2021 20:33

Re: Execute UNC path containing literal %, in command line mode

#12 Post by noah » 05 Jul 2021 21:04

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.

aGerman
Expert
Posts: 4198
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Execute UNC path containing literal %, in command line mode

#13 Post by aGerman » 06 Jul 2021 00:26

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

jeb
Expert
Posts: 987
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Execute UNC path containing literal %, in command line mode

#14 Post by jeb » 06 Jul 2021 09:32

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.

Code: Select all

=::
=C:
=ExitCode
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:%=

noah
Posts: 6
Joined: 03 Jul 2021 20:33

Re: Execute UNC path containing literal %, in command line mode

#15 Post by noah » 06 Jul 2021 19:54

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.

Post Reply