YellowBox.Core.psm1
# Update-TypeData (Join-Path (ls $MyInvocation.MyCommand.Path).DirectoryName "YellowBox.Types.ps1xml") # ==================================================================================================== # Internal helpers <# .SYNOPSIS If necessary, abbreviates a given text. .PARAMETER Text Text which, if it surpasses a maximum length, gets abbreviated. .PARAMETER MaxLength Length to which, if needed, the given text gets abbreviated. .OUTPUTS Potentially abbreviated text. #> function Abbreviate-Text([string]$Text, [int]$MaxLength) { if ($Text.Length -le [Math]::Abs($MaxLength)) { return $Text } elseif ($MaxLength -ge 0) # start of string followed by ellipsis { # 0123456 # "abcdefg" # => "abc..." return $Text.Substring(0, $MaxLength - 3) + "..." } else # ellipsis followed by end of string { # 0123456 # "abcdefg" # => "...efg" return "..." + $Text.Substring( $Text.Length + $MaxLength + 3, -($MaxLength + 3)) } } # ==================================================================================================== # XPath-related Functions function Test-UIXPath( [parameter(Mandatory=$true)][string] $XPath, $UIObject) { if ($UIObject -eq $null) { $UIObject = Get-Item UI: } [YellowBox.Client.UIXPathNavigator] $navigator = New-Object YellowBox.Client.UIXPathNavigator $UIObject return $navigator.Matches($XPath) } function Select-UIXPath( [parameter(Mandatory=$true)][string] $XPath, $UIObject) { if ($UIObject -eq $null) { $UIObject = Get-Item UI: } [YellowBox.Client.UIXPathNavigator] $navigator = New-Object YellowBox.Client.UIXPathNavigator $UIObject $matches = $navigator.Select($XPath) while ($matches.MoveNext()) { $matches.Current.CurrentElement } } function Get-UIXPath([YellowBox.Client.UIElement] $Origin, [YellowBox.Client.UIElement] $Target) { [string] $path = "" $current = $Target while ($current -ne $Origin) { [YellowBox.Client.UIElement] $parent = Get-ParentUIElement $current [string] $pathPart = "" while ($true) # not a loop, just a way to prevent excessive nesting { $controlType = $current.ControlType; if ((Select-UIXPath -UIObject $parent -XPath $controlType) -eq $current) { # control type is sufficient identification $pathPart = $controlType break } [string] $filterExpression = "" $automationId = $current.AutomationId if ($automationId -ne $null -and $automationId -ne "") { $filterExpression += "@AutomationId = '$automationId'" if ((Select-UIXPath -UIObject $parent -XPath "$controlType[$filterExpression]") -eq $current) { $pathPart = "$controlType[$filterExpression']" break } $filterExpression += " and " } $className = $current.ClassName if ($className -ne $null -and $className -ne "") { $filterExpression += "@ClassName = '$className'" if ((Select-UIXPath -UIObject $parent -XPath "$controlType[$filterExpression]") -eq $current) { $pathPart = "$controlType[$filterExpression']" break } $filterExpression += " and " } $name = $current.Name if ($name -ne $null -and $name -ne "") { $filterExpression += "@Name = '$name'" if ((Select-UIXPath -UIObject $parent -XPath "$controlType[$filterExpression]") -eq $current) { $pathPart = "$controlType[$filterExpression']" break } } $allMatches = Select-UIXPath -UIObject $parent -XPath "$controlType[$filterExpression]" [int] $index = 1 for(; $index -le $allMatches.Count; ++$index) { if ($allMatches[$index] -eq $current) { break } } if ($index -gt $allMatches.Count) { throw "unable to identify element via index expression" } $pathPart = "$controlType[$filterExpression][$index]" break } if ([string]::IsNullOrEmpty($path)) { $path = "$pathPart" } else { $path = "$pathPart/$path" } $current = $parent } return $path } function Set-LocationViaUIXPath( [parameter(Mandatory=$true)][string] $XPath, $UIObject) { $matches = Select-UIXPath -UIObject $UIObject -XPath $XPath switch ($matches.Count) { 0 { Write-Error "'$XPath' does not match any UI element" } 1 { Set-Location $matches[0] } default { $format = "{0,6} {1,20} {2,20} {3,20} {4,20}" Write-Host "multiple matches" Write-Host ($format -f "Choice", "RuntimeId", "ControlType", "Name", "ClassName") Write-Host ($format -f "------", "---------", "-----------", "----", "---------") for($i = 0; $i -lt $matches.Count; ++$i) { Write-Host ($format -f $i, (Abbreviate-Text $matches[$i].RuntimeId -20), (Abbreviate-Text $matches[$i].ControlType 20), (Abbreviate-Text $matches[$i].Name 20), (Abbreviate-Text $matches[$i].ClassName 20)) } $choice = Read-Host "Enter choice" Set-Location $matches[$choice] } } } # ==================================================================================================== # Pattern-related Functions function Get-UIPattern( [parameter(ValueFromPipeline=$true)] $UIElement, [object[]] $PatternId) { begin { if ($PatternId -eq $null) { $PatternId = [System.Enum]::GetValues([YellowBox.PatternId]) } else { $normalizedPatternIds = @() foreach ($patid in $PatternId) { if ($patid -is [YellowBox.PatternId]) { $normalizedPatternIds += $patid } elseif ($patid -is [string]) { $normalizedPatternIds += [System.Enum]::Parse([YellowBox.PatternId], $patid, $true) } else { throw "unexpected pattern identifier type" } } $PatternId = $normalizedPatternIds } } process { if ($UIElement -eq $null) { $e = Get-Item (Get-Location).ProviderPath if ($e.GetType().Name -ne "UIElement") { throw "This command only works with a path or a current location representing a UI element" } $UIElement = @($e) } if ($PatternId -eq $null) { $PatternId = [System.Enum]::GetValues([YellowBox.PatternId]) } foreach($e in $UIElement) { foreach($i in $PatternId) { $p = $e.GetPattern($i) if($p -ne $null) { $p } } } } } function Test-UIPattern( [parameter(ValueFromPipeline=$true)] $UIElement, [YellowBox.PatternId] $PatternId) { (Get-UIPattern -UIElement $UIElement -PatternId $PatternId) -ne $null } function Collapse-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement) { process { foreach ($e in $UIElement) { $expandCollapsePattern = Get-UIPattern -UIElement $e -PatternId ([YellowBox.PatternId]::ExpandCollapse) if ($expandCollapsePattern -eq $null) { Write-Error "UIElement does not support the 'ExpandCollapse' pattern." continue } $expandCollapsePattern.Collapse() } } } function Expand-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement) { process { foreach($e in $UIElement) { $expandCollapsePattern = Get-UIPattern -UIElement $e -PatternId ([YellowBox.PatternId]::ExpandCollapse) if ($expandCollapsePattern -eq $null) { Write-Error "UIElement does not support the 'ExpandCollapse' pattern." continue } $expandCollapsePattern.Expand() } } } function Invoke-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement) { process { foreach($e in $UIElement) { $invokePattern = Get-UIPattern -UIElement $e -PatternId ([YellowBox.PatternId]::Invoke) if ($invokePattern -eq $null) { Write-Error "UIElement does not support the 'Invoke' pattern." continue } $invokePattern.Invoke() } } } function Close-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement) { process { foreach($e in $UIElement) { $windowPattern = Get-UIPattern -UIElement $e -PatternId ([YellowBox.PatternId]::Window) if ($windowPattern -eq $null) { Write-Error "UIElement does not support the 'Window' pattern." continue } $windowPattern.Close() } } } function Minimize-UIElement([parameter(ValueFromPipeline=$true)] $UIElement) { process { foreach($e in $UIElement) { $windowPattern = Get-UIPattern -UIElement $e -PatternId ([YellowBox.PatternId]::Window) if ($windowPattern -eq $null) { Write-Error "UIElement does not support the 'Window' pattern." continue } $windowPattern.WindowVisualState = [YellowBox.WindowVisualState]::Minimized } } } function Get-UIElementValue( [parameter(ValueFromPipeline=$true)] $UIElement) { process { foreach($e in $UIElement) { $valuePattern = Get-UIPattern -UIElement $e -PatternId ([YellowBox.PatternId]::Value) if ($valuePattern -eq $null) { Write-Error "UIElement does not support the 'Value' pattern." continue } $valuePattern.Value } } } # ==================================================================================================== # Event-related Functions function Wait-UIEvent( $UIElement = (Get-Item UI:\), [YellowBox.EventId[]]$EventIds = $null, [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { $uiEventSourceId = "YellowBox.WaitForUIEvent.UIEventSourceId" $uiEventListener = New-Object YellowBox.Client.UIEventListener -ArgumentList $UIElement, $EventIds, $TreeScope Register-ObjectEvent -InputObject $uiEventListener -EventName UIEventOccurred -SourceIdentifier $uiEventSourceId $uiEventListener.StartListening() $Action.Invoke() $e = Wait-Event -SourceIdentifier $uiEventSourceId -Timeout $Timeout $retVal = $null if ($e -ne $null) { #Write-Host "PS: received event, $($e.GetType()), $($e.SourceEventArgs.GetType()), $($e.SourceEventArgs.UIElement.ClassName)" Remove-Event -EventIdentifier $e.EventIdentifier $retVal = $e.SourceEventArgs } $uiEventListener.StopListening() Unregister-Event -SourceIdentifier $uiEventSourceId $uiEventListener.Dispose() return $retVal } function Wait-WindowOpened( $UIElement = (Get-Item UI:\), [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { $event = Wait-UIEvent -UIElement $UIElement -EventIds ([YellowBox.EventId]::Window_WindowOpened) -TreeScope $TreeScope -Timeout $Timeout -Action $Action $retVal = $null if ($event -ne $null) { $retVal = $event.UIElement } return $retVal } function Wait-WindowClosed( $UIElement = (Get-Item UI:\), [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { Wait-UIEvent -UIElement $UIElement -EventIds ([YellowBox.EventId]::Window_WindowClosed) -TreeScope $TreeScope -Timeout $Timeout -Action $Action } function Wait-MenuOpened( $UIElement = (Get-Item UI:\), [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { $event = Wait-UIEvent -UIElement $UIElement -EventIds ([YellowBox.EventId]::MenuOpened) -TreeScope $TreeScope -Timeout $Timeout -Action $Action $retVal = $null if ($event -ne $null) { $retVal = $event.UIElement } return $retVal } function Wait-MenuClosed( $UIElement = (Get-Item UI:\), [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { Wait-UIEvent -UIElement $UIElement -EventIds ([YellowBox.EventId]::MenuClosed) -TreeScope $TreeScope -Timeout $Timeout -Action $Action } function Get-UIEvent( $UIElement = (Get-Item UI:\), [YellowBox.EventId[]]$EventIds = $null, [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree) { # The loop below could potentially be broken out of via # [console]::KeyAvailable # or # [console]::ReadKey("NoEcho,IncludeKeyDown") # However, this will only work in powershell.exe (a console application), but not powershell_ise.exe. # Therefore, we use our own HotkeyListener instead. $uiEventSourceId = "YellowBox.GetUIEvent.UIEventSourceId" $uiEventListener = New-Object YellowBox.Client.UIEventListener -ArgumentList $UIElement, $EventIds, $TreeScope Register-ObjectEvent -InputObject $uiEventListener -EventName UIEventOccurred -SourceIdentifier $uiEventSourceId $uiEventListener.StartListening() [UInt16] $cancelKey = Add-Hotkey "Enter" [bool] $cont = $true while($cont) { $event = Wait-Event switch($event.SourceIdentifier) { $uiEventSourceId { Remove-Event -EventIdentifier $event.EventIdentifier echo $event.SourceEventArgs } $Global:HotkeySourceId { Remove-Event -EventIdentifier $event.EventIdentifier $cont = $false } } } Remove-Hotkey $cancelKey $uiEventListener.StopListening() Unregister-Event -SourceIdentifier $uiEventSourceId $uiEventListener.Dispose() } function Wait-UIPropertyChanged( $UIElement = (Get-Item UI:\), [YellowBox.PropertyId[]] $PropertyIds = $null, [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { $eventSourceId = "YellowBox.Wait-UIPropertyChanged.EventSourceId" $eventListener = New-Object YellowBox.Client.UIPropertyChangedEventListener -ArgumentList $UIElement, $PropertyIds, $TreeScope Register-ObjectEvent -InputObject $eventListener -EventName UIEventOccurred -SourceIdentifier $eventSourceId $eventListener.StartListening() $Action.Invoke() $e = Wait-Event -SourceIdentifier $eventSourceId -Timeout $Timeout $retVal = $null if ($e -ne $null) { #Write-Host "PS: received event, $($e.GetType()), $($e.SourceEventArgs.GetType()), $($e.SourceEventArgs.UIElement.ClassName)" Remove-Event -EventIdentifier $e.EventIdentifier $retVal = $e.SourceEventArgs } $eventListener.StopListening() Unregister-Event -SourceIdentifier $eventSourceId $eventListener.Dispose() return $retVal } function Wait-UIValueChanged( $UIElement = (Get-Item UI:\), [YellowBox.TreeScope]$TreeScope = [YellowBox.TreeScope]::Subtree, [int] $Timeout = 10, [scriptblock] $Action) { $eventArg = Wait-UIPropertyChanged -UIElement $UIElement -PropertyIds ([YellowBox.PropertyId]::ValueValue) -TreeScope $TreeScope -Timeout $Timeout -Action $Action return $eventArg } # ==================================================================================================== # Hotkey functions [YellowBox.HotkeyListener] $Global:HotkeyListener [string] $Global:HotkeySourceId = "YellowBox.HotkeySourceId" $Global:HotkeyActions = @{} <# .SYNOPSIS Executes the action associated with a hotkey. .PARAMETER HotkeyId Hotkey identifier. #> function Execute-HotkeyAction($HotkeyId) { $Global:HotkeyActions[$HotkeyId].Invoke() } <# .SYNOPSIS Registers an action to be executed upon a hotkey press. .PARAMETER Hotkey Hotkey specification of the form "<modkey>{'+'<modkey>}'+'<key>". <modkey> can be "Alt", "Control" (alternatively "Ctrl") and "Shift"; <key> is the string representation of one of the System.Windows.Forms.Keys enumeration. .PARAMETER Action Script block to be executed when the hotkey is pressed. .OUTPUTS System.UInt16. Hotkey identifier. #> function Add-Hotkey([string] $Hotkey, [scriptblock] $Action) { [UInt16] $m, [Windows.Forms.Keys]$k = Parse-Hotkey $Hotkey if ($Global:HotkeyListener -eq $null) { $Global:HotkeyListener = New-Object YellowBox.HotkeyListener if ($Action -ne $null) { Register-ObjectEvent -InputObject $Global:HotkeyListener -EventName KeyPressed -Action { param($hkl, $kea) ; Execute-HotkeyAction $kea.Id } -SourceIdentifier $Global:HotkeySourceId | Out-Null } else { Register-ObjectEvent -InputObject $Global:HotkeyListener -EventName KeyPressed -SourceIdentifier $Global:HotkeySourceId | Out-Null } } else { $Global:HotkeyListener.StopListening() } [uint16] $id = $Global:HotkeyListener.AddHotkey($m, $k) $Global:HotkeyActions[$id] = $Action $Global:HotkeyListener.StartListening() return $id } <# .SYNOPSIS Removes the specified hotkey. .PARAMETER Id Identifier of the hotkey to be removed. The identifier was returned by the corresponding Add-Hotkey call. Hotkey identifiers can also be obtained by listing hotkeys via the Get-Hotkey command. #> function Remove-Hotkey([uint16]$Id) { if ($Global:HotkeyListener -ne $null) { if ($Global:HotkeyListener.RemoveHotkey($Id)) { if ($Global:HotkeyListener.Hotkeys.Count -eq 0) { Unregister-Event -SourceIdentifier $Global:HotkeySourceId $Global:HotkeyListener.Dispose() $Global:HotkeyListener = $null } } } } <# .SYNOPSIS Retrieves hotkeys previously registered via Add-Hotkey. .PARAMETER Id Identifier of the hotkey to retrieve. If unspecified, all registered hotkeys are being retrieved. .OUTPUTS Registered hotkeys. #> function Get-Hotkey([UInt16] $Id = 0) { if ($Global:HotkeyListener -ne $null) { foreach($h in $Global:HotkeyListener.Hotkeys) { if (($Id -eq 0) -or ($h.Id -eq $Id)) { echo $h } } } } <# .SYNOPSIS Parses a hotkey string specification into a modifier and a key value. .PARAMETER Hotkey Hotkey string specification. .OUTPUTS Pair of 1) a System.UInt16 modifier keys value and 2) a System.Windows.Forms.Keys value. #> function Parse-Hotkey([string] $Hotkey) { [UInt16] $m = 0 [Windows.Forms.Keys] $k = [Windows.Forms.Keys]::None foreach ($p in $Hotkey.Split("+", ([StringSplitOptions]::RemoveEmptyEntries))) { switch ($p) { "Alt" { $m = $m -bor 0x0001 } "Control" { $m = $m -bor 0x0002 } "Ctrl" { $m = $m -bor 0x0002 } "Shift" { $m = $m -bor 0x0004 } default { $k = [Windows.Forms.Keys][Enum]::Parse([Windows.Forms.Keys], $p) } } } return $m, $k } # ==================================================================================================== # Mouse functions function Get-MousePosition([System.UInt32] $SamplingFrequency = 10) { [UInt16] $cancelKey = Add-Hotkey "Enter" $mousePositionSourceId = "YellowBox.GetMousePosition.MousePositionSourceId" $mousePositionListener = New-Object YellowBox.MousePositionListener -ArgumentList $SamplingFrequency Register-ObjectEvent -InputObject $mousePositionListener -EventName MousePositionChanged -SourceIdentifier $mousePositionSourceId $mousePositionListener.StartListening() [bool] $cont = $true # echo ([Windows.Forms.Cursor]::Position) while($cont) { ($event = Wait-Event) | Out-Null # Write-Host "EV: $($event.GetType()), $($event.SourceIdentifier), $($event.SourceEventArgs.GetType())" switch($event.SourceIdentifier) { $mousePositionSourceId { Remove-Event -EventIdentifier $event.EventIdentifier echo $event.SourceEventArgs.Position } $Global:HotkeySourceId { if ($event.SourceEventArgs.Id -eq $cancelKey) { Remove-Event -EventIdentifier $event.EventIdentifier $cont = $false } } } } $mousePositionListener.StopListening() Unregister-Event -SourceIdentifier $mousePositionSourceId $mousePositionListener.Dispose() Remove-Hotkey $cancelKey } <# .SYNOPSIS Continuously gets UI elements according to mouse position and key presses. .DESCRIPTION This command loops until interrupted by the user pressing the Enter key. Each loop iteration can determine a current UI element which - if different from the previous UI element - is written to the pipeline. .PARAMETER StartPath String. Path to the initial UI element. If not specified, but -FromMouse is active, the initial UI element is set to the UI element at the mouse position. If neither -StartPath nor -FromMouse are specified, the initial UI element is specified by the current path. If, in that case, the current path does not specify a UI element, an error occurs. .PARAMETER FromMouse Switch. Indicates that the current UI element is obtained from the mouse position. .PARAMETER AdjustWithKeys Switch. Indicates that the current UI element can be adjusted with key strokes. The following key strokes result in the current UI element being set to the corresponding relative of the previous UI element: Cursor Up: Parent. Cursor Down: First child. Cursor Right: Next sibling. Cursor Left: Previous sibling. .PARAMETER MouseSamplingFrequency System.UInt32. Frequency, in Hertz, with which the mouse position is being checked. Ignored unless -FromMouse is specified. .OUTPUTS YellowBox.Client.UIElement. Current UI elements, written to pipeline. #> function Get-UIElement( [string] $StartPath = "", [switch] $FromMouse = $true, [switch] $AdjustWithKeys = $true, [System.UInt32] $MouseSamplingFrequency = 10) { [YellowBox.Client.UIElement] $currentUIElement = $null [YellowBox.Client.UIElement] $newUIElement = $null if ($StartPath -eq "" -and !$FromMouse) { $currentUIElement = Get-Item (Get-Location).ProviderPath } elseif ($StartPath -ne "") # StartPath trumps mouse position { $currentUIElement = Get-Item $StartPath } elseif ($FromMouse) { $currentUIElement = Get-UIElementFromPoint ([Windows.Forms.Cursor]::Position) } if ($currentUIElement -eq $null) { throw "Failed to determine a start UI element." } if ($currentUIElement.GetType().Name -ne "UIElement") { throw "This command only works with a path or a current location representing a UI element." } [UInt16] $cancelKey = Add-Hotkey "Enter" [UInt16] $parentKey = 0 [UInt16] $previousSiblingKey = 0 [UInt16] $nextSiblingKey = 0 [UInt16] $firstChildKey = 0 if ($AdjustWithKeys) { $parentKey = Add-Hotkey "Up" $previousSiblingKey = Add-Hotkey "Left" $nextSiblingKey = Add-Hotkey "Right" $firstChildKey = Add-Hotkey "Down" } $mousePositionSourceId = "YellowBox.GetUIElementFromMousePosition.MousePositionSourceId" [YellowBox.MousePositionListener] $mousePositionListener = $null if ($FromMouse) { $mousePositionListener = New-Object YellowBox.MousePositionListener -ArgumentList $MouseSamplingFrequency Register-ObjectEvent -InputObject $mousePositionListener -EventName MousePositionChanged -SourceIdentifier $mousePositionSourceId $mousePositionListener.StartListening() } [bool] $cont = $true [bool] $firstIteration = $true while ($cont) { # 1. First iteration, current element, but no new element. We want to echo the current element. # 2. We have a new element, but it's the same as the current element. We want to skip echoing the new element (we could assign new to current, # though it would be pointless). # 3. We have a new element, and it's different from the current element. We want to echo the new element and assign it to the current element. # 4. We're on a subsequent (not the first) iteration, but the new element is null (i.e. the operation to obtain a new element resulted in no # element). We want to skip echoing the new element. if ($firstIteration) { echo $currentUIElement } else { if ($newUIElement -ne $null) { if ($newUIElement -ne $currentUIElement) { $currentUIElement = $newUIElement echo $currentUIElement } } } $event = Wait-Event switch ($event.SourceIdentifier) { $mousePositionSourceId { Remove-Event -EventIdentifier $event.EventIdentifier $newUIElement = Get-UIElementFromPoint $event.SourceEventArgs.Position } $Global:HotkeySourceId { Remove-Event -EventIdentifier $event.EventIdentifier switch($event.SourceEventArgs.Id) { $cancelKey { $cont = $false } $parentKey { $newUIElement = Get-ParentUIElement $currentUIElement } $previousSiblingKey { $newUIElement = Get-PreviousSiblingUIElement $currentUIElement } $nextSiblingKey { $newUIElement = Get-NextSiblingUIElement $currentUIElement } $firstChildKey { $newUIElement = Get-FirstChildUIElement $currentUIElement } } } } $firstIteration = $false } Remove-Hotkey $cancelKey if ($AdjustWithKeys) { Remove-Hotkey $parentKey Remove-Hotkey $previousSiblingKey Remove-Hotkey $nextSiblingKey Remove-Hotkey $firstChildKey } if ($FromMouse) { $mousePositionListener.StopListening() Unregister-Event -SourceIdentifier $mousePositionSourceId $mousePositionListener.Dispose() } } # ==================================================================================================== # Prompt Functions [YellowBox.Highlight] $promptHighlight = $null [scriptblock] $originalPrompt = $null function Set-HighlightPrompt { $script:originalPrompt = (Get-Command Prompt).ScriptBlock $Function:prompt = { Highlighting-Prompt } } function Highlighting-Prompt() { $currentLocation = Get-Location if ($currentLocation.Provider.Name -eq "UIA") { if ($script:promptHighlight -eq $null) { $script:promptHighlight = New-Object YellowBox.Highlight ([System.Drawing.Color]::LightGreen) } [YellowBox.Client.UIElement] $currentUIElement = Get-Item $currentLocation $script:promptHighlight.SetBounds($currentUIElement.BoundingRectangle) } return $script:originalPrompt.Invoke() } <# .SYNOPSIS Sets the location via keys. #> function Set-LocationViaKeys() { $e = $null Get-UIElement -FromMouse:$false -AdjustWithKeys | Highlight-UIElement -PassThru | ScreenTag-UIElement -PassThru | %{ $e = $_ } Set-Location $e } function Set-LocationViaMouseAndKeys() { $e = $null Get-UIElement -FromMouse -AdjustWithKeys | Highlight-UIElement -PassThru | ScreenTag-UIElement -PassThru | %{ $e = $_ } Set-Location $e } # ==================================================================================================== # Speach-related Functions function Speak-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement, [string]$Format = '$Name $ControlType') { process { foreach($e in $UIElement) { $text = Get-UIElementText $e $Format Speak-Text $text } } } # ==================================================================================================== # Tree-related Functions function Get-Tree($Item, [string] $Format = '$ControlType "$Name"') { function Label($Item) { if($Item -is [YellowBox.Client.UIElement]) { Get-UIElementText $Item $Format } else { $Item.Name } } # homogenize the 'item' arg if($Item -eq $null) { $Item = Get-Item . } elseif ($Item -is [string]) { $Item = Get-Item $Item } [Collections.Stack] $stack = New-Object System.Collections.Stack $stack.Push(@{Item = $Item; IndentFormat = '0'; IsLastChild = $false}) while($stack.Count -gt 0) { $current = $stack.Pop() if($current.IndentFormat -eq '0') { $indent = '' $connector = '' $childIndentFormat = '' } else { $indent = $current.IndentFormat.Replace("!", "│ ").Replace(".", " ") $connector = if ($Current.IsLastChild) { '└───' } else { '├───' } $childIndentSuffix = if ($current.IsLastChild) { "." } else { "!" } $childIndentFormat = $current.IndentFormat + $childIndentSuffix } Write-Output "$indent$connector$(Label $current.Item)" $children = Select-UIXPath -UIObject $current.Item -XPath "*" if ($children -eq $null) { continue } elseif ($children -is [array] -and $children.Length -gt 0) { # ensure the last child gets popped off the stack last [array]::Reverse($children); for([int] $i = 0; $i -lt $children.Length; ++$i) { $stack.Push(@{ Item = $children[$i]; IndentFormat = $childIndentFormat; IsLastChild = ($i -eq 0)}) } } elseif ($children -is [YellowBox.Client.UIElement]) { $stack.Push(@{ Item = $children; IndentFormat = $childIndentFormat; IsLastChild = $true}) } else { throw "unexpected return from Select-UIXPath" } } } # ==================================================================================================== # Miscellaneous Functions function GetAndHighlight-ChildItem($path) { Get-ChildItem $path | Highlight-AllUIElements -Color ([System.Drawing.Color]::LightCyan) -PassThru } function Label-Position( [parameter(ValueFromPipeline=$true)] $position) { process { foreach($p in $position) { [PSCustomObject]@{Position=new-object System.Drawing.Point $_.X,($_.Y - 20); Text="$($_.X), $($_.Y)"} } } } $YellowBoxMacroPattern = New-Object System.Text.RegularExpressions.Regex "\`$(?<MacroName>\w+)" function Get-UIElementText($UIElement, [string]$Format) { $text = $YellowBoxMacroPattern.Replace($Format, { param($m) switch($m.Groups["MacroName"].Value) { "AutomationId" { $UIElement.AutomationId } "Name" { $UIElement.Name } "ClassName" { $UIElement.ClassName } "ControlType" { $UIElement.ControlType } "LocalizedControlType" { $UIElement.LocalizedControlType } "RuntimeId" { $UIElement.RuntimeId } default { $m.Value } } }) if (Test-UIPattern $UIElement "Value") { $value = (Get-UIPattern $UIElement "Value").Value if ($value -match "^\((\d+(?:\.\d+)?)\)$") { $value = "-" + $Matches[1]} $text += " with value " + $value } return $text } # ==================================================================================================== # Highlight Functions function Highlight-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement, [switch]$PassThru) { begin { $recordCount = 0 $hl = New-Object YellowBox.Highlight } process { foreach($e in $UIElement) { $hl.SetBounds($e.BoundingRectangle) if($PassThru){ $e } ++$recordCount } } end { if($recordCount -eq 1) { Start-Sleep -Seconds 3 } $hl.Dispose() } } function Highlight-AllUIElements( [parameter(ValueFromPipeline=$true)] $UIElement, [System.Drawing.Color] $Color = ([System.Drawing.Color]::Yellow), [int] $Duration = 3, [switch] $PassThru) { begin { $hls = @() } process { foreach($e in $uiElement) { $hl = New-Object YellowBox.Highlight $Color $hls += $hl $hl.SetBounds($e.BoundingRectangle) if ($PassThru) { $e } } } end { Start-Sleep -Seconds $Duration foreach($hl in $hls) { $hl.Dispose() } } } function ScreenTag-UIElement( [parameter(ValueFromPipeline=$true)] $UIElement, [string]$Format = '"$Name" $ControlType', [switch]$PassThru) { begin { $recordCount = 0 $st = New-Object YellowBox.ScreenTag } process { foreach($e in $UIElement) { $br = $e.BoundingRectangle $pos = New-Object -TypeName System.Drawing.Point -ArgumentList ($br.X - 6),($br.Y - 23) $text = Get-UIElementText $e $Format $st.SetPositionAndText($pos, $text) if($PassThru) { $e } ++$recordCount } } end { if($recordCount -eq 1) { Start-Sleep -Seconds 3 } $st.Dispose() } } # ==================================================================================================== # Aliases Set-Alias tree Get-Tree Set-Alias cdk Set-LocationViaKeys Set-Alias cdm Set-LocationViaMouseAndKeys Set-Alias cdx Set-LocationViaUIXPath Set-Alias slx Select-UIXPath Set-Alias dirh GetAndHighlight-ChildItem Set-Alias lsh GetAndHighlight-ChildItem |