C# get the full path of a not yet running Process Object - c#

I'm currently trying to accomplish the following:
For an SDK, which we provide to our customers, we want the SDK-developers to be able to provide external application calls, so that they can insert additional buttons. These buttons than will start an external application or open a file with the default application for it (Word for *.docx for example).
There should be some visual distinction between the different buttons, so our approach is to show the icon of the application to be called.
Now, there are three different kind of calls:
(The strings below would always be the value of ProcessStartInfo.FileName)
Calling an application providing the full application path, possibly with environement vars (e.g. "C:\Program Files\Internet Explorer\iexplore.exe" / "%ProgramFiles%\Internet Explorer\iexplore.exe")
Calling an application providing only the executable name, given the application can be found in the PATH Variable (e.g. "iexplore")
Opening a document, without providing an application to open it (e.g. "D:\test.html")
We are looking for a way, to find the appropriate Icon for any given call. For this we have to find the full application path of the application, which will be executed in any of the three ways above, but before we actually have started the Process
Is there a way to find the full path or the icon of a System.Diagnostics.Process or System.Diagnostics.ProcessStartInfo object, before the process has been started?
Important: We must not start the process before (could have side effects)
Example Code:
var process = new Process
{
StartInfo =
{
//applicationPath could be any of the stated above calls
FileName = Environment.ExpandEnvironmentVariables(applicationPath)
}
};
//we have to find the full path here, but MainModule is null as long as the process object has not yet started
var icon = Icon.ExtractAssociatedIcon(process.MainModule.FullPath)
Solution
Thanks to you guys I found my solution. The project linked here at CodeProject provides a solution for my exact problem, which works equally with programs and files and can provide the icon before starting the process. Thanks for the link #wgraham

If you want your UI to be visually-consistent with the rest of the user's machine, you may want to extract the icon from the file using Icon.ExtractAssociatedIcon(string path). This works under the WinForms/GDI world. Alternatively, this question addresses how to complete it with P/Invoke.

Your first two examples shouldn't be too hard to figure out using Environment.ExpandEnvironmentVariables. Your last one is the tougher one - the best bet seems to be using PInvoke to call AssocCreate. Adapted from the pinvoke page (http://www.pinvoke.net/default.aspx/shlwapi/AssocCreate.html):
public static class GetDefaultProgramHelper
{
public unsafe static string GetDefaultProgram(string ext)
{
try
{
object obj;
AssocCreate(
ref CLSID_QueryAssociations,
ref IID_IQueryAssociations,
out obj);
IQueryAssociations qa = (IQueryAssociations)obj;
qa.Init(
ASSOCF.INIT_DEFAULTTOSTAR,
ext, //".doc",
UIntPtr.Zero, IntPtr.Zero);
int size = 0;
qa.GetString(ASSOCF.NOTRUNCATE, ASSOCSTR.COMMAND,
"open", null, ref size);
StringBuilder sb = new StringBuilder(size);
qa.GetString(ASSOCF.NOTRUNCATE, ASSOCSTR.COMMAND,
"open", sb, ref size);
//Console.WriteLine(".doc is opened by : {0}", sb.ToString());
return sb.ToString();
}
catch(Exception e)
{
if((uint)Marshal.GetHRForException(e) == 0x80070483)
//Console.WriteLine("No command line is associated to .doc open verb.");
return null;
else
throw;
}
}
[DllImport("shlwapi.dll")]
extern static int AssocCreate(
ref Guid clsid,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object ppv);
[Flags]
enum ASSOCF
{
INIT_NOREMAPCLSID = 0x00000001,
INIT_BYEXENAME = 0x00000002,
OPEN_BYEXENAME = 0x00000002,
INIT_DEFAULTTOSTAR = 0x00000004,
INIT_DEFAULTTOFOLDER = 0x00000008,
NOUSERSETTINGS = 0x00000010,
NOTRUNCATE = 0x00000020,
VERIFY = 0x00000040,
REMAPRUNDLL = 0x00000080,
NOFIXUPS = 0x00000100,
IGNOREBASECLASS = 0x00000200,
INIT_IGNOREUNKNOWN = 0x00000400
}
enum ASSOCSTR
{
COMMAND = 1,
EXECUTABLE,
FRIENDLYDOCNAME,
FRIENDLYAPPNAME,
NOOPEN,
SHELLNEWVALUE,
DDECOMMAND,
DDEIFEXEC,
DDEAPPLICATION,
DDETOPIC,
INFOTIP,
QUICKTIP,
TILEINFO,
CONTENTTYPE,
DEFAULTICON,
SHELLEXTENSION
}
enum ASSOCKEY
{
SHELLEXECCLASS = 1,
APP,
CLASS,
BASECLASS
}
enum ASSOCDATA
{
MSIDESCRIPTOR = 1,
NOACTIVATEHANDLER,
QUERYCLASSSTORE,
HASPERUSERASSOC,
EDITFLAGS,
VALUE
}
[Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IQueryAssociations
{
void Init(
[In] ASSOCF flags,
[In, MarshalAs(UnmanagedType.LPWStr)] string pszAssoc,
[In] UIntPtr hkProgid,
[In] IntPtr hwnd);
void GetString(
[In] ASSOCF flags,
[In] ASSOCSTR str,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszOut,
[In, Out] ref int pcchOut);
void GetKey(
[In] ASSOCF flags,
[In] ASSOCKEY str,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out] out UIntPtr phkeyOut);
void GetData(
[In] ASSOCF flags,
[In] ASSOCDATA data,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] out byte[] pvOut,
[In, Out] ref int pcbOut);
void GetEnum(); // not used actually
}
static Guid CLSID_QueryAssociations = new Guid("a07034fd-6caa-4954-ac3f-97a27216f98a");
static Guid IID_IQueryAssociations = new Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57");
}
You could call this using string filePathToProgram = GetDefaultProgramHelper.GetDefaultProgram(".docx");

