0

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)

  1. 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")
  2. Calling an application providing only the executable name, given the application can be found in the PATH Variable (e.g. "iexplore")
  3. 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

crazy_crank
  • 659
  • 4
  • 17
  • 1
    You need to query the default program list. then find the default program associated with the extension you want to open. then get the startup path and you can get the icon of the program with one of the win api method – Franck Apr 10 '15 at 15:36

3 Answers3

0

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. :/

four
  • 38
  • 6
  • The first two examples are good, but he doesn't want to actually start the program, he just wants the path. Your third example won't get him that. – Dan Field Apr 10 '15 at 16:02
  • Thanks for your answer, even though I've used wgrahams answer, I still need the FindFileInPath part. There's a small mistake though, the method has to search recursivly for the executable. Otherwise, thank you very much :) – crazy_crank Apr 14 '15 at 07:49
0

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");

Dan Field
  • 20,885
  • 5
  • 55
  • 71
0

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.

Community
  • 1
  • 1
wgraham
  • 1,383
  • 10
  • 16
  • Thanks for the link about the question about P/Invoke, that's perfect. The [linked Code Project](http://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using) project (^^) works with files and their default applications and normal applocations equally. – crazy_crank Apr 14 '15 at 07:03