If you’re not using the command line interpreter, then the command line interpreter metacharacters mean nothing


A customer observed that the parameters passed to Create­Process were not being interpreted correctly.

char commandLine[] = "reg.exe query "
                     "HKLM\\SYSTEM\\CurrentControlSet\\"
                     "Enum\\HID\VID_0461^&PID_4D15";
CreateProcess(NULL, commandLine, ...);

The process is created successfully, but it prints the message ERROR: The system was unable to find the specified registry key or value.. Why aren't the parameters being parsed correctly by Create­Process? They work fine if I paste them into a command prompt.

This is a variation of the problem we saw a few years ago. Back then, we had a string with command line redirection metacharacters, but since we were passing them directly to Create­Process, the command interpreter never got a chance to interpret them. Here, we have a string that contains an ampersand, which has special meaning to the command interpreter, so we escaped it so that the command interpreter won't try to treat it as command separator. But then we passed it directly to the Create­Process function without ever invoking the command processor.

It's like exchanging your U.S. dollars for Canadian dollars because you think you're going to drive through Canada, and then deciding to take the southern route instead, and then wondering why the Canadian money doesn't work. You went to the extra effort of converting the string into a form that will survive a journey you never sent it on!

If you're going to munge the string so that you get the desired end result after it travels through the command interpreter, then you need to send it through the command interpreter. Or more preferably, cut out the middle man and don't bother munging it in the first place.

If you're going to prepare a string for a journey, you need to send it on that journey.

Comments (15)
  1. Owen says:

    This comes up periodically in Unix circles, too: people passing command lines (with variables, quoting, redirection, and other shell features) to fork+exec wrappers that don't invoke a shell (such as Python's subprocess.call function). I feel like the distinction between "OS services" like process creation and "shell services" like command-line processing is lost on a few too many folks…

  2. Mason Wheeler says:

    OK then, to ask the obvious question: if CreateProcess doesn't work in this context, what is the correct way to programatically execute an arbitrary DOS shell command from the Windows API?

  3. GrumpyYoungMan says:

    @Mason Wheeler

    Spawn a command processor instance and feed the shell command to it.  Review the command line parameters for cmd.exe for specifics.

  4. NB says:

    The "system" CRT function might be useful.

  5. Maurits says:

    Or just use RegQueryValueEx directly.

  6. Kevin says:

    @Owen: I'm pretty sure that's just people who spent too much time mucking about with system(3) and popen(3).  The former is a joke because it blocks the calling process and the latter is easily deadlocked if you don't know what you're doing, so I'm not quite sure why we teach people about them…

  7. Myria says:

    @Mason: GetEnvironmentVariableW(L"ComSpec"), then use that as the first parameter of CreateProcessW, the executable path.  For the second parameter, the command line, initially set it to the same as the first parameter.  However, if the executable path has a space, put double quotes around it.  To this string, add a space and parameter /c, then add whatever else you want.  For example, these:

    lpApplicationName = C:WindowsSystem32cmd.exe

    lpCommandLine = C:WindowsSystem32cmd.exe /c echo hello batch world

    lpApplicationName = C:Program Files (x86)Some Silly Cmd Replacementsillycmd.exe

    lpCommandLine = "C:Program Files (x86)Some Silly Cmd Replacementsillycmd.exe" /c echo hello batch world

  8. ErikF says:

    @Kevin: Obviously it's because C is too hard, so you should let a shell script do the heavy lifting. :-)

    I usually see stuff like this happen when people are doing testing in one environment and then take their results and plunk them down in another environment. For example, this happened when I was debugging some low-level networking code that was migrated from old world Mac to new world (the original program didn't do any ntoh* or hton* stuff because it wasn't strictly needed there, and the programmer doing the porting didn't check…)

  9. GWO says:

    @Kevin system() is fine for exactly one purpose — "I have a executable which provides exactly the side effects I want, but do not wish to write a function to perform, and only care about the success/failure (and maybe not even that)".  i.e. "Call an executable as if it were a function".  On a well set up machine, it's a hell of a lot easier to calls system("mount /dev/myuserfs -o this,that,theotherthing") than to finagle the mount() syscall.

    The bad thing is, it causes people to start writing shell scripts in C, which is usually the worst of both worlds.

  10. Neil says:

    You look like you're querying a mouse device. Are you trying to write a bells and whistles installer that's going to annoy the hell out of everyone?

  11. dave says:

    >You look like you're …

    Clippy, is that you?

    ;-)

  12. Joshua says:

    @GWO:mount is setuid-root. Your mount() syscall won't work.

  13. baramin says:

    Uff… No so easy problem. JDK 7u21 was initiated by the expanded interpretation of the syntax for the command file execution. Exactly in Create­Process. The paradigm of a single executable module per a Create­Process call is being destroyed if the module – the script.

  14. GWO says:

    @Joshua: I know, that's what I meant by "finagle the mount() syscall".  system("mount …") does what its supposed to do, providing you're allowed to do it.  It's much harder (i.e. impossible) to do it in "pure" C.

  15. hm says:

    baramin: what do you mean? The JDK release notes do not backup what you are saying. Also, the changes there have nothing to do with scripts. Finally, CreateProcess starts only one process, not multiple.

Comments are closed.