The Process class can do exactly what you want.
Environment Variables like %ProgramFiles% need to be expanded first with Environment.ExpandEnvironmentVariables(string).
1.
using System.IO;
using System.Diagnostics;
string iexplore = #"C:\Program Files\Internet Explorer\iexplore.exe");
string iexploreWithEnvVars = Environment.ExpandEnvironmentVariables(#"%ProgramFiles%\Internet Explorer\iexplore.exe");
2.
public static string FindFileInPath(string name)
{
foreach (string path in Environment.ExpandEnvironmentVariables("%path%").Split(';'))
{
string filename;
if (File.Exists(filename = Path.Combine(path, name)))
{
return filename; // returns the absolute path if the file exists
}
}
return null; // will return null if it didn't find anything
}
3.
Process.Start("D:\\test.html");
You want to put your code into try-catch blocks as well since Process.Start will throw an Exception if the file doesn't exist.
Edit: Updated the code, thanks to Dan Field for pointing out that I missed the meaning of the question. :/

Related

Read the content of the string intern pool

I would like to enumerate the strings that are in the string intern pool.
That is to say, I want to get the list of all the instances s of string such that:
string.IsInterned(s) != null
Does anyone know if it's possible?
Thanks to the advice of #HansPassant, I managed to get the list of string literals in an assembly. Which is extremely close to what I originally wanted.
You need to use read assembly meta-data, and enumerate user-strings. This can be done with these three methods of IMetaDataImport:
[ComImport, Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMetaDataImport
{
void CloseEnum(IntPtr hEnum);
uint GetUserString(uint stk, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] char[] szString, uint cchString, out uint pchString);
uint EnumUserStrings(ref IntPtr phEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]uint[] rStrings, uint cmax, out uint pcStrings);
// interface also contains 62 irrelevant methods
}
To get the instance of IMetaDataImport, you need to get a IMetaDataDispenser:
[ComImport, Guid("809C652E-7396-11D2-9771-00A0C9B4D50C")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[CoClass(typeof(CorMetaDataDispenser))]
interface IMetaDataDispenser
{
uint OpenScope([MarshalAs(UnmanagedType.LPWStr)]string szScope, uint dwOpenFlags, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppIUnk);
// interface also contains 2 irrelevant methods
}
[ComImport, Guid("E5CB7A31-7512-11D2-89CE-0080C792E5D8")]
class CorMetaDataDispenser
{
}
Here is how it goes:
var dispenser = new IMetaDataDispenser();
var metaDataImportGuid = new Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44");
object scope;
var hr = dispenser.OpenScope(location, 0, ref metaDataImportGuid, out scope);
metaDataImport = (IMetaDataImport)scope;
where location is the path to the assembly file.
After that, calling EnumUserStrings() and GetUserString() is straighforward.
Here is a blog post with more detail, and a demo project on GitHub.
The SSCLI function that its pointing to is
STRINGREF*AppDomainStringLiteralMap::GetStringLiteral(EEStringData *pStringData)
{
...
DWORD dwHash = m_StringToEntryHashTable->GetHash(pStringData);
if (m_StringToEntryHashTable->GetValue(pStringData, &Data, dwHash))
{
STRINGREF *pStrObj = NULL;
pStrObj = ((StringLiteralEntry*)Data)->GetStringObject();
_ASSERTE(!bAddIfNotFound || pStrObj);
return pStrObj;
}
else { ... }
return NULL; //Here, if this returns, the string is not interned
}
If you manage to find the native address of m_StringToEntryHashTable, you can enumerate the strings that exist.

How to check if the recycle bin is empty

If possible, I want to be able to check whether or not the recycle bin is empty, with minimal hassle (importing dlls, importing anything, creating entire new class to hold recycle bin functionality etc...)
I already have the code below that I found online to empty the recycle bin so it seems natural to suspect that I should be able to extend this to check if it needs emptying first, perhaps.. another function within Shell32.dll.
enum BinFlags : uint
{
SHERB_NOCONFIRMATION = 0x00000001,
SHERB_NOPROGRESSUI = 0x00000002,
SHERB_NOSOUND = 0x00000004
}
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
static extern uint SHEmptyRecycleBin(IntPtr hwnd, string rootPath,
BinFlags flags);
/* snip, bunch of code... */
SHEmptyRecycleBin(IntPtr.Zero, null, 0);
You can add reference to the C:\Windows\System32\Shell32.dll and use the following snippet:
Shell shell = new Shell();
Folder recycleBin = shell.NameSpace(10);
int itemsCount = recycleBin.Items().Count;
Taken from here.
It's poor documentation, but you might want SHQueryRecycleBin EDIT: Slightly better documentation over at MSDN.
[DllImport("shell32.dll")]
static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO
pSHQueryRBInfo);
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct SHQUERYRBINFO
{
public int cbSize;
public long i64Size;
public long i64NumItems;
}
It looks like you make the call and it fills the object and if you look at i64NumItems and it is 0 then the recycle bin is empty.
public static int GetCount()
{
SHQUERYRBINFO sqrbi = new SHQUERYRBINFO();
sqrbi.cbSize = Marshal.SizeOf(typeof(SHQUERYRBINFO));
int hresult = SHQueryRecycleBin(string.Empty, ref sqrbi);
return (int)sqrbi.i64NumItems;
}

