4

I need to open a URL in a new browser process. I need to be notified when that browser process quits. The code I'm currently using is the following:

        Process browser = new Process();
        browser.EnableRaisingEvents = true;
        browser.StartInfo.Arguments = url;
        browser.StartInfo.FileName = "iexplore";

        browser.Exited += new EventHandler(browser_Exited);

        browser.Start();

Clearly, this won't due because the "FileName" is fixed to iexplore, not the user's default web browser. How do I figure out what the user's default web browser is?

I'm running on Vista->forward. Though XP would be nice to support if possible.

A bit more context: I've created a very small stand-alone web server that serves some files off a local disk. At the end of starting up the server I want to start the browser. Once the user is done and closes the browser I'd like to quit the web server. The above code works perfectly, other than using only IE.

Thanks in advance!

Gordon
  • 3,012
  • 2
  • 26
  • 35

7 Answers7

6

Ok. I now have working C# code to do what I want. This will return the "command line" you should run to load the current default browser:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;

namespace testDefaultBrowser
{
    public enum ASSOCIATIONLEVEL
    {
        AL_MACHINE,
        AL_EFFECTIVE,
        AL_USER,
    };

    public enum ASSOCIATIONTYPE
    {
        AT_FILEEXTENSION,
        AT_URLPROTOCOL,
        AT_STARTMENUCLIENT,
        AT_MIMETYPE,
    };

    [Guid("4e530b0a-e611-4c77-a3ac-9031d022281b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IApplicationAssociationRegistration
    {
        void QueryCurrentDefault([In, MarshalAs(UnmanagedType.LPWStr)] string pszQuery,
        [In, MarshalAs(UnmanagedType.I4)] ASSOCIATIONTYPE atQueryType,
        [In, MarshalAs(UnmanagedType.I4)] ASSOCIATIONLEVEL alQueryLevel,
        [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAssociation);

        void QueryAppIsDefault(
            [In, MarshalAs(UnmanagedType.LPWStr)] string pszQuery,
            [In] ASSOCIATIONTYPE atQueryType,
            [In] ASSOCIATIONLEVEL alQueryLevel,
            [In, MarshalAs(UnmanagedType.LPWStr)] string pszAppRegistryName,
            [Out] out bool pfDefault);

        void QueryAppIsDefaultAll(
            [In] ASSOCIATIONLEVEL alQueryLevel,
            [In, MarshalAs(UnmanagedType.LPWStr)] string pszAppRegistryName,
            [Out] out bool pfDefault);

        void SetAppAsDefault(
            [In, MarshalAs(UnmanagedType.LPWStr)] string pszAppRegistryName,
            [In, MarshalAs(UnmanagedType.LPWStr)] string pszSet,
            [In] ASSOCIATIONTYPE atSetType);

        void SetAppAsDefaultAll(
            [In, MarshalAs(UnmanagedType.LPWStr)] string pszAppRegistryName);

        void ClearUserAssociations();
    }

    [ComImport, Guid("591209c7-767b-42b2-9fba-44ee4615f2c7")]//
    class ApplicationAssociationRegistration
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            IApplicationAssociationRegistration reg = 
                (IApplicationAssociationRegistration) new ApplicationAssociationRegistration();

            string progID;
            reg.QueryCurrentDefault(".txt",
                ASSOCIATIONTYPE.AT_FILEEXTENSION,
                ASSOCIATIONLEVEL.AL_EFFECTIVE,
                out progID);
            Console.WriteLine(progID);

            reg.QueryCurrentDefault("http",
                ASSOCIATIONTYPE.AT_URLPROTOCOL,
                ASSOCIATIONLEVEL.AL_EFFECTIVE,
                out progID);
            Console.WriteLine(progID);
        }
    }
}

Whew! Thanks everyone for help in pushing me towards the right answer!

Gordon
  • 3,012
  • 2
  • 26
  • 35
3

If you pass a path of the known file type to the (file) explorer application, it will 'do the right thing', e.g.

