Random header image... Refresh for more!

SWA: Straight Outta Redmond

Back in .Net 3.0, Microsoft slipped a little library into the Framework that was missed by most people. That library, System.Windows.Automation, was intended to allow direct programmatic access to MS UIA from .Net. UIA, or UIAutomation is Microsoft’s replacement for MSAA (Microsoft Active Accessibility), and is designed to expose window controls to accessibility devices, like screen readers for the blind. However, since it exposes all manner of window controls and operations through a direct programming interface, UIA is one of the most useful tools for UI Testers who are trying to write automation for Windows applications.1 In other words, if you want to write a program to drive the UI of another program for your automated tests, then System.Windows.Automation is where to begin.

Where to begin with SWA itself is a bit of a mystery, though. The documentation was sparse and confusing when I first started playing around with it, so most of what I know what a result of tinkering until it worked or searching the Internet until I found a similar confused person that had already solved the same problem and posted the solution. That’s why I’m writing this tutorial. I found that SWA had a learning cliff to overcome, so I hope to spare you some of the same trouble by explaining what I had to discover the hard way.

First, though, let’s take a trip through a highly opinionated aside about the general design of SWA. .Net 1.0 was beautiful and clean and easy. Everything made sense. .Net 1.1 cleaned more things up and made it even better. Then .Net 2.0 came out and the awesome was truly solidified by the introduction of generics and anonymous delegates. After that, everything fell apart. .Net 3.0 and 3.5 saw the introduction of bizarre things like WCF and Linq and semi-awesome, yet complicated things like WPF. It was like all the people who had guided the .Net Framework up through version 2 and shaped it with the mantra “Make it easy, make it clean” had been thrown out in a coup and replaced by an evil cabal of leftover COM programmers who wanted to restore the glory of MFC and ATL.

System.Windows.Automation seems to have been designed by one of these groups. At it’s core, it seems that the people who wrote it had never heard of things like interfaces or the MAUI libraries.2 When you work in SWA, you get generic AutomationElement objects, but they’re not the control type you want, and you can’t case them to the control type you want. There’s no Button class or TextBox class that you can get. Instead, you have to ask the element for the control pattern you’re interested in, and only then will you get an object that you can use. When I was first working with SWA, this approach made absolutely no sense to me. Why can’t I get an AutomationElement that I can cast to ButtonElement or IButtonElement and use directly? Why do I have to ask for these control patterns and get back some strange type? Then, about a year ago, I discovered what the model was. At that time, I was developing a toolbar for Internet Explorer, which requires extensive use of COM. This was my first exposure the the special brand of hell that is COM programming, as I mercifully had spent the late 90’s in the sheltered arena of school, and by the time I joined the real world, everyone was using .Net. When I saw QueryInterface in COM and what it was doing, it struck me that it was exactly the same thing that I’d had to do with AutomationElement.GetCurrentPattern().

The people who designed System.Windows.Automation had brought QueryInterface into the world of .Net. There is a special place in Hell for doing things like that.

Anyway, the utility of the library is enough to overcome any stupid choices in its design. So, let’s get going!

First, you’ll want to get the UISpy tool. You may already have it buried in your Visual Studio installation, but if not, head over to the MSDN and try to track it down. It’s usually part of the Windows SDK or .Net SDK, except when Microsoft apparently forgets to include it. I got mine from the Developer Tools in the Vista SDK Update, but you might want to see if there’s a better place by the time you read this.

UISpy is a lot like Spy++, which has been around since at least the VS6 days. You can walk the window control tree and find window handles and window classes and other things like that, just like Spy++, but it’s been extended with support for UIA. Once you get it, take it for a spin around your window hierarchy.3 I’d suggest turning on the “Hover” tracking mode, which lets you select a window to inspect by holding the CTRL key and hovering over a window. It’ll sometimes take a while to get to the control you’re selecting, but that’s what a full tree walk will do to you.


This screenshot shows the basic window of UISpy. On the left is the control hierarchy. On the right are the properties of the currently selected window. You’ll become very familiar with some of these properties and you’ll decide that other properties are completely useless. The determination of which fields are useful or useless is left as an exercise to the reader.