Problems with "SetupScanFileQueue" callback from SetupAPI C#

I've implemented a SetupAPI wrapper in C# that enumerates devices based on GUIDs. I have, more or less, converted the DevCon c++ example code found on MSDN to C# (Yes I very much know that this is all veyr painfully to do in .NET, but it's fun and a challenge).
I'm able to get all the appropriate information about a certain device, but a problem occurred when I reached the "SetupScanFileQueue" method.
I cant seem to be able to make the "SetupScanFileQueue" to call my callback. The method returns true, so it seems to be working.
My end goal is to get the driver files of the specific device.
Additional information:
The files appear to be added to the FileQueue correctly, I get this popup window that seems to copy the correct files.
// create a file queue so we can look at this queue later
var queueHandler = SetupOpenFileQueue();
if (queueHandler == IntPtr.Zero)
return false;
// modify flags to indicate we're providing our own queue
var deviceInstallParams = new SP_DEVINSTALL_PARAMS();
deviceInstallParams.cbSize = Marshal.SizeOf(deviceInstallParams);
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we want to add the files to the file queue, not install them!
deviceInstallParams.FileQueue = queueHandler;
deviceInstallParams.Flags |= DI_NOVCP;
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// now fill queue with files that are to be installed
// this involves all class/co-installers
if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, handle, ref devInfo))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we now have a list of delete/rename/copy files
// iterate the copy queue twice - 1st time to get # of files
// 2nd time to get files
// (WinXP has API to get # of files, but we want this to work
// on Win2k too)
var scanResult = 0;
var count = 0;
var callback = new PSP_FILE_CALLBACK(PSP_FILEFOUND_CALLBACK);
var t = SetupScanFileQueue(queueHandler, SPQ_SCAN_USE_CALLBACK, IntPtr.Zero,
callback, ref count, ref scanResult);
SetupDiDestroyDriverInfoList(handle, ref devInfo, SPDIT_CLASSDRIVER);
if (queueHandler != IntPtr.Zero)
SetupCloseFileQueue(queueHandler);
The definition of my Callback is as follows:
public delegate uint PSP_FILE_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2);
public static uint PSP_FILEFOUND_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2)
{
//This callback is never triggered
return 0;
}
Does anyone have any suggestions to what I'm doing wrong in the "SetupScanFileQueue" function, and why the callback is never called?
Any help is very much appreciated!
Edit:
I should also have added the DllImport for the SetupScanFileQueue function:
[DllImport("setupapi.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern uint SetupScanFileQueue(IntPtr QueueHandle,
int Flags,
IntPtr Window,
PSP_FILE_CALLBACK CallbackRoutine,
int CallbackContext,
out int ScanResult
);
I've also tried it without the CallingConvention.

Using c#, how do I copy a symbolic link in Windows Vista,7,2008

Using c#, how do I copy a symbolic link in Windows Vista,7,2008.
When I use File.Copy to copy a symlink, its target gets copied.
I wish to mimic the behavior one gets when you use xcopy with the /B option.
Is this possible using .NET 3.5?
You can use the Win32 CopyFileEx function to do this. It took a bit of effort, but here is the whole CopyFileEx helper class (C# 3.0 and .NET 3.5 Client Profile compatible and tested!). You can also reuse it for any other CopyFileEx tasks that you have:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public static class CopyHelper
{
[Flags]
public enum CopyFileFlags : uint
{
COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
COPY_FILE_RESTARTABLE = 0x00000002,
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008,
COPY_FILE_COPY_SYMLINK = 0x00000800 //NT 6.0+
}
public enum CopyProgressResult : uint
{
PROGRESS_CONTINUE = 0,
PROGRESS_CANCEL = 1,
PROGRESS_STOP = 2,
PROGRESS_QUIET = 3
}
public enum CopyProgressCallbackReason : uint
{
CALLBACK_CHUNK_FINISHED = 0x00000000,
CALLBACK_STREAM_SWITCH = 0x00000001
}
public delegate CopyProgressResult CopyProgressRoutine(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
uint dwStreamNumber,
CopyProgressCallbackReason dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CopyFileEx(string lpExistingFileName,
string lpNewFileName, CopyProgressRoutine lpProgressRoutine,
IntPtr lpData, ref bool pbCancel, CopyFileFlags dwCopyFlags);
}
Here is some sample code that shows how to use it to copy a symbolic link (and not the file it refers to):
string srcLink = #"c:\l.txt"; // Sample source soft link
string destFile = #"d:\l.txt"; // Sample destination soft link
bool bCancel = false;
bool bSuccess = CopyHelper.CopyFileEx(srcLink, destFile,
null, IntPtr.Zero, ref bCancel,
CopyHelper.CopyFileFlags.COPY_FILE_COPY_SYMLINK);
if (!bSuccess)
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error);
}
There is no API for links (neither hardlinks, softlinks or symbolic links) in the .NET framework.
You must either call mklink.exe with Process.Start and create the link you want,
or you have a look for a third party library which is able to do such things.
You could make use of pinvoke and call CopyFileEx. Note the COPY_FILE_COPY_SYMLINK which is what you are looking for.
I recognize this is a fairly old thread, but I've just finished some work in C code (yes, really) that supports this. Posting because I searched all over for this information, and it was not easy to find.
Sadly CopyFileEx with COPY_FILE_SYMLINK does not do the trick. That simply creates a 0-size file in the target directory with the correct name, but no symbolic link. Another old article contains a solution you could try to implement: How to copy directory symbolic link as a link to the target?.
I do think the problem with CopyFileEx is a bug, one that still exists in Windows 10.

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

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
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.
I believe (it has been a while since I did this) that you can just use:
System.Diagnostics.Process.Start(/*File to open*/);
and it will open the file with the default application if there is one. You did not need to know the application it is going to use.
Am I understanding what you are looking? Or did I miss something?
How about spawning cmd.exe /C "your string"
ie - something like
Process proc = new Process();
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.Arguments = #"/C ""C:\Program Files\AppName\Executable.exe"" /arg1 /arg2 /arg3 """ + fileName + """";
proc.Start();
I had a similar problem (parsing a ClickOnce UninstallString from the registry to execute using System.Diagnostics.Process). I resolved it by removing tokens from the end of the uninstall string until I could detect a valid file path.
public static string GetExecutable(string command)
{
string executable = string.Empty;
string[] tokens = command.Split(' ');
for (int i = tokens.Length; i >= 0; i--)
{
executable = string.Join(" ", tokens, 0, i);
if (File.Exists(executable))
break;
}
return executable;
}

Categories

Resources