12

I am writing an app (C# and WPF in .NET 4.0) that needs to get open windows and close them if they are not in it's white-list.

So far, using EnumDesktopWindows Windows API from User32.dll, I can enumerate all open windows in about 10 ms on my machine. As you've probably guessed by now, I need to do this in small periods of time to be as quick as possible and on the other hand selecting small time periods will put a big overhead on the system.

The question is, "Is there any way to get notified when a window is opened (like using an event)? Either way, what is the most efficient way of doing this?

Arashv
  • 305
  • 4
  • 15

3 Answers3

23

You can Hook on to Shell to receive messages by using RegisterWindowMessage and RegisterShellHookWindow API functions.

You will need the following Interop imports:

public static class Interop
{
    public enum ShellEvents : int
    {
        HSHELL_WINDOWCREATED = 1,
        HSHELL_WINDOWDESTROYED = 2,
        HSHELL_ACTIVATESHELLWINDOW = 3,
        HSHELL_WINDOWACTIVATED = 4,
        HSHELL_GETMINRECT = 5,
        HSHELL_REDRAW = 6,
        HSHELL_TASKMAN = 7,
        HSHELL_LANGUAGE = 8,
        HSHELL_ACCESSIBILITYSTATE = 11,
        HSHELL_APPCOMMAND = 12
    }
    [DllImport("user32.dll", EntryPoint = "RegisterWindowMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public static extern int RegisterWindowMessage(string lpString);
    [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public static extern int DeregisterShellHookWindow(IntPtr hWnd);
    [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public static extern int RegisterShellHookWindow(IntPtr hWnd);
    [DllImport("user32", EntryPoint = "GetWindowTextA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public static extern int GetWindowText(IntPtr hwnd, System.Text.StringBuilder lpString, int cch);
    [DllImport("user32", EntryPoint = "GetWindowTextLengthA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public static extern int GetWindowTextLength(IntPtr hwnd);
}

To be able to hook on to the shell, you'll need a class that inherits from Form and overrides the WndProc function. You can make this Form to have an Event that will be raised when a Window change its state.

public class SystemProcessHookForm : Form
{
    private readonly int msgNotify;
    public delegate void EventHandler(object sender, string data);
    public event EventHandler WindowEvent;
    protected virtual void OnWindowEvent(string data)
    {
        var handler = WindowEvent;
        if (handler != null)
        {
            handler(this, data);
        }
    }

    public SystemProcessHookForm()
    {
        // Hook on to the shell
        msgNotify = Interop.RegisterWindowMessage("SHELLHOOK");
        Interop.RegisterShellHookWindow(this.Handle);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == msgNotify)
        {
            // Receive shell messages
            switch ((Interop.ShellEvents)m.WParam.ToInt32())
            {
                case Interop.ShellEvents.HSHELL_WINDOWCREATED:
                case Interop.ShellEvents.HSHELL_WINDOWDESTROYED:
                case Interop.ShellEvents.HSHELL_WINDOWACTIVATED:
                    string wName = GetWindowName(m.LParam);
                    var action = (Interop.ShellEvents)m.WParam.ToInt32();
                    OnWindowEvent(string.Format("{0} - {1}: {2}", action, m.LParam, wName));
                    break;
            }
        }
        base.WndProc(ref m);
    }

    private string GetWindowName(IntPtr hwnd)
    {
        StringBuilder sb = new StringBuilder();
        int longi = Interop.GetWindowTextLength(hwnd) + 1;
        sb.Capacity = longi;
        Interop.GetWindowText(hwnd, sb, sb.Capacity);
        return sb.ToString();
    }

    protected override void Dispose(bool disposing)
    {
        try { Interop.DeregisterShellHookWindow(this.Handle); }
        catch { }
        base.Dispose(disposing);
    }
}

And then, in your Main function of your application, you can have for example:

static void Main(string[] args)
{
    var f = new SystemProcessHookForm();
    f.WindowEvent += (sender, data) => Console.WriteLine(data); 
    while (true)
    {
        Application.DoEvents();
    }
}

Output sample: enter image description here

thepirat000
  • 12,362
  • 4
  • 46
  • 72
  • Thanks for the answer but it doesn't fire when a dialog box opens on other apps. what should I do about that? – Arashv Feb 22 '14 at 09:33
  • Any particular reason why you're using the ANSI APIs instead of the Unicode ones? – Joey Apr 03 '19 at 06:31
10

Use the System.Windows.Automation namespace.

Example (taken from The Old New Thing) which waits for a specific process to open a dialog box, then dismisses it:

using System;
using System.Windows.Automation;
using System.Diagnostics;
using System.Threading;

class Program
{
    [STAThread]
    public static void Main(string[] args)
    {     
        Automation.AddAutomationEventHandler(
            WindowPattern.WindowOpenedEvent,
            AutomationElement.RootElement,
            TreeScope.Children,
            (sender, e) =>
            {
                var element = sender as AutomationElement;

                Console.WriteLine("new window opened");
            });

        Console.ReadLine();

        Automation.RemoveAllEventHandlers();
    }
}
Mitch
  • 21,223
  • 6
  • 63
  • 86
  • 2
    I checked it and it was pretty straightforward. Thanks for the comment, but there's one nasty thing about it which makes it not suitable from security point of view. As heavenly as it seemed at first, it turned out that the event won't be fired until the window finishes initializing all it needs on the screen! e.g., if you open multiple instances of `eventvwr.msc`, each instance takes considerable amount of time to populate a summary in start page which results in event not fired before window finishes creating summary (about 8-10 sec) – Arashv Mar 13 '14 at 16:16
  • To clarify let's say that if you enumerate windows in old-fashioned way during the time the aforementioned summary is being populated, you can find all instances of event viewer in the list, meaning that they are created by that time, but the event will not be fired until the load is completed. – Arashv Mar 13 '14 at 16:21
0

If you are accessing windows from other application,this is not going to be an easy task but you could try to use Hooks in windows.

Tinu
  • 197
  • 2
  • 9