Executing another program from C#, do I need to parse the "command line" from registry myself?

Lasse V. Karlsen picture Lasse V. Karlsen · Oct 13, 2009 · Viewed 17k times · Source

From the registry, for a given file type, I get a string containing something like this:

"C:\Program Files\AppName\Executable.exe" /arg1 /arg2 /arg3

or sometimes:

"C:\Program Files\AppName\Executable.exe" /arg1 /arg2 /arg3 "%1"

In order for me to execute this program, and pass along a filename as a parameter (which I know it accepts), do I have to parse this string myself, or is there a runtime class that will do this for me? Note that I'm not asking about handling the difference between the two in regards to whether it has a "%1" or not, but rather I need to split off the name of the executable, get the command line arguments to it separately.

I tried just appending/injecting the full path to and name of the file to pass along into the string above and pass the whole shebang to Process.Start, but of course it expects just the filename as the single argument, so that doesn't work.

Basically, the above would have to be done like this manually:

Process proc = new Process();
proc.StartInfo.FileName = @"C:\Program Files\AppName\Executable.exe";
proc.StartInfo.Arguments = "/arg1 /arg2 /arg3 \"" + fileName + "\"";
proc.Start();

I tried using UseShellExecute, but that didn't help. Any other pointers?

To be clear, I want this:

String commandPath = ReadFromRegistry();
String fullCommand = commandPath + " " + fileName; // assuming not %1
Process.Start(fullCommand); // <-- magic happens here

Answer

Abel picture Abel · Oct 13, 2009

The problem you are facing is that the executable name and some arguments are already together in your variable commandPath (which is not only the path, but also some params). If the first part were only made up of characters (no spaces), it wouldn't have been too hard to separate the executable from the params, but this is Windows, so you may have spaces, so you are stuck. So it seems.

The solution is in not using Process.Start, and not using ShellExecute. Process.Start, whether you ask it to use ShellExecute or CreateProcess, in both cases, it requires the FileName parameter/member to be set, which is passed as-is to CreateProcess and ShellExecute.

So what then? Rather simply put: use CreateProcess yourself. A lesser known feature of that API function is that you can pass a full commandline to it, just as you can under WinKey+R (Windows Run). The "magic" that you ask for can be achieved by setting its first param to null and its second param to the full path, including all parameters. Like the following, which will start the Windows Photo Gallery for you, while using the same string with the params with Process.Start any which way would yield a "File Not Found" error:

STARTUPINFO si = new STARTUPINFO();
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
CreateProcess(
    /* app name     */ null,
    /* cmd line     */ @"C:\Program Files\Windows Photo Gallery\WindowsPhotoGallery.exe testBogusParam", 
    /* proc atts    */ IntPtr.Zero, 
    /* thread atts  */ IntPtr.Zero, 
    /* inh handles  */ false,
    /* create flags */ 0, 
    /* env ptr      */ IntPtr.Zero, 
    /* current dir  */ null, 
    /* startupinfo  */ ref si, 
    /* processinfo  */ out pi);

Note that I deliberately did not include quotes around the executable path. But if the executable path has quotes around it, as with your code above, it will still work, all the magic is there. Combine that with your code snippet, the following will start the process the way you want:

/* with your code */
String commandPath = ReadFromRegistry();
String fullCommand = commandPath + " " + fileName; // assuming not %1
STARTUPINFO si = new STARTUPINFO();
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
CreateProcess(
    null,
    fullCommand, 
    IntPtr.Zero, 
    IntPtr.Zero, 
    false,
    0, 
    IntPtr.Zero, 
    null, 
    ref si, 
    out pi);

The declarations are something you can get from http://www.pinvoke.net, but for convenience, here's the part that should be pasted inside the class section to get the above to work. Reference of these functions, how to check the result (success / fail) and the STARTUPINFO and PROCESS_INFORMATION structures can be found at Microsoft's MSDN here. for convenience, I recommend to place the call to CreateProcess in a utility function.

/* place the following at the class level */
[DllImport("kernel32.dll")]
static extern bool CreateProcess(
    string lpApplicationName, 
    string lpCommandLine, 
    IntPtr lpProcessAttributes, 
    IntPtr lpThreadAttributes,
    bool bInheritHandles, 
    uint dwCreationFlags, 
    IntPtr lpEnvironment,
    string lpCurrentDirectory, 
    ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation);

public struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public uint dwProcessId;
    public uint dwThreadId;
}



public struct STARTUPINFO
{
    public uint cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public uint dwX;
    public uint dwY;
    public uint dwXSize;
    public uint dwYSize;
    public uint dwXCountChars;
    public uint dwYCountChars;
    public uint dwFillAttribute;
    public uint dwFlags;
    public short wShowWindow;
    public short cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

Hope I understood your problem correctly. Let me know if you have trouble implementing the above code.