 Process.Start("explorer.exe", @"\\path.to\filename.pdf");

and open the file in the PDF reader.

But if you try the same thing with a URL, e.g.

Process.Start("explorer.exe", @"http://www.stackoverflow.com/");

it fires up IE (which isn't the default browser on my machine).

I know doesn't answer the question, but I thought it was an interesting sidenote.

Unsliced
  • 10,404
  • 8
  • 51
  • 81
  • 1
    ShellExecute will do exactly what you want - it will start up the default browser. The problem is I need back the process ID so I can monitor the process that was started: my code needs to quit when the user closes the browser (yeah, tabs cause some difficulty, but close enough for now). – Gordon Apr 23 '09 at 05:29
  • Thats weird.. My default browser is Chrome and it opened chrome – om471987 May 14 '12 at 17:59
  • ShellExecute and Process.Start are nice until you have a URL with an anchor (#). They will fail to launch when passed those even if properly encoded. – jschroedl Jun 05 '15 at 14:06
  • Just a note... this will **not** open the default browser. It will open the default application set to open html files, which might very well be _an editor_ on some people's PCs, if they happen to do a lot of html editing. – Nyerguds Aug 12 '15 at 09:12
1

I've written this code for a project once... it keeps in mind any additional parameters set for the default browser. It was originally created to open HTML documentation in a browser, for the simple reason I always set my default program for HTML to an editor rather than a browser, and it annoys me to no end to see some program open its HTML readme in my text editor. Obviously, it works perfectly for URLs too.

    /// <summary>
    ///     Opens a local file or url in the default web browser.
    /// </summary>
    /// <param name="path">Path of the local file or url</param>
    public static void openInDefaultBrowser(String pathOrUrl)
    {
        pathOrUrl = "\"" + pathOrUrl.Trim('"') + "\"";
        RegistryKey defBrowserKey = Registry.ClassesRoot.OpenSubKey(@"http\shell\open\command");
        if (defBrowserKey != null && defBrowserKey.ValueCount > 0 && defBrowserKey.GetValue("") != null)
        {
            String defBrowser = (String)defBrowserKey.GetValue("");
            if (defBrowser.Contains("%1"))
            {
                defBrowser = defBrowser.Replace("%1", pathOrUrl);
            }
            else
            {
                defBrowser += " " + pathOrUrl;
            }
            String defBrowserProcess;
            String defBrowserArgs;
            if (defBrowser[0] == '"')
            {
                defBrowserProcess = defBrowser.Substring(0, defBrowser.Substring(1).IndexOf('"') + 2).Trim();
                defBrowserArgs = defBrowser.Substring(defBrowser.Substring(1).IndexOf('"') + 2).TrimStart();
            }
            else
            {
                defBrowserProcess = defBrowser.Substring(0, defBrowser.IndexOf(" ")).Trim();
                defBrowserArgs = defBrowser.Substring(defBrowser.IndexOf(" ")).Trim();
            }
            if (new FileInfo(defBrowserProcess.Trim('"')).Exists)
                Process.Start(defBrowserProcess, defBrowserArgs);
        }
    }
Nyerguds
  • 5,360
  • 1
  • 31
  • 63
  • Seems Windows keeps changing where to find the default browser in the registry. But the method to build the full process and its arguments is still usable, I guess. – Nyerguds Sep 06 '16 at 08:20
1

The way to determine the default browser is explained in this blog post:

http://ryanfarley.com/blog/archive/2004/05/16/649.aspx

From the blog post above:

private string getDefaultBrowser()
{
    string browser = string.Empty;
    RegistryKey key = null;
    try
    {
        key = Registry.ClassesRoot.OpenSubKey(@"HTTP\shell\open\command", false);

        //trim off quotes
        browser = key.GetValue(null).ToString().ToLower().Replace("\"", "");
        if (!browser.EndsWith("exe"))
        {
            //get rid of everything after the ".exe"
            browser = browser.Substring(0, browser.LastIndexOf(".exe")+4);
        }
    }
    finally
    {
        if (key != null) key.Close();
    }
    return browser;
}
adeel825
  • 5,677
  • 12
  • 40
  • 44
  • Thanks! I was under the impression that since they added the default programs settings there was now an API to look at this. And sorry for waiting so long to answer - for whatever (as yet know understood reason) I never got email there was an answer. – Gordon Apr 22 '09 at 07:48
  • This doesn't get the default web browser. In my case, IE8 is set as default and it went for Firefox. There must be a better way to handle this. – スーパーファミコン Aug 20 '09 at 17:32
  • matthews is correct, at least on Vista this always returns the path to Firefox. Looks like maybe HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice is what you want. – mhenry1384 Sep 08 '09 at 21:42
1

Ok, I think I might have found it - IApplicationAssociationRegistration::QueryCurrentDefault [1]. According to the docs this is what is used by ShellExecute. I'll post code when I get it to work, but I'd be interested if others think this is the right thing to use (BTW, I'm Vista or greater for OS level).

[1]: http://msdn.microsoft.com/en-us/library/bb776336(VS.85).aspx QueryCurrentDefault

Gordon
  • 3,012
  • 2
  • 26
  • 35
1

Ok. Been away on the conference circuit for a week, now getting back to this. I can do this with C++ now - and it even seems to behave properly! My attempts to translate this into C# (or .NET) have all failed however (Post On Question).

Here is the C++ code for others that stumble on this question:

#include "stdafx.h"
#include <iostream>
#include <shobjidl.h>
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS      // some CString constructors will be explicit

#include <atlbase.h>
#include <atlstr.h>
#include <AtlDef.h>
#include <AtlConv.h>

using namespace std;
using namespace ATL;

int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr = CoInitialize(NULL);
    if (!SUCCEEDED(hr)) {
        cout << "Failed to init COM instance" << endl;
        cout << hr << endl;
    }

    IApplicationAssociationRegistration *pAAR;
    hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
        NULL, CLSCTX_INPROC, __uuidof(IApplicationAssociationRegistration),
        (void**) &pAAR);
    if (!SUCCEEDED(hr))
    {
        cout << "Failed to create COM object" << endl;
        cout << hr << endl;
        return 0;
    }

    LPWSTR progID;
    //wchar_t *ttype = ".txt";
    hr = pAAR->QueryCurrentDefault (L".txt", AT_FILEEXTENSION, AL_EFFECTIVE, &progID);
    if (!SUCCEEDED(hr)) {
        cout << "Failed to query default for .txt" << endl;
        cout << hr << endl;
    }
    CW2A myprogID (progID);
    cout << "Result is: " << static_cast<const char*>(myprogID) << endl;

    /// Now for http

    hr = pAAR->QueryCurrentDefault (L"http", AT_URLPROTOCOL, AL_EFFECTIVE, &progID);
    if (!SUCCEEDED(hr)) {
        cout << "Failed to query default for http" << endl;
        cout << hr << endl;
    }
    CW2A myprogID1 (progID);
    cout << "Result is: " << static_cast<const char*>(myprogID1) << endl;

    return 0;
}

I will post the C# code when I finally get it working!

Gordon
  • 3,012
  • 2
  • 26
  • 35
  • Just a warning that on Windows 8.1 this will fail. Instead of the path to iexplore.exe it will return the progid: "IE.HTTP" so some more work is needed to launch properly there. – jschroedl Jun 05 '15 at 14:08
0

Short answer, you can't.

If the default browser is, say, Firefox, and the user already has a Firefox instance running, it will just be opened in another window or tab of the same firefox.exe process, and even after they close your page, the process won't exit until they close every window and tab. In this case, you would receive notification of the process exiting as soon as you started it, due to the temporary firefox.exe proc that would marshal the URL to the current process. (Assuming that's how Firefox's single instance management works).

bsneeze
  • 4,369
  • 25
  • 20