Here’s an example of what UISpy will tell you about the window used by the Windows Calculator. On the left side, you can see that it has a bunch of child controls. They’re marked as check boxes, radio buttons, buttons, even an edit box. If the window were bigger, you’d also see that it has a title bar and menu bar. You can get information on all of these objects and interact with most of these objects. Pretty much anything you see here is something you can use SWA to control. On the right side are the properties for the selected object. Things like AutomationId, Name, and ClassName are generally good identifiers, while fields like ProcessId and RuntimeId may change from run to run.

At the bottom of the property list are the Control Patterns supported by this element. Control Patterns are how SWA interacts with controls. For instance, in this screenshot, it shows that the main calculator window supports the Transform pattern and the Window pattern. The Transform pattern means that you may be able to perform move, rotate, and resize actions on this object. In this case, the calculator reports that you can move it, but that you can’t resize or rotate it. If you right click on the element in the tree on the left side and select “Control Patterns” from the menu, you’ll get a dialog where you can trigger some of the methods on a supported control pattern. When you get to writing your automation program, you’ll ask for one of these Control Patterns and be able to use it to drive the control. There are other ControlPatterns, like “Invoke” for buttons and “Text” or “Value” for things like text boxes. You’ll probably find that you only use a small handful of these patterns regularly.

If you looked at that last screenshot of UISpy, you probably noticed that odd rectangle floating over the screen. If you’re playing the home game along with me, then you’ve probably had your own rectangle floating about. It’s the UISpy highlight window, showing the outline of the last window you selected.4 It’ll go away if you close UISpy, but I’ve found that you’ll tune it out. Sometimes I’ve had it linger around for several hours after I get the information I’ve needed, until someone comes by and asks me what that strange red thing on my screen is. If you move the mouse over the edge of the box, you’ll get a tooltip with a little bit of information on the selected window.

Anyway, we’ve been playing around in the ever-important UISpy, but we haven’t gotten around to actually using SWA yet. Giving that’s what this article is supposed to be about, let’s get to it.

My example code can be pulled from SVN here: https://mathpirate.net/svn/Projects/SWAExample/

I’m creating a Console App, but there’s no reason you can’t use SWA in a test DLL or a Windows app or whatever.5 It’s just a .Net library.

To begin, add references to UIAutomationClient, UIAutomationClientsideProviders and UIAutomationTypes.6


After that, add a using for System.Windows.Automation.

Now you’re ready to get rolling. The main class you’ll be using is the AutomationElement class. The entire control hierarchy is made up of AutomationElements. Think of them as an analog to the Control class in Windows Forms. It’s important to note that you’ll never create an AutomationElement yourself. AutomationElement does not have any constructors on it that you’re allowed to use. Instead, you’ll use methods on AutomationElements to give you other AutomationElements. The AutomationElement class has a number of static methods on it. Type “AutomationElement.” to bring up your good friend Intellisense to tell you what to do.

The first thing you’ll notice is that the AutomationElement has a hell of a lot of static fields on it. They’re pretty much all Dependency Property garbage. If you don’t know what Dependency Properties are, think of them as an enum value that will let you pass the name of a property to access on an object. (And if you do know what they are, then you know that the explanation I gave is horribly oversimplified and pretty much wrong and misleading. SHHH! Don’t tell anyone!) You can ignore them for now, but they’ll come back to haunt us in a bit. Right now, there are only four things you’ll care about on Automation Element:

  • RootElement: A reference to the root automation element, otherwise known as the main desktop window. Everything else is a child of this element. If you have to search for an element, you probably want to use this element as your base, at least until you find a better parent element to operate from.
  • FromHandle(IntPtr hwnd): If you have the window handle to the window or control you want to work with, use this method to grab an AutomationElement. It’ll be faster than searching for it and it will also give you exactly what you were looking for. I almost always start here rather than starting with a search, because you really don’t want to walk the entire control tree looking for something if you don’t have to.
  • FocusedElement: If the element you’re interested in has focus, use this and go straight there. No searching and no window handles necessary.
  • FromPoint(Point pt): Need the control at 132, 526? Use this. I’m not sure if this will do a tree walk, so use at your own risk.

To begin my example, I’m going to launch an instance of the Windows Calculator application, then use FromHandle to grab the Calculator window and print out some information on it. (BTW, I’m running XP, so if you’re playing along at home, the calculator may be different in your operating system.)

