Writing automation to wait for a window to be created (and dismiss it)


Today's Little Program uses UI Automation to cancel the Run dialog whenever it appears. Why? Well, it's not really useful in and of itself, but it at least provides an example of using UI Automation to wait for an event to occur and then respond to it.

using System.Windows.Automation;

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

    var cancelButton = element.FindFirst(TreeScope.Children,
     new PropertyCondition(AutomationElement.AutomationIdProperty, "2"));
    if (cancelButton != null) {
     var invokePattern = cancelButton.GetCurrentPattern(InvokePattern.Pattern)
                         as InvokePattern;
     invokePattern.Invoke();
     System.Console.WriteLine("Run dialog canceled!");
    }
   });
  System.Console.ReadLine();
  Automation.RemoveAllEventHandlers();
 }
}

Okay, let's see what's going on here.

The program registers a delegate with UI automation which is called for any Window­Opened event that is an immediate child (Tree­Scope.Children) of the root (Automation­Element.Root­Element). This will catch changes to top-level unowned windows, but not bother firing for changes that occur inside top-level windows or for owned windows.

Inside our handler, we check if the window's title is Run. If not, then we ignore the event. (This will get faked out by any other window that calls itself Run.)

Once we think we have a Run dialog, we look for the Cancel button, which we have determined by using UI Spy to have the automation ID "2". (That this is the numeric value of IDCANCEL is hardly a coincidence.)

If we find the Cancel button, we obtain its Invoke pattern so we can Invoke it, which for buttons means pressing it.

Take this program out for a spin. Run the program and then hit Win+R to open the Run dialog. Oops, the program cancels it!

Ha-ha!

