-1

The complex solution below is justified by the need of bringing a browser window to front. It is working ~90% of the time. The problem is the 10%, when it doesn't.

I have an application that is running on a different desktop than the user's active one (it is a screensaver). I also have a windows service that receives events from the screensaver. This service then does the following:

  • Impersonates the currently logged in user and starts a helper application with a URL in the command line arguments.
  • The helper application is started by CreateProcessAsUser - this is also the justification for the helper, I need to use ShellExec, so a separate process have to be used.

This helper application does the following:

  • Waits until the user's current desktop becomes active. It does a while loop with some sleep until then.
  • Then it finds out the user's default browser
  • Starts the default browser using ShellExec (Process.Start in C#), and passes the browser some command line arguments and the URL.

The actual command line invoked by the helper application is this:

cmd /C start "" C:\PathToBrowser\Browser.exe URL -someargument

Up to this point everything is working except one important thing: The browser is not brought to front in all possible cases.

Is there anything further than this, that I could do with these browsers to force them to come to front? My problem is this:

Let's say I start Chrome from command line. Chrome will just send a message to the already running instance, and quit. So I can't rely on the PID and the hWnd of the process I started, it will not be the same as the one actually showing the webpage.

Any help would be much appreciated.

Peter Bulyaki
  • 309
  • 5
  • 10
  • I think the answer [here](http://stackoverflow.com/a/2636915/996081) should do the job! – cbr Jun 18 '15 at 10:16
  • Thanks cubrr, I have been through that post already. Actually posts here on stackoverflow helped a lot already to get to this point where I am. Using FindWindow and SetForeground are not options, as I can't know which window will show my webpage in the end (please read the end of my post) – Peter Bulyaki Jun 18 '15 at 10:28
  • Here is why SetForegroundWindow will not work with a browser: "So I can't rely on the PID and the hWnd of the process I started, it will not be the same as the one actually showing the webpage." – Peter Bulyaki Jun 18 '15 at 10:30
  • You shouldn't be using the PID from `Process.Id`, you should be getting the window handle from `FindWindow`. If you know the HTML title of your webpage, you can get the handle to the Chrome window by using `FindWindow(null, "Page title - Google Chrome");`. If it's minimized, use the functions mentioned [here](http://stackoverflow.com/questions/19859620/bring-to-forward-window-when-minimized). – cbr Jun 18 '15 at 10:38
  • Hi cubrr, that is not entirely a bad idea if it works. However, I don't know the title of the page in advance - I would need to download and parse the html myself before attempting to send the URL to the browser. I am going to give that a try. – Peter Bulyaki Jun 18 '15 at 10:47
  • This code definitely works for me even after starting the browser via `cmd /C start`: https://gist.github.com/cubrr/347e4f3c7fbf5ff08d47 – cbr Jun 18 '15 at 10:49
  • Thanks, I am getting close to trying it, with added code to determine the title of the page. I will post a reply soon if it works. However, I would still not count this as a duplicate of that solution you posted - I have to use EnumWindows instead of FindWindow. – Peter Bulyaki Jun 18 '15 at 12:01
  • Great! I agree, this question differ quite a bit. If you're going to be using EnumWindows, make sure you account for the possibility that the user has two separate Chrome windows open. – cbr Jun 18 '15 at 12:05
  • Yes, I am taking that into account. Actually, it is the main use-case for this problem. If the browser is started as a new process (no other instances of it running) then it almost certainly comes to front, automatically. – Peter Bulyaki Jun 18 '15 at 12:12
  • Please see my final solution below. Thanks for the help! – Peter Bulyaki Jun 18 '15 at 13:36

1 Answers1

2

Thanks to cubrr for the help, his idea worked with some extension from my part. First of all, I have to find out the Title of the webpage that will be displayed within the browser. After this I have to use EnumWindows to find the newly opened browser window, and call SetForegroundWindow on it. My solution is based on these other sources:

How to use EnumWindows to find a certain window by partial title.

Get the title from a webpage.

Bring to forward window when minimized

The solution suggested by cubrr, using FindWindow (you have to know the exact window title to be able to use this):

[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern bool IsIconic(IntPtr handle);

[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr handle, int nCmdShow);

void Main()
{
    const int SW_RESTORE = 9;
    var hWnd = FindWindow(null, "Google - Google Chrome");
    if (IsIconic(hWnd))
        ShowWindow(hWnd, SW_RESTORE);
    SetForegroundWindow(hWnd);
}

Here is the final code I ended up using:

public class MyClass
{
    private const int SW_RESTORE = 9;
    [DllImport("User32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);
    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool BringWindowToTop(IntPtr hWnd);
    [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
    private static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
    [DllImport("user32.dll")]
    private static extern bool IsIconic(IntPtr handle);
    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr handle, int nCmdShow);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int GetWindowTextLength(IntPtr hWnd);
    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    public static string GetWebPageTitle(string url)
    {
        // Create a request to the url
        HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;

        // If the request wasn't an HTTP request (like a file), ignore it
        if (request == null) return null;

        // Use the user's credentials
        request.UseDefaultCredentials = true;

        // Obtain a response from the server, if there was an error, return nothing
        HttpWebResponse response = null;
        try { response = request.GetResponse() as HttpWebResponse; }
        catch (WebException) { return null; }

        // Regular expression for an HTML title
        string regex = @"(?<=<title.*>)([\s\S]*)(?=</title>)";

        // If the correct HTML header exists for HTML text, continue
        if (new List<string>(response.Headers.AllKeys).Contains("Content-Type"))
            if (response.Headers["Content-Type"].StartsWith("text/html"))
            {
                // Download the page
                WebClient web = new WebClient();
                web.UseDefaultCredentials = true;
                string page = web.DownloadString(url);

                // Extract the title
                Regex ex = new Regex(regex, RegexOptions.IgnoreCase);
                return ex.Match(page).Value.Trim();
            }

        // Not a valid HTML page
        return null;
    }

    public static void BringToFront(string title)
    {
        try
        {
            if (!String.IsNullOrEmpty(title))
            {
                IEnumerable<IntPtr> listPtr = null;

                // Wait until the browser is started - it may take some time
                // Maximum wait is (200 + some) * 100 milliseconds > 20 seconds
                int retryCount = 100;
                do
                {
                    listPtr = FindWindowsWithText(title);
                    if (listPtr == null || listPtr.Count() == 0)
                    {
                        Thread.Sleep(200);
                    }
                } while (--retryCount > 0 || listPtr == null || listPtr.Count() == 0);

                if (listPtr == null)
                    return;

                foreach (var hWnd in listPtr)
                {
                    if (IsIconic(hWnd))
                        ShowWindow(hWnd, SW_RESTORE);
                    SetForegroundWindow(hWnd);
                }
            }
        }
        catch (Exception)
        {
            // If it fails at least we tried
        }
    }

    public static string GetWindowText(IntPtr hWnd)
    {
        int size = GetWindowTextLength(hWnd);
        if (size++ > 0)
        {
            var builder = new StringBuilder(size);
            GetWindowText(hWnd, builder, builder.Capacity);
            return builder.ToString();
        }

        return String.Empty;
    }

    public static IEnumerable<IntPtr> FindWindowsWithText(string titleText)
    {
        IntPtr found = IntPtr.Zero;
        List<IntPtr> windows = new List<IntPtr>();

        EnumWindows(delegate(IntPtr wnd, IntPtr param)
        {
            if (GetWindowText(wnd).Contains(titleText))
            {
                windows.Add(wnd);
            }
            return true;
        }, IntPtr.Zero);

        return windows;
    }

    [STAThread]
    public static int Main(string[] args)
    {
        try
        {
            if (args.Count() == 0)
                return 0;

            // ...
            // Wait until the user's desktop is inactive (outside the scope of this solution)
            // ...

            String url = args[0];


            System.Diagnostics.Process process = new System.Diagnostics.Process();
            System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
            startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            startInfo.FileName = "cmd.exe";

            // ...
            // Get the path to the default browser from registry, and create a StartupInfo object with it.
            // ...

            process.StartInfo = startInfo;
            process.Start();
            try
            {
                process.WaitForInputIdle();
            }
            catch (InvalidOperationException)
            {
                // if the process exited then it passed the URL on to the other browser process.
            }

            String title = GetWebPageTitle(url);
            if (!String.IsNullOrEmpty(title))
            {
                BringToFront(title);
            }

            return 0;
        }
        catch (System.Exception ex)
        {
            return -1;
        }
    }
}
Community
  • 1
  • 1
Peter Bulyaki
  • 309
  • 5
  • 10