//Launches the Windows Calculator and gets the Main Window's Handle.
Process calculatorProcess = Process.Start("calc.exe");
IntPtr calculatorWindowHandle = calculatorProcess.MainWindowHandle;

//Here I use a window handle to get an AutomationElement for a specific window.
AutomationElement calculatorElement = AutomationElement.FromHandle(calculatorWindowHandle);

if(calculatorElement == null)
throw new Exception("Uh-oh, couldn't find the calculator...");

//Walks some of the more interesting properties on the AutomationElement.
Console.WriteLine("AutomationId: {0}", calculatorElement.Current.AutomationId);
Console.WriteLine("Name: {0}", calculatorElement.Current.Name);
Console.WriteLine("ClassName: {0}", calculatorElement.Current.ClassName);
Console.WriteLine("ControlType: {0}", calculatorElement.Current.ControlType.ProgrammaticName);
Console.WriteLine("IsEnabled: {0}", calculatorElement.Current.IsEnabled);
Console.WriteLine("IsOffscreen: {0}", calculatorElement.Current.IsOffscreen);
Console.WriteLine("ProcessId: {0}", calculatorElement.Current.ProcessId);

//Commented out because it requires another library reference. However, it's useful to see that this exists.
//Console.WriteLine("BoundingRectangle: {0}", calculatorElement.Current.BoundingRectangle);

Console.WriteLine("Supported Patterns:");
foreach (AutomationPattern supportedPattern in calculatorElement.GetSupportedPatterns())
Console.WriteLine("\t{0}", supportedPattern.ProgrammaticName);

(Apologies for the horizontal scrollies…)

The example above will output something like this, although your specific values may vary.

Name: Calculator
ClassName: SciCalc
ControlType: ControlType.Window
IsEnabled: True
IsOffscreen: False
ProcessId: 3660
Supported Patterns:

As you may have noticed, the information that was just printed out here is the same that was in UISpy, although the output in my example has been edited for time. Of course, if you ran this sample, you probably also noticed that the Calculator remains open after the app exits. That’s not very polite. Let’s clear that up now.

One of the patterns listed supported by the main window is, surprisingly enough, the WindowPattern. If you looked at what methods are on the WindowPattern back when you were playing around in UISpy, you may have noticed that there’s a method called Close which you can call. Something tells me that method will be useful for our current situation. I think I’m going to give it a spin.

(By the way, for my sanity and to make the examples more compact, I’m going to be moving parts of the sample code into helper functions as I go. For instance, all of those WriteLine statements have been put into a method called “PrintElementInfo”. So, if you see an odd function in the samples, that’s probably all it is. I’m not going to intentionally leave out important code that you’ll need to make things work.)

In order to get a control pattern off an AutomationElement object, you have to call QueryInt- er, I mean, you have to call GetCurrentPattern on the object. The GetCurrentPattern method takes an AutomationPattern object. AutomationPattern has a static LookupById() method on it, which is completely worthless to you, and, like everything else we’ve seen, has no public constructor. So, WTF, where are you supposed to get the pattern from? In a complete failure to make the code usable from Intellisense alone, you have to use a static member off of the type of the pattern you want to retrieve. You want to use a text box? Use TextPattern.Pattern. Need to play with a dropdown combo box? SelectionPattern.Pattern. We want the WindowPattern7, so we’re going to call GetCurrentPattern(WindowPattern.Pattern). Of course, GetCurrentPattern returns the pattern object as an ever-helpful IUnkno- I mean, object type, so you have to cast it.

Once you have the WindowPattern object, a quick examination of its members shows that it has a Close() method. Calling it should close the calculator and clean up after our program.

Here’s what those lines look like in code. Add them to the end of the sample and watch the window disappear!

//Get the WindowPattern from the window and use it to close the calculator app.
WindowPattern calculatorWindowPattern = (WindowPattern)calculatorElement.GetCurrentPattern(WindowPattern.Pattern);

So, there you go! That’s all you need to know about System.Windows.Automation! You can find a window and close it, therefore, you can do anything! Have at it!

Or… Not… Let’s continue, shall we?

Since this is a calculator, let’s calculate something. Something too complex to do by hand, something we need the full power of a modern multi-core computer to figure out. Something like “What do you get if you multiply six by nine”, perhaps? To begin, you’ll need to list out the steps that you take when you manually perform this action.

  1. Open Calculator. (Hey! We did that already. We’re awesome.)
  2. Type “6”.
  3. Press the Multiplication button.
  4. Type “9”.
  5. Press the Equals button.
  6. Read the result and know the answer.

So, the first thing we need to do is type “6” into the calculator text box. So, we need to find the Calculator’s text box. Let’s bring up our friend UISpy to find out how to reference that box.


So, we’ve got a class name of “Edit”, a control type “ControlType.Edit” and an AutomationID of “403”. That should be enough to find the control we’re looking for, so let’s get to the code and grab it.

(BTW, obviously, this code will have to go before the Close method call we added. You can’t use a calculator that isn’t running anymore…)

An AutomationElement object has two methods that let you search the control hierarchy for the elements you’re interested in: FindAll and FindFirst. Since we’re only expecting a single text box, we’ll be using FindFirst. Intellisense will show you that FindFirst has two parameters: FindFirst(TreeScope scope, Condition condition);

TreeScope is an enum, so it’s very Intellisensible and clear. You’re like to use the values “Children” and “Descdendants” the most. Children limits the search to the immediate children of the element you’re searching on, while Descendants are the children and the children of children and so on, all the way to the bottom. I prefer to use Descendants by default, unless I know that I want something else. It should be noted that the Parent and Ancestor scopes are listed as “Not Supported”, so don’t expect to be able to use them. Anyway, we’ll use TreeScope.Descendants here.

Condition, on the other hand, offers no Intellisense help for you at all. That’s because Condition is an abstract base class of many conditions. There’s PropertyCondition, which will match based on a property value, and And/Or/Not conditions, which can be used to group multiple conditions logically. Off of the Condition class are static True and False conditions. And, if you need your own sort of crazy condition, I think you can derive from Condition and make one yourself, although I would question your MentalCondition if you were to do that without good reason. PropertyCondition is the only stock condition that you’ll find yourself using, and it’s also the only one that requires any kind of in depth explanation.

Warning! We are about to be haunted by Dependency Properties!

PropertyCondition allows you to specify the value you want a property to match for your control tree search. PropertyCondition actually has a constructor, to which you pass an AutomationProperty and a value to match. The AutomationProperty parameter is where Dependency Properties come in. You have to pass in one of the static values from the AutomationElement that I told you to ignore earlier. If you look, you’ll find that there’s one of these static values for each of the properties on an AutomationElement instance. So, if you want to find an AutomationElement that has an AutomationId of 403 (Which, coincidentally, is what we want to find), then you’ll use AutomationElement.AutomationIdProperty in your PropertyCondition. Like so:

PropertyCondition editBoxAutomationIDProperty = new PropertyCondition(AutomationElement.AutomationIdProperty, "403");

(Note that the value “403” is passed as a string. That’s because AutomationId is a string, and the types need to match. You’ll have to make sure that you’re passing the same type as the property yourself, otherwise you’ll get an exception at runtime.)

Then you pass that condition to your FindFirst call and presto, you get the element you’re looking for. (Or null or possibly an exception or maybe some other element that happens to match what you asked for or that isn’t what you wanted…) I’m going to do that now, but first, we need something to call FindFirst on. Doing a tree search can be very slow, on the order of 10+ seconds per call in some cases, so you want to limit the scope of the search. If you don’t have any element to go on, then you can use the static AutomationElement.RootElement property that I mentioned earlier, and that will look through EVERYTHING. However, we already have the main calculator window, so let’s just assume that anything in the window, including the edit box, will be a descendant of that window and use that as the starting point of our search. That gives us this:

PropertyCondition editBoxAutomationIDProperty = new PropertyCondition(AutomationElement.AutomationIdProperty, "403");
AutomationElement editBoxElement = calculatorElement.FindFirst(TreeScope.Descendants, editBoxAutomationIDProperty);

Printing out the element that’s returned gives you something like this:

AutomationId: 403
ClassName: Edit
ControlType: ControlType.Edit
IsEnabled: True
IsOffscreen: False
ProcessId: 1884
Supported Patterns:

Looks like the one we want, so let’s use it. We want to set the value of the control, so let’s grab the ValuePattern for the text box and use the SetValue method to set the value of the element to “6”, our first number.

ValuePattern editBoxValuePattern = (ValuePattern)editBoxElement.GetCurrentPattern(ValuePattern.Pattern);

Now you hit run and…

System.InvalidOperationException was unhandled
   Message="Exception of type 'System.InvalidOperationException' was thrown."
        at System.Windows.Automation.ValuePattern.SetValue(String value)
        at SWACalculatorExample.Program.Main(String[] args) in E:\svn\Projects\SWAExample\SWACalculatorExample\Program.cs:line 28
        at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
        at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
        at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
        at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
        at System.Threading.ThreadHelper.ThreadStart()


You followed the directions, you got an instance of the correct pattern, it should have worked, but didn’t. So what happened? Well, if you took the time to investigate the edit box in UISpy, you would have noticed this little tidbit down in the information about the Value pattern:

IsReadOnly: "True"

Lovely, so the edit box is read-only, which means we can’t assign the value in that way. Now, for a confession: I noticed that the box was read-only early on, but I still dragged you down this dead end for three reasons:

  1. I wanted to teach you that life sucks sometimes.
  2. I wanted to teach you how to use ValuePattern to set the value of a text box that’s not read-only.
  3. I wanted to illustrate one of the most important skills for an automation developer to have: The ability to come up with a Plan B workaround when the automation tool fails you. Because it will fail you. Frequently. And at the most annoying times.

The most common Plan B is to use System.Windows.Forms.SendKeys to send a series of keystrokes to do what you want. (If you’re using the VS Testing stuff, there’s also a Keyboard class that exposes essentially the same thing.) It’s less reliable than the SWA methods, so use them when you can, but when you can’t, SendKeys might just do the trick. It has a wide grammar for sending complex keystrokes and it’s well work browsing the documentation to see what it can do, but for now, we just need to type the number “6”. Focus the edit box first, using the SetFocus() method on the edit box AutomationElement. That will make the key strokes go where we want, then use SendKeys.SendWait(“6”) to simulate the keypress. (SendKeys also requires a reference to System.Windows.Forms, for don’t forget to set that up if you don’t have one already.)

//Since the direct ValuePattern method didn't work, this is Plan B, using SendKeys.

If you run it now, well, you’ll see the calculator open and close really fast, but trust me, if you watch really really carefully (or comment out the Close line), you’ll see that the edit box will have the number 6 that we just “typed”.

Now that that’s out of the way, it’s time to hit the multiplication button. You grab the button the same way you grabbed the edit box: Look in UISpy for something uniquely identifying the control, then run a tree search and get what you’re looking for. In this case, the multiply button has a meaningful name that we can use: “*”. Again, it will be a PropertyCondition, but this time, we’ll have to use the AutomationElement.NameProperty as the property to match against. Once you have the button, you’ll want to click on it. The button click action is handled by an InvokePattern, so ask the button for its InvokePattern and call Invoke on it to click the button.

//Grab the multiplication button by name, then click on it.
AutomationElement multiplyButtonElement = calculatorElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "*"));
InvokePattern multiplyButtonInvokePattern = (InvokePattern)multiplyButtonElement.GetCurrentPattern(InvokePattern.Pattern);

Now we need to enter the “9” and press the “=” button to get our answer. I’ll leave that up to you, since that’s pretty much a copy and paste of the last two things I just got done doing. You may even want to take this opportunity to refactor. You’ll find that using SWA will result in tons of areas in your code where one logical action takes three or four long lines of code, and you’ll end up with those lines copied all over the place. For instance, by parameterizing the last block of code, you can make a function called “InvokeChildButtonByName” and turn those three lines of ugly into one line that makes sense. I would strongly recommend moving as much of your SWA related code into various helper functions or classes because, quite simply, SWA code is ugly and will distract from what you’re actually trying to do.

At this point, we’re finished with the first 5 steps in performing the calculation, and are left only with the sixth and final step: Reading the result. If you recall, we’ve already seen how to pull the ValuePattern from the edit box. We couldn’t use it at the time, but that’s because it was read-only. Now that we only want to read from it, its read-onlyness shouldn’t be a problem. Once you have the pattern, in order to get the value, you’ll have to use the .Value property off the instance. However, .Value isn’t on the pattern itself. To get to it, you either have to go through the .Current or .Cached properties first. I’m not entirely sure what all of the features and limitations and differences are between .Current and .Cached, but I know that .Current usually works and that .Cached usually doesn’t, therefore I’d strongly recommend using .Current.

ValuePattern editBoxValuePattern = (ValuePattern)editBoxElement.GetCurrentPattern(ValuePattern.Pattern);
string editBoxResult = editBoxValuePattern.Current.Value;

At this point, the string editBoxResult will have the answer to the question.

Six by nine: 54.

That’s it. That’s all there is.

If you want the full source code to the example, go here.

At this point, you should know about as much about SWA as I learned in three days of fighting with the technology and documentation. Points to remember:

  • Use UISpy to inspect windows and controls to find some uniquely identifying set of information.
  • Use FromHandle, FindFirst, or FindAll to grab elements that you’re interested in using.
  • Use GetCurrentPattern to ask for an object that you can use to interact with the control in a specific way. ValuePattern and InvokePattern are two of the most common ones you’re likely to use, but become familiar with the others for when you find a different control.
  • If all else fails, use a dirty hack workaround.8

Of course, this posting only talks about how to use SWA to write automation. It completely omits the other half of the equation, which is to write a UI application that can play nicely with SWA, which will lead to happier testers. The example of using Calculator was simple. In a lot of applications, you’ll run into controls without automatable IDs, leaving you to do nasty things like grab all of the Edit controls in a window and selecting the third one from the list and hoping that it’s actually the edit box that you want. You’ll find buttons that don’t actually click, forcing you to perform crazy workarounds. It doesn’t have to be like that. When writing an application, you can implement some support for SWA, making it easier to reference the elements in your tests later. Perhaps I’ll cover that in a later post.

  1. It’s also supported by Silverlight, so all you Internet testers don’t have to feel left out. []
  2. MAUI was an internal library that was widely used for UI Automation by teams within Microsoft back when I did my time as a contractor in 2004. It was a .Net wrapper which had classes for every type of UI control. It was simple to understand and use, largely because if you wanted to interact with a button or a text box, there was a Button class and a TextBox class that you could program against. It was almost a mirror to Windows Forms. []
  3. You’ll probably find that it doesn’t work for web pages, because they’re custom drawn panels and not real Windows controls. It won’t even work for Firefox at all, because that browser is laid out entirely in XUL. I’ll probably talk about browser automation in a later article. []
  4. Which is usually the main editor window in Visual Studio, because every time you do a CTRL-C, CTRL-V, you’ll be hitting the CTRL hotkey, which makes UISpy select the window the mouse is over. []
  5. There is, however, a restriction on the environment where you can use SWA. Since it interacts with window controls and UI things, you must have a UI present for it to interact with. That means if you try to use SWA in a headless session, like the one your automated tests running on your build server will be executed in, you’re screwed. If you can, you may need to have your automated tests run on a server that’s always logged into a window session and unlocked. If not, you can do what I did, which was write a program to launch a virtual machine on an instance of Virtual Server, then remotely execute your tests within that virtual machine. Although complicated, you may actually find it easier to implement that solution than it is to get the corporate computing security policy altered so that it doesn’t get in the way of doing your job. []
  6. And why these all aren’t under one unified System.Windows.Automation library, I don’t know. Or at least named System.Windows.Automation.Client, .ClientsideProviders, and .Types. More evidence that the SWA team probably hadn’t used .Net before writing this library and that all the people keeping things sane had fled the scene. []
  7. Not to be confused with the Willow Pattern []
  8. And when that fails, then you can use fire. []


1 SweatCode.com { 02.19.10 at 14:23:00 [974] }

I’ve got a related write-up on testing Windows UI using the accessibility libraries. It’s nice to see I’m not the only one !

2 ThomasSchrantz { 02.19.10 at 19:17:08 [178] }

Yeah, I felt somewhat compelled to write a few posts about SWA and UIAutomation after I didn’t find anything good when I first tried to use it. It took me a day to figure it out. I hope that these posts make it so others don’t have to waste as much time getting started as I did.
I’ve been thinking about writing a few more posts on the subject, if I can find the time.

3 Using UISpy to Handle the Windows Security Dialog in Windows 7 | Nithin's Blog { 02.26.13 at 18:41:06 [153] }

[…] this point, I’d like to refer to the excellent tutorial on https://mathpirate.net/log/2009/09/27/swa-straight-outta-redmond/. He walks thru using UISpy to automate calc with UIAutomation. Go take a few minutes and read that, […]

Leave a Comment