Comments (18)
  1. BOFH says:

    Well, how does one compile it?

    I get this error:

    —8<—

    Microsoft (R) Visual C# Compiler version 4.0.30319.18408

    for Microsoft (R) .NET Framework 4.5

    Copyright (C) Microsoft Corporation. All rights reserved.

    closerun.cs(1,22): error CS0234: The type or namespace name 'Automation' does

           not exist in the namespace 'System.Windows' (are you missing an assembly

           reference?)

    —8<—

    [Have you considered the possibility that you are missing an assembly reference? -Raymond]
  2. BOFH says:

    Yes, but I'm not a kodhjon so I don't know what to do with that information.

    Do you have a suggestion as to what to add to your Little Program?

  3. Ross Presser says:

    stackoverflow.com/…/referencing-system-windows-automation

    "referencing System.Windows.Automation"

    The UIAutomationClient.dll is located in this folder:

    C:Program FilesReference AssembliesMicrosoftFrameworkv3.0

    [Note that if you have to have the answer given to you, then you probably aren't in the target audience for this blog. In particular, it's not clear what you're going to do with the techniques illustrated by the Little Program. -Raymond]
  4. JDT says:

    I suspect Raymond has been replaced by an imposter; this blog post both:

    > gives instructions for how to do something evil;

    > is written in C# where an aspect of the CLR is not the subject of the blog.

  5. Azarien says:

    Now, how to get the localized string for "Run"?

    [UI automation is not intended for driving UI programmatically in production. It's for automating your testing. So the way you get it is by whatever means you use to localize your program (since your program is the one whose dialog is being driven). I chose the Run dialog just for illustration. -Raymond]
  6. VZ says:

    @JDT: This was my first thought as well. But the inline reply in the first BOFH comment is pure Raymond. Now I'm confused…

  7. Joshua says:

    [UI automation is not intended for driving UI programmatically in production. -Raymond]

    So it's not a good idea to use to write accessibility software then?

    [That's not what I'm talking about. Go ahead and use it for accessibility. But that is different from writing a program to identify the Run dialog every time it appears or to programmatically click on buttons on dialogs automatically. When used for accessibility, the end user is the one who is identifying the dialog and deciding which buttons to click. -Raymond]
  8. alegr1 says:

    Wait for somebody using this to disable Run box as "security" policy.

  9. John says:

    I always enjoy these Automation Posts, its part of my daily job function. Its a pretty slick library, but in some senses not documented as well as it could be (although it has gotten much much better in recent years).

  10. jas88 says:

    alegr1: Run is actually disabled on the machine on my desktop at work (as was logging in, until two weeks ago, and writing to my home directory until last week). There's some paperwork somewhere I can do to get admin rights, or just walk next door where there's a whole lab of machines on which I already have. Ironic, as a security researcher, but we all start off with standard staff builds (and the occasional glitch!) until we social-engineer ourselves more access…

  11. cheong00 says:

    @jas88: In univeristy, we used to have (boring) kiosk everywhere that have everything (mostly) locked up too. We found that the "Save as" dialog in IE is handy for us to run "telnet" to connect to the Unix clusters, in order to do things like emailing homework or so.

  12. JK says:

    To be fair finding the reference for a particular namespace is much harder than it should be for some namespaces (here the reference has a different name than the namespace and the namespace documentation on MSDN doesn't mention the dll/reference)

    [Namespaces don't have references. Classes have references. (A single namespace can be spread out among multiple assemblies, and a single assembly can include classes from multiple namespaces.) So you need to look up the class documentation to see what assembly it is in. -Raymond]
  13. voo says:

    @JK Much harder than it should be? Because the only thing I did was google for "msdn Automation.AddAutomationEventHandler", click the first link that goes to msdn and read the third line which states Assembly:  UIAutomationClient (in UIAutomationClient.dll)

    How exactly you make this process any easier really isn't clear to me and considering Raymond's target audience, anyone who can't figure that one out is probably reading the wrong blog – as harsh as that sounds.

  14. RonO says:

    @voo Well, I think there's a reasonable expectation the MSDN documentation will give the specific assembly for a namespace. Many (most?) namespaces only exist in a single assembly. Some namespaces could result in a long list, though. As you point out, using the class (or in your example, class + method name — namespace + class will also work) results in a clear reference.

    [The problem is that anybody can inject a class into a namespace. I could write a "foo.cs" that includes the line "namespace System.Windows.Automation { public class Haha { … } }" and bingo, there is a new Foo.dll assembly that participates in the System.Windows.Automation namespace. It's like coming up with a list of all the subclasses of a base class. -Raymond]
  15. Joshua says:

    [The problem is that anybody can inject a class into a namespace. I could write a "foo.cs" …]

    Non-issue. foo.cs doesn't build into an assembly that is part of .NET.

    [Different pieces of the BCL update at different times. (See .NET 3.5.) Maybe it would be nice if all the different pieces of the BCL were interrelated so that whenever, say, a new class is added to UIA, it adds itself to a list of classes maintained by WPF. But in practice, this is hard to manage. Tightly-coupled is harder to maintain than loosely-coupled. -Raymond]
  16. IInspectable says:

    @RonO Namespaces do not exist in assemblies. In fact, namespaces do not exist at all. Some programming languages have support for namespaces to ease constructing and accessing long (and thus more likely to be unique) type names. As far as the CLR is concerned, there are no namespaces. Your expectation that assemblies (which do exist) and namespaces (which don't) are somehow related is misguided.

  17. RonO says:

    @Raymond Different pieces of the BLC may update at different times, but they are still released as a part of an official and specific version of the .NET Framework (even if just a service pack). And the online MSDN documentation is pretty good now (complete?) at separating the documentation of the different versions. It's just as important to look at the specific version of the documentation for a class/method in the version of the .NET Framework used in a project (and yes, I've burned myself a time or two).

    And tools like ILSpy seem to do a good job finding sub-classes, though I imagine it is dependent upon which assemblies are loaded.

    @IInspectable We'll have to agree to disagree here. Namespaces exist to serve the same purpose in the overall Framework as they do in any project referencing those assemblies. If they did not, it would be impossible to use System.Globalization.Calendar and System.Web.UI.WebControls.Calendar in the same project and there would be no purpose in specifying using (Imports) directives.

  18. Joshua says:

    [Different pieces of the BCL update at different times. (See .NET 3.5.) Maybe it would be nice if all the different pieces of the BCL were interrelated so that whenever, say, a new class is added to UIA, it adds itself to a list of classes maintained by WPF. But in practice, this is hard to manage. Tightly-coupled is harder to maintain than loosely-coupled. -Raymond]

    Enter auto-gen, stage right. It appears those docs are mostly auto-gened anyway.

Comments are closed.

Skip to main content