2

I'm writing a toolbox program in C# and on of its functions is to emulate the Windows Start menu "Run" dialog, but also to allow the user to elevate the privileges if needed.

For this, I have a simple WinForm with a textBox (where the user types), and a checkBox (if checked, add the "runas" verb). And of course 3 buttons : OK, Cancel and Browse (which opens a file dialog). So my code looks like this :

var psi = new ProcessStartInfo(textBox1.Text, "");
psi.CreateNoWindow = true;
psi.UseShellExecute = true;
if (checkBox1.Checked == true)
{
    psi.Verb = "runas";
}
try
{
    Process.Start(psi);
}
catch (System.ComponentModel.Win32Exception ex)
{
    // do something
    MessageBox.Show("Error : " + ex.Message);
}

If users types "notepad" or "notepad.exe" or "c:\whatever\path\notepad", it works. Problems start to occur when arguments are passed : "notepad test.txt" won't work.

My first thought was to split the textBox1.Text when a space is encountered, then use the first part for the ProcessStartInfo.Filename, and the second part for the Arguments. "notepad test.txt" is ok then. But if the users uses the file dialog to select a file where the path and/or filename contains a space (or types it), of course the string will be splitted and everything will go wrong.

Unfortunately, ParseStartInfo needs both the filename and arguments (can be an empty string), but the filename cannot contain the arguments... Using quotes (for the whole textBox1.Text as filename) doesn't work either.

So, does anyone has a solution to :

  1. Either split correctly textBox1.Text to valid filename + arguments, just as the Windows Start - Run dialog does it

OR

  1. Maybe use Shell32.FileRun() but in this case, how to ask for elevation (UAC prompt) at will?

Edit : added the MessageBox.Show to show error messages

Dédé Lateur
  • 55
  • 1
  • 7
  • How about using 2 textboxes? – Sinatr Jan 09 '18 at 10:23
  • 1
    Sure it will help, but my goal is really to work exactly as the Windows Start / Run dialog works (users are used to it)... – Dédé Lateur Jan 09 '18 at 10:25
  • 1
    if you let the user browse you can be quite sure that there will be no arguments?=! am I right? You grab the file name directly from the dialog afterwards ?=! So in this case no splitting is necessary, only when the textbox is used. Or do you write the dialog filename into the textbox? – Mong Zhu Jan 09 '18 at 10:27
  • 1
    https://stackoverflow.com/q/298830/1271037 https://stackoverflow.com/a/17052018/1271037 – dovid Jan 09 '18 at 10:36
  • @Mong Zhu user still can edit the textBox1.Text after browsing and add arguments, so I won't rely on this... – Dédé Lateur Jan 09 '18 at 10:54
  • so you are writing the filename from the dialog into the textbox?=! – Mong Zhu Jan 09 '18 at 11:28

3 Answers3

2

While I usually dislike when a longer code is dumped on SO, maybe this time it is still somewhat helpful for you.

This is my own function I'm using since years to split:

/// <summary>
/// Splits the file name if it contains an executable AND an argument.
/// </summary>
public static void CheckSplitFileName( ref string fileName, ref string arguments )
{
    if ( !string.IsNullOrEmpty( fileName ) )
    {
        if ( fileName.IndexOf( @"http://" ) == 0 ||
            fileName.IndexOf( @"https://" ) == 0 ||
            fileName.IndexOf( @"ftp://" ) == 0 ||
            fileName.IndexOf( @"file://" ) == 0 )
        {
            // URLs not supported, skip.
            return;
        }
        if ( File.Exists( fileName ) )
        {
            // Already a full path, do nothing.
            return;
        }
        else if ( Directory.Exists( fileName ) )
        {
            // Already a full path, do nothing.
            return;
        }
        else
        {
            // Remember.
            string originalFileName = fileName;

            if ( !string.IsNullOrEmpty( fileName ) )
            {
                fileName = fileName.Trim();
            }

            if ( !string.IsNullOrEmpty( arguments ) )
            {
                arguments = arguments.Trim();
            }

            // --

            if ( string.IsNullOrEmpty( arguments ) &&
                !string.IsNullOrEmpty( fileName ) && fileName.Length > 2 )
            {
                if ( fileName.StartsWith( @"""" ) )
                {
                    int pos = fileName.IndexOf( @"""", 1 );

                    if ( pos > 0 && fileName.Length > pos + 1 )
                    {
                        arguments = fileName.Substring( pos + 1 ).Trim();
                        fileName = fileName.Substring( 0, pos + 1 ).Trim();
                    }
                }
                else
                {
                    int pos = fileName.IndexOf( @" " );
                    if ( pos > 0 && fileName.Length > pos + 1 )
                    {
                        arguments = fileName.Substring( pos + 1 ).Trim();
                        fileName = fileName.Substring( 0, pos + 1 ).Trim();
                    }
                }
            }

            // --
            // Possibly revert back.

            if ( !string.IsNullOrEmpty( fileName ) )
            {
                string s = fileName.Trim( '"' );
                if ( !File.Exists( s ) && !Directory.Exists( s ) )
                {
                    fileName = originalFileName;
                }
            }
        }
    }
}

I'm using it in the following way:

var fileName = textBox1.Text.Trim();
var arguments = string.Empty;
CheckSplitFileName( ref fileName, ref arguments );

Then, pass it to the ProcessStartupInfo class:

var info = new ProcessStartInfo();
info.FileName = fileName;
info.Arguments = arguments;
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • 1
    I think we're really near, unfortunately, it fails on "notepad test.txt" with a File not found error (whereas the Run dialog opens notepad, which asks if test.txt is to be created). But it works on "notepad" or "C:\A long\Path with\Spaces\my file.xlsx"... – Dédé Lateur Jan 09 '18 at 11:02
  • 1
    Ok, just commented out the code block (the nested ifs) in the function after "Possibly revert back" and it looks like it works as I expected ! URLs, spaced path/file names, notepad with or without args, everything seems to work ! Many thanks ! – Dédé Lateur Jan 09 '18 at 11:51
  • Thanks for choosing my answer! – Uwe Keim Jan 09 '18 at 12:21
  • 1
    Thanks Uwe for answering ! :) – Dédé Lateur Jan 09 '18 at 12:21
0

I would try to split the TextBox string like that:

  1. string[] elements1 = textBox1.Text.Split(new string[] {"\\"}, StringSplitOptions.RemoveEmptyEntries); and you will get the path elements
  2. string[] elements2 = elements1[elements1.length-1].Split(new string[] {" "}, StringSplitOptions.RemoveEmptyEntries); so elements2[0] contains the application name(ex. notepad) and the other elements the filename
  3. string filename = String.Concat(elements2[1], " ", elements2[2], " ", ...)
OverFloo
  • 23
  • 8
-1

You can still use the split method and than use the "first string" (the first element of the array returned from split method) as the command, and then re-concatenate the other strings to compose the name of the file.

S-Wing
  • 485
  • 6
  • 25
  • 1
    Except it won't work if the command is something like "C:\Program Files\whatever.exe" as the first string will be "C:\Program" (and the second "Files\whatever.exe")... It works for "notepad test.txt", for instance, but not for anything else containing one or more space (also consider "c:\users\me\My Documents\latest figures.xlsx" : two spaces) – Dédé Lateur Jan 09 '18 at 10:32
  • Correct, my fault – S-Wing Jan 09 '18 at 10:37