Description

The script uses .Net Framework and System.Windows.Automation classes to browse Windows UI elements hierarchy. When started, the script shows a hierarchy of Windows UI elements on the left hand side of the form. Details for each element can be viewed on the right hand side by clicking on an element. The script displays most of the element properties as well as UI automation patterns available for the element.

When running the script from PowerShell console, the -sta switch should be used. The UI element retrieval can be slow when enumerating elements of applications with complex UI, lsuch as Microsoft Visual Studio or Internet Explorer.

The information can be useful when writing scripts that ise UIAutomation which use AutomationElement.FindFirst() and AutomationElement.FindAll() methods to search for UI elements - the search can be performed by any of AutomationElement properties and automation patterns can be used to simulate user interaction.

The script is long (more than 400 lines), but most of the code is just for Windows Forms GUI layout.

Script

 

PowerShell
Edit|Remove
#requires -version 2.0


### When running the script from the PowerShell console
### the console should be started with the -sta switch

[void] [Reflection.Assembly]::Load('System.Windows.Forms, '+
    'Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089')
[void] [Reflection.Assembly]::Load('UIAutomationClient, ' + 
    'Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35')
[void] [Reflection.Assembly]::Load('UIAutomationTypes, ' + 
    'Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35')


### Functions

function AddHelperClass
{

# Loads C# class that is used only
# to get the UIAutomation root element

$source = @"

using System;
using System.Windows.Automation;

namespace UIAutomationHelper
{
    public class Element
    {
        public static AutomationElement Root
        {
            get
            {
                return AutomationElement.RootElement;
            }
        }
    }
} 
"@

Add-Type -TypeDefinition $source -ReferencedAssemblies( `
    "UIAutomationClient", "UIAutomationTypes")
}

function Walk
{
    # $node - TreeViewNode
    # $element - AutomationElement
    # Adds a child tode to $node for each $element child

    Param($element, $node)
    
    $node.Nodes.Clear()
    
    # TrueCondition should return all child AutomationElements
    # notPSCondition excludes excludes both the PowerShell host
    # but slows down the script a bit
    
    $PSCondition = New-Object Windows.Automation.PropertyCondition(
        [Windows.Automation.AutomationElement]::ProcessIdProperty, $pid)
    $notPSCondition = New-Object Windows.Automation.NotCondition(
        $PSCondition)
    
    #$trueCondition = [Windows.Automation.Condition]::TrueCondition
    
    $walker = New-Object Windows.Automation.TreeWalker($notPSCondition)    
    
    $childElement = $walker.GetFirstChild($element)

    while($childElement -ne $null)
    {   
        # Set TreeNode text to AutomationElement name + 
        # AutomationElement type. 
        #Some AutomationElements don't have names.

        $childNode = 
            New-Object Windows.Forms.TreeNode('"' + $childElement.Current.Name + 
            '", ' + $childElement.Current.ControlType.LocalizedControlType)
      
        $childNode.Tag = $childElement
        
        # Add an empty node to each subnode so that the + appears 
        # in the TreeView before it when the parent node is expanded.
        # The empty node is removed before the parent node gets expanded.
        
        $emptyNode = New-Object Windows.Forms.TreeNode("empty")
        $emptyNode.Name = "empty"
        $childNode.Nodes.Add($emptyNode)
    
        $node.Nodes.Add($childNode)
        $childElement = $walker.GetNextSibling($childElement)
    }
}

### end functions

### form layout
   
$form = New-Object System.Windows.Forms.Form
   
$form.Text = "UIAutomation Browser"
$form.Width = 650
$form.Height = 650

# SplitContainer left panel contains a TreeView
# with nodes representing AutomationElement chierarchy.
# The right panel contains a ListView
# to display the details of the AutomationElement
# when it is clicked in the TreeView

$splitContainer = New-Object System.Windows.Forms.SplitContainer
$splitContainer.Name = "splitContainer"
$form.Controls.Add($splitContainer) 
$splitContainer.Dock = [System.Windows.Forms.Dockstyle]::Fill
$splitContainer.SplitterDistance = 300

# ListView to display AutomationElement details

$details = New-Object System.Windows.Forms.ListView
$details.View = [System.Windows.Forms.View]::Details
$details.FullRowSelect = $true

# ListView groups based on UISpy details panel

$details.Groups.Add("GeneralAccessibility", "General accessibility") | Out-Null
$details.Groups.Add("State", "State") | Out-Null
$details.Groups.Add("Identification", "Identification") | Out-Null
$details.Groups.Add("State", "State") | Out-Null
$details.Groups.Add("Visibility", "Visibility") | Out-Null
$details.Groups.Add("ControlPatterns", "Control patterns") | Out-Null

# ListView columns

$details.Columns.Add("PropertyName", "Property name") | Out-Null
$details.Columns.Add("PropertyValue", "Property value") | Out-Null
$details.Columns["PropertyName"].Width = 150
$details.Columns["PropertyValue"].Width = 180

# All ListView items - various information 
# about the selected AutomationElement

$AccessKey = New-Object System.Windows.Forms.ListViewItem
$AccessKey.Name = "AccessKey"
$AccessKey.Text = "Access key"
$AccessKey.SubItems.Add("") | Out-Null
$AccessKey.Group = $details.Groups["GeneralAccessibility"]

$AcceleratorKey = New-Object System.Windows.Forms.ListViewItem
$AcceleratorKey.Name = "AcceleratorKey"
$AcceleratorKey.Text = "Accelerator key"
$AcceleratorKey.SubItems.Add("") | Out-Null
$AcceleratorKey.Group = $details.Groups["GeneralAccessibility"]

$IsKeyboardFocusable = New-Object System.Windows.Forms.ListViewItem
$IsKeyboardFocusable.Name = "IsKeyboardFocusable"
$IsKeyboardFocusable.Text = "Is keyboard focusable"
$IsKeyboardFocusable.SubItems.Add("") | Out-Null
$IsKeyboardFocusable.Group = $details.Groups["GeneralAccessibility"]

$HelpText = New-Object System.Windows.Forms.ListViewItem
$HelpText.Name = "HelpText"
$HelpText.Text = "Help text"
$HelpText.SubItems.Add("") | Out-Null
$HelpText.Group = $details.Groups["GeneralAccessibility"]

$IsEnabled = New-Object System.Windows.Forms.ListViewItem
$IsEnabled.Name = "IsEnabled"
$IsEnabled.Text = "Is enabled"
$IsEnabled.SubItems.Add("") | Out-Null
$IsEnabled.Group = $details.Groups["State"]

$HasKeyboardFocus = New-Object System.Windows.Forms.ListViewItem
$HasKeyboardFocus.Name = "HasKeyboardFocus"
$HasKeyboardFocus.Text = "Has keyboard focus"
$HasKeyboardFocus.SubItems.Add("") | Out-Null
$HasKeyboardFocus.Group = $details.Groups["State"]

$RuntimeId = New-Object System.Windows.Forms.ListViewItem
$RuntimeId.Name = "RuntimeId"
$RuntimeId.Text = "Runtime Id"
$RuntimeId.SubItems.Add("") | Out-Null
$RuntimeId.Group = $details.Groups["Identification"]

$ClassName = New-Object System.Windows.Forms.ListViewItem
$ClassName.Name = "ClassName"
$ClassName.Text = "Class name"
$ClassName.SubItems.Add("") | Out-Null
$ClassName.Group = $details.Groups["Identification"]

$ControlType = New-Object System.Windows.Forms.ListViewItem
$ControlType.Name = "ControlType"
$ControlType.Text = "Control type"
$ControlType.SubItems.Add("") | Out-Null
$ControlType.Group = $details.Groups["Identification"]

$AutomationId = New-Object System.Windows.Forms.ListViewItem
$AutomationId.Name = "AutomationId"
$AutomationId.Text = "Automation Id"
$AutomationId.SubItems.Add("") | Out-Null
$AutomationId.Group = $details.Groups["Identification"]

$LocalizedControlType = New-Object System.Windows.Forms.ListViewItem
$LocalizedControlType.Name = "LocalizedControlType"
$LocalizedControlType.Text = "Localized control type"
$LocalizedControlType.SubItems.Add("") | Out-Null
$LocalizedControlType.Group = $details.Groups["Identification"]

$ElementName = New-Object System.Windows.Forms.ListViewItem
$ElementName.Name = "Name"
$ElementName.Text = "Name"
$ElementName.SubItems.Add("") | Out-Null
$ElementName.Group = $details.Groups["Identification"]

$ProcessId = New-Object System.Windows.Forms.ListViewItem
$ProcessId.Name = "ProcessId"
$ProcessId.Text = "Process Id"
$ProcessId.SubItems.Add("") | Out-Null
$ProcessId.Group = $details.Groups["Identification"]

$IsPassword = New-Object System.Windows.Forms.ListViewItem
$IsPassword.Name = "IsPassword"
$IsPassword.Text = "Is password"
$IsPassword.SubItems.Add("") | Out-Null
$IsPassword.Group = $details.Groups["Identification"]

$IsControlElement = New-Object System.Windows.Forms.ListViewItem
$IsControlElement.Name = "IsControlElement"
$IsControlElement.Text = "Is control element"
$IsControlElement.SubItems.Add("") | Out-Null
$IsControlElement.Group = $details.Groups["Identification"]

$IsContentElement = New-Object System.Windows.Forms.ListViewItem
$IsContentElement.Name = "IsContentElement"
$IsContentElement.Text = "Is content element"
$IsContentElement.SubItems.Add("") | Out-Null
$IsContentElement.Group = $details.Groups["Identification"]

$BoundingRectangle = New-Object System.Windows.Forms.ListViewItem
$BoundingRectangle.Name = "BoundingRectangle"
$BoundingRectangle.Text = "Bounding rectangle"
$BoundingRectangle.SubItems.Add("") | Out-Null
$BoundingRectangle.Group = $details.Groups["Visibility"]

$ClickablePoint = New-Object System.Windows.Forms.ListViewItem
$ClickablePoint.Name = "ClickablePoint"
$ClickablePoint.Text = "Clickable point"
$ClickablePoint.SubItems.Add("") | Out-Null
$ClickablePoint.Group = $details.Groups["Visibility"]

$IsOffScreen = New-Object System.Windows.Forms.ListViewItem
$IsOffScreen.Name = "IsOffScreen"
$IsOffScreen.Text = "Is off screen"
$IsOffScreen.SubItems.Add("") | Out-Null
$IsOffScreen.Group = $details.Groups["Visibility"]

$details.Items.Add($AccessKey) | Out-Null
$details.Items.Add($AcceleratorKey) | Out-Null
$details.Items.Add($IsKeyboardFocusable) | Out-Null
$details.Items.Add($HelpText) | Out-Null
$details.Items.Add($IsEnabled) | Out-Null
$details.Items.Add($HasKeyboardFocus) | Out-Null
$details.Items.Add($ClassName) | Out-Null
$details.Items.Add($ControlType) | Out-Null
$details.Items.Add($AutomationId) | Out-Null
$details.Items.Add($LocalizedControlType) | Out-Null
$details.Items.Add($ElementName) | Out-Null
$details.Items.Add($Processid) | Out-Null
$details.Items.Add($RuntimeId) | Out-Null
$details.Items.Add($IsPassword) | Out-Null
$details.Items.Add($IsControlElement) | Out-Null
$details.Items.Add($IsContentElement) | Out-Null
$details.Items.Add($BoundingRectangle) | Out-Null
$details.Items.Add($ClickablePoint) | Out-Null
$details.Items.Add($IsOffScreen) | Out-Null

# End of the ListView items

# Add the ListView to the SplitContainer right panel

$splitContainer.Panel2.Controls.Add($details)
$details.Dock = [System.Windows.Forms.Dockstyle]::Fill

# The AutomationElement TreeView

$tree = New-Object System.Windows.Forms.TreeView
$tree.Dock = [System.Windows.Forms.Dockstyle]::Fill
$tree.Name = "tree"

# The TreeView BeforeExpand event code:
# enumerate all child AutomationElements
# Node.Tag contains a reference to the 
# AutomationElement corresponding to the expanded TreeView node

$tree_BeforeExpand = 
{
    Walk -element $_.Node.Tag -node $_.Node | out-null
}

# The TreeView BeforeSelect event code:
# Set ListView item text to the AutomationElement
# property values

$tree_BeforeSelect = 
{
    # Set text for each item to empty string

    foreach($item in $details.Items)
    {
        $item.SubItems[1].Text = ""
    }
    
    # Set values for the ListView items
    
    $details.Items["AccessKey"].SubItems[1].Text = 
        $_.Node.Tag.Current.AccessKey
    $details.Items["AcceleratorKey"].SubItems[1].Text = 
        $_.Node.Tag.Current.AcceleratorKey
    $details.Items["IsKeyboardFocusable"].SubItems[1].Text = 
        $_.Node.Tag.Current.IsKeyboardFocusable
    $details.Items["HelpText"].SubItems[1].Text = 
        $_.Node.Tag.Current.HelpText
    $details.Items["IsEnabled"].SubItems[1].Text = 
        $_.Node.Tag.Current.IsEnabled
    $details.Items["HasKeyboardFocus"].SubItems[1].Text = 
        $_.Node.Tag.Current.HasKeyboardFocus
    $details.Items["ClassName"].SubItems[1].Text = 
        $_.Node.Tag.Current.ClassName
    $details.Items["ControlType"].SubItems[1].Text = 
        $_.Node.Tag.Current.ControlType.ProgrammaticName
    $details.Items["AutomationId"].SubItems[1].Text = 
        $_.Node.Tag.Current.AutomationId
    $details.Items["LocalizedControlType"].SubItems[1].Text = 
        $_.Node.Tag.Current.LocalizedControlType
    $details.Items["Name"].SubItems[1].Text = 
        $_.Node.Tag.Current.Name
    $details.Items["ProcessId"].SubItems[1].Text = 
        $_.Node.Tag.Current.ProcessId.ToString()
        
        foreach($RID in $_.Node.Tag.GetRuntimeId())
    {
        $details.Items["RuntimeId"].SubItems[1].Text += 
            ($RID.ToString() + " ")
    }
    
    $details.Items["IsPassword"].SubItems[1].Text = 
        $_.Node.Tag.Current.IsPassword
    $details.Items["IsControlElement"].SubItems[1].Text = 
        $_.Node.Tag.Current.IsControlElement
    $details.Items["IsContentElement"].SubItems[1].Text = 
        $_.Node.Tag.Current.IsContentElement
    $details.Items["BoundingRectangle"].SubItems[1].Text = 
        $_.Node.Tag.Current.BoundingRectangle
    try
    {
        $details.Items["ClickablePoint"].SubItems[1].Text = 
            $_.Node.Tag.GetClickablePoint()
    }
    catch [System.Windows.Automation.NoClickablePointException]
    {
        $details.Items["ClickablePoint"].SubItems[1].Text = '(Null)'
    }
    $details.Items["IsOffScreen"].SubItems[1].Text = 
        $_.Node.Tag.Current.IsOffScreen
    
    foreach($item in $details.Items)
    {
        if($item.Group -eq $details.Groups["ControlPatterns"])
        {
            $details.Items.Remove($item)
        }
    }
           
    foreach($pattern in $_.Node.Tag.GetSupportedPatterns())
    {
        $item = New-Object System.Windows.Forms.ListViewItem
        $item.Name = $pattern.ProgrammaticName
        $item.Text = $pattern.ProgrammaticName.Replace("PatternIdentifiers.", " ")
        $item.SubItems.Add("")
        $item.Group = $details.Groups["ControlPatterns"]
        $details.Items.Add($item)
    }
}

# Add event code to the TreeView

$tree.Add_BeforeExpand($tree_BeforeExpand)
$tree.Add_BeforeSelect($tree_BeforeSelect)

# Add the TreeView to the form

$splitContainer.Panel1.Controls.Add($tree)

### End form layout


# Add the C# class

AddHelperClass

# Add the root element and show the form

$root = [UIAutomationHelper.Element]::Root
$rootNode = New-Object Windows.Forms.TreeNode("Desktop")
$rootNode.Tag = $root
$tree.Nodes.Add($rootNode)

$emptyNode = New-Object Windows.Forms.TreeNode("empty")
$emptyNode.Name = "empty"
$rootNode.Nodes.Add($emptyNode)

[System.Windows.Forms.Application]::EnableVisualStyles()
[void]$form.ShowDialog()