Jump to content

Using UI Automation Code in AutoIt


LarsJ
 Share

Recommended Posts

  • 5 months later...

Hello.
I want to automate the application "PM&FM Tool dla kas FAREX" (https://www.edatapolska.pl/fm/upload/programy/KASY-ONLINE/FM_and_PM_Tool.zip)
I need to read text from the "Status bar" window

screen1.jpg.82e589ba6658e6792aaaa9d90aaa26bb.jpg

screen2.thumb.jpg.e700ae9a5c49ffd6652458cf67d40f9f.jpg

I am using the function GetCurrentPropertyValue but as a result is Null 😪

ONLY this item is Null, other elements correctly (Window, StatusBar -look code and comments and return console SciTe)

All code below

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
#AutoIt3Wrapper_UseX64=y ; If target application is running as 64 bit code
#include "UIA_Constants.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_Functions.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_SafeArray.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_Variant.au3" ; Can be copied from UIASpy Includes folder

Opt( "MustDeclareVars", 1 )

Example()

Func Example()
    ; Create UI Automation object
    Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $dtag_IUIAutomation )
    If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
    ConsoleWrite( "$oUIAutomation OK" & @CRLF )

    ; Get Desktop element
    Local $pDesktop, $oDesktop
    $oUIAutomation.GetRootElement( $pDesktop )
    $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oDesktop ) Then Return ConsoleWrite( "$oDesktop ERR" & @CRLF )
    ConsoleWrite( "$oDesktop OK" & @CRLF )

    ; --- Find window/control ---
    ConsoleWrite( "--- Find window/control ---" & @CRLF )
    Local $pCondition0
    $oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "WindowsForms10.Window.8.app.0.378734a", $pCondition0 )
    If Not $pCondition0 Then Return ConsoleWrite( "$pCondition0 ERR" & @CRLF )
    ConsoleWrite( "$pCondition0 OK" & @CRLF )

    Local $pWindow1, $oWindow1
    $oDesktop.FindFirst( $TreeScope_Children, $pCondition0, $pWindow1 )
    $oWindow1 = ObjCreateInterface( $pWindow1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oWindow1 ) Then Return ConsoleWrite( "$oWindow1 ERR" & @CRLF )
    ConsoleWrite( "$oWindow1 OK" & @CRLF )

    ; --- Find window/control ---
    ConsoleWrite( "--- Find window/control ---" & @CRLF )
    Local $pCondition1
    $oUIAutomation.CreatePropertyCondition( $UIA_AutomationIdPropertyId, "m_status_strip", $pCondition1 )
    If Not $pCondition1 Then Return ConsoleWrite( "$pCondition1 ERR" & @CRLF )
    ConsoleWrite( "$pCondition1 OK" & @CRLF )

    Local $pStatusBar1, $oStatusBar1
    $oWindow1.FindFirst( $TreeScope_Descendants, $pCondition1, $pStatusBar1 )
    $oStatusBar1 = ObjCreateInterface( $pStatusBar1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oStatusBar1 ) Then Return ConsoleWrite( "$oStatusBar1 ERR" & @CRLF )
    ConsoleWrite( "$oStatusBar1 OK" & @CRLF )

    ; --- Find window/control ---
    ConsoleWrite( "--- Find window/control ---" & @CRLF )
    Local $pCondition2
    $oUIAutomation.CreatePropertyCondition( $UIA_ControlTypePropertyId, $UIA_TextControlTypeId, $pCondition2 )
    If Not $pCondition2 Then Return ConsoleWrite( "$pCondition2 ERR" & @CRLF )
    ConsoleWrite( "$pCondition2 OK" & @CRLF )

    Local $pText1, $oText1
    $oWindow1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 )
    $oText1 = ObjCreateInterface( $pText1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oText1 ) Then Return ConsoleWrite( "$oText1 ERR" & @CRLF )
    ConsoleWrite( "$oText1 OK" & @CRLF )

    ConsoleWrite( "--- Element Properties ---" & @CRLF )
    Local $sWG
    $oWindow1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sWG )  ;its works
    ConsoleWrite( "NamePropertyId Window = " & $sWG & @CRLF )

    Local $sSB
    $oStatusBar1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sSB )   ;its works
    ConsoleWrite( "NamePropertyId StatusBar = " & $sSB & @CRLF )

    Local $sName1
    $oText1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName1 ) ; NOT WORK return empty string !!!
    ConsoleWrite( "NamePropertyId Text = " & $sName1 & @CRLF )
EndFunc

SciTe console

>"C:\Users\viszn\OneDrive\AutoIt3\SciTE\..\AutoIt3.exe" "C:\Users\viszn\OneDrive\AutoIt3\SciTE\AutoIt3Wrapper\AutoIt3Wrapper.au3" /run /prod /ErrorStdOut /in "C:\Users\viszn\OneDrive\_PROJEKTY\AutoIT\UIA\UIASpy_2020-10-15\smietnik.au3" /UserParams    
+>15:48:36 Starting AutoIt3Wrapper (21.316.1639.1) from:SciTE.exe (4.4.6.0)  Keyboard:00000415  OS:WIN_10/2009  CPU:X64 OS:X64  Environment(Language:0415)  CodePage: 65001  utf8.auto.check:4
+>         SciTEDir => C:\Users\viszn\OneDrive\AutoIt3\SciTE   UserDir => C:\Users\viszn\AppData\Local\AutoIt v3\SciTE\AutoIt3Wrapper   SCITE_USERHOME => C:\Users\viszn\AppData\Local\AutoIt v3\SciTE 
>Running AU3Check (3.3.14.5)  params:-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7  from:C:\Users\viszn\OneDrive\AutoIt3  input:C:\Users\viszn\OneDrive\_PROJEKTY\AutoIT\UIA\UIASpy_2020-10-15\smietnik.au3
+>15:48:36 AU3Check ended.rc:0
>Running:(3.3.14.5):C:\Users\viszn\OneDrive\AutoIt3\autoit3_x64.exe "C:\Users\viszn\OneDrive\_PROJEKTY\AutoIT\UIA\UIASpy_2020-10-15\smietnik.au3"    
+>Setting Hotkeys...--> Press Ctrl+Alt+Break to Restart or Ctrl+BREAK to Stop.
$oUIAutomation OK
$oDesktop OK
--- Find window/control ---
$pCondition0 OK
$oWindow1 OK
--- Find window/control ---
$pCondition1 OK
$oStatusBar1 OK
--- Find window/control ---
$pCondition2 OK
$oText1 OK
--- Element Properties ---
NamePropertyId Window = PRO FM  PM Reader Tool ~ Edata Polska 01.07
NamePropertyId StatusBar = statusStrip1
NamePropertyId Text = 
+>15:48:36 AutoIt3.exe ended.rc:0
+>15:48:37 AutoIt3Wrapper Finished.
>Exit code: 0    Time: 1.436

How do I get text: "Paragon: 2/55   Zakres: 1-55   Błędy Transmisji Liczyć: 0"? I need this to know what the current preview document is.

I attach UIASpy screenshots of all elements of the application, maybe there is another way to read this text?

screen3.thumb.jpg.7e4dbe9796b6cff3cac9db136741067a.jpg

screen4.thumb.jpg.0f8895fd43d80bfddd2e67d5ae24e44e.jpg

screen5.thumb.jpg.9c774a5e3cd792c14d3f8d0d1f66e577.jpg

 

Thank you in advance for your help

Link to comment
Share on other sites

As you can see the value is in the name property. Try to run as admin or try simplespy generated code snippet to see how that behaves. The good news as spy is seeing the text that you can use uia library.

       local $tName= _UIA_getPropertyValue($oUIElement, $UIA_NamePropertyId)

 

 

 

Link to comment
Share on other sites

In the code snippet here near bottom of your code:

Local $pText1, $oText1
$oWindow1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 )
$oText1 = ObjCreateInterface( $pText1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
If Not IsObj( $oText1 ) Then Return ConsoleWrite( "$oText1 ERR" & @CRLF )
ConsoleWrite( "$oText1 OK" & @CRLF )

I just think you should replace $oWindow1 with $oStatusBar1 this way:

Local $pText1, $oText1
$oStatusBar1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 )
$oText1 = ObjCreateInterface( $pText1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
If Not IsObj( $oText1 ) Then Return ConsoleWrite( "$oText1 ERR" & @CRLF )
ConsoleWrite( "$oText1 OK" & @CRLF )

 

Link to comment
Share on other sites

Hello, I am so sorry that I am writing back so late, but I had a business trip.
First of all, thank you very much, junkew and LarsJ solutions work 😀

$oUIAutomation OK
$oDesktop OK
--- Find window/control ---
$pCondition0 OK
$oWindow1 OK
--- Find window/control ---
$pCondition1 OK
$oStatusBar1 OK
--- Find window/control ---
$pCondition2 OK
$oText1 OK
--- Element Properties ---
NamePropertyId Window = PRO FM  PM Reader Tool ~ Edata Polska 01.07
NamePropertyId StatusBar = statusStrip1
NamePropertyId Text = Paragon: 2/55   Zakres: 1-55   B³êdy Transmisji Liczyæ: 0
Name2PropertyId Text = Paragon: 2/55   Zakres: 1-55   B³êdy Transmisji Liczyæ: 0
_UIA_getPropertyValue($oText1, $UIA_NamePropertyId) = Paragon: 2/55   Zakres: 1-55   B³êdy Transmisji Liczyæ: 0

The cause was a bad item reference

;~  $oWindow1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 )    ; <====== wrong solution
    $oStatusBar1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 ) ; <====== its works

I am a beginner in UIA and I will probably make many mistakes.

Below is the complete working code to analyze for others with the problem

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
#AutoIt3Wrapper_UseX64=y ; If target application is running as 64 bit code
#include "UIA_Constants.au3" ; Can be copied from UIASpy Includes folder
#include "UIA_Functions.au3" ; Can be copied from UIASpy Includes folder
#include "UIA_SafeArray.au3" ; Can be copied from UIASpy Includes folder
#include "UIA_Variant.au3" ; Can be copied from UIASpy Includes folder

Opt( "MustDeclareVars", 1 )

Example()

Func Example()
    ; Create UI Automation object
    Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $dtag_IUIAutomation )
    If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
    ConsoleWrite( "$oUIAutomation OK" & @CRLF )

    ; Get Desktop element
    Local $pDesktop, $oDesktop
    $oUIAutomation.GetRootElement( $pDesktop )
    $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oDesktop ) Then Return ConsoleWrite( "$oDesktop ERR" & @CRLF )
    ConsoleWrite( "$oDesktop OK" & @CRLF )

    ; --- Find window/control ---
    ConsoleWrite( "--- Find window/control ---" & @CRLF )
    Local $pCondition0
    $oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "WindowsForms10.Window.8.app.0.378734a", $pCondition0 )
    If Not $pCondition0 Then Return ConsoleWrite( "$pCondition0 ERR" & @CRLF )
    ConsoleWrite( "$pCondition0 OK" & @CRLF )

    Local $pWindow1, $oWindow1
    $oDesktop.FindFirst( $TreeScope_Children, $pCondition0, $pWindow1 )
    $oWindow1 = ObjCreateInterface( $pWindow1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oWindow1 ) Then Return ConsoleWrite( "$oWindow1 ERR" & @CRLF )
    ConsoleWrite( "$oWindow1 OK" & @CRLF )

    ; --- Find window/control ---
    ConsoleWrite( "--- Find window/control ---" & @CRLF )
    Local $pCondition1
    $oUIAutomation.CreatePropertyCondition( $UIA_AutomationIdPropertyId, "m_status_strip", $pCondition1 )
    If Not $pCondition1 Then Return ConsoleWrite( "$pCondition1 ERR" & @CRLF )
    ConsoleWrite( "$pCondition1 OK" & @CRLF )

    Local $pStatusBar1, $oStatusBar1
    $oWindow1.FindFirst( $TreeScope_Descendants, $pCondition1, $pStatusBar1 )
    $oStatusBar1 = ObjCreateInterface( $pStatusBar1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oStatusBar1 ) Then Return ConsoleWrite( "$oStatusBar1 ERR" & @CRLF )
    ConsoleWrite( "$oStatusBar1 OK" & @CRLF )

    ; --- Find window/control ---
    ConsoleWrite( "--- Find window/control ---" & @CRLF )
    Local $pCondition2
    $oUIAutomation.CreatePropertyCondition( $UIA_ControlTypePropertyId, $UIA_TextControlTypeId, $pCondition2 )
    If Not $pCondition2 Then Return ConsoleWrite( "$pCondition2 ERR" & @CRLF )
    ConsoleWrite( "$pCondition2 OK" & @CRLF )

    Local $pText1, $oText1
;~  $oWindow1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 )    ; <====== wrong solution
    $oStatusBar1.FindFirst( $TreeScope_Descendants, $pCondition2, $pText1 ) ; <====== its works
    $oText1 = ObjCreateInterface( $pText1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
    If Not IsObj( $oText1 ) Then Return ConsoleWrite( "$oText1 ERR" & @CRLF )
    ConsoleWrite( "$oText1 OK" & @CRLF )

    ConsoleWrite( "--- Element Properties ---" & @CRLF )
    Local $sWG
    $oWindow1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sWG )  ;its works
    ConsoleWrite( "NamePropertyId Window = " & $sWG & @CRLF )

    Local $sSB
    $oStatusBar1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sSB )   ;its works
    ConsoleWrite( "NamePropertyId StatusBar = " & $sSB & @CRLF )

    Local $sName1
    $oText1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName1 ) ; first method
    ConsoleWrite( "NamePropertyId Text = " & $sName1 & @CRLF )

    Local $sName2
    $oText1.GetCurrentPropertyValue( $UIA_LegacyIAccessibleNamePropertyId, $sName2 )    ; friend method
    ConsoleWrite( "Name2PropertyId Text = " & $sName2 & @CRLF )

    local $tName= _UIA_getPropertyValue($oText1, $UIA_NamePropertyId)   ;   third method
    ConsoleWrite( "_UIA_getPropertyValue($oText1, $UIA_NamePropertyId) = " & $tName & @CRLF)
EndFunc


Func _UIA_GetPropertyValue($oElement, $iPropertyID)
    Local $vRetVal
    If Not _UIA_IsElement($oElement) Then
        Return SetError(1, 0, 0)
    EndIf
    $oElement.GetCurrentPropertyValue($iPropertyID, $vRetVal)
    Return $vRetVal
EndFunc   ;==>_UIA_GetPropertyValue

Func _UIA_IsElement($control)
    Return IsObj($control) ; derp, TODO: check name?
EndFunc   ;==>_UIA_IsElement

Once again, THANK YOU VERY MUCH - it will make my work a lot easier

Best regards

Link to comment
Share on other sites

  • 2 months later...

Hi, 

I am starting to learn uiautomation and got something working.

But.. now I am having trouble running my code on different scenario's / setups.

I have a window that keeps changing the classname on different machines. (MainWindows_QMLTYPE_426 changing numbers)

Local $pCondition
$oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "MainWindow_QMLTYPE_426", $pCondition )
If Not $pCondition Then Return ConsoleWrite( "$pCondition ERR" & @CRLF )
ConsoleWrite( "$pCondition OK" & @CRLF )

I read somewhere uiautomation does not support wildcards!?, is there a workaround to get the full classnameproperty based on wildcard something like: "MainWindows_QMLTYPE_*"  ???

Thank you.

Edited by MightyWeird
Link to comment
Share on other sites

You have to iterate the objects yourself and then filter yourself (thats partly what I did with UIAWrappers UDF)
You could check if the controltype stays the same or your automationid property

Use 

  • findall
  • treewalkers

see the examples in below links but you can search many in the forum

a

b

 

 

 

Link to comment
Share on other sites

  • 2 weeks later...

Hi,

I want to close a popup by pressing the "X" on the top right of the window popup.

I managed get my script to "click" the button (the x button  is now surrounded by a square box (Bounding rectangle?) , but it does not actual click the button and closing the pop-up.

I tried to use   "UIA_MouseClick "  (see example 16), but when I run the example script it jumps to "UIA_Constants" with a lot of "errors: $UIA_InvokePatternId previously declared as a 'Const'." errors....

I am confused....

Anyone got any hints?

Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $dtagIUIAutomation )

    If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
    ConsoleWrite( "$oUIAutomation OK" & @CRLF )

  ; Get Desktop element
  Local $pDesktop, $oDesktop
  $oUIAutomation.GetRootElement( $pDesktop )
  $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )

  If Not IsObj( $oDesktop ) Then Return ConsoleWrite( "$oDesktop ERR" & @CRLF )
  ConsoleWrite( "$oDesktop OK" & @CRLF )

; --- Find window/control ---

ConsoleWrite( "--- Find window/control ---" & @CRLF )

Local $pCondition
$oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "MainWindow_QMLTYPE_426", $pCondition )
If Not $pCondition Then Return ConsoleWrite( "$pCondition ERR" & @CRLF )
ConsoleWrite( "$pCondition OK" & @CRLF )

Local $pMBAMmain, $oMBAMmain
$oDesktop.FindFirst( $TreeScope_Children, $pCondition, $pMBAMmain )
$oMBAMmain = ObjCreateInterface( $pMBAMmain, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
If Not IsObj( $oMBAMmain ) Then Return ConsoleWrite( "$oMBAMmain ERR" & @CRLF )
ConsoleWrite( "$oMBAMmain OK" & @CRLF )


#########################################################################################################

Local $pCondition1
$oUIAutomation.CreatePropertyCondition( $UIA_AutomationIdPropertyId, "Dashboard.scannerCard.buttonScan", $pCondition1 )
If Not $pCondition1 Then Return ConsoleWrite( "$pCondition1 ERR" & @CRLF )
ConsoleWrite( "$pCondition1 OK" & @CRLF )

Local $pCondition2
$oUIAutomation.CreatePropertyCondition( $UIA_NamePropertyId, "Scannen", $pCondition2 )
If Not $pCondition2 Then Return ConsoleWrite( "$pCondition2 ERR" & @CRLF )
ConsoleWrite( "$pCondition2 OK" & @CRLF )

  ; And condition
  $oUIAutomation.CreateAndCondition( $pCondition1, $pCondition2, $pCondition )
  If Not $pCondition Then Return ConsoleWrite( "$pCondition ERR" & @CRLF )
  ConsoleWrite( "$pCondition OK" & @CRLF )

###########################

Local $pscanbut, $oscanbut
$oMBAMmain.FindFirst( $TreeScope_Descendants, $pCondition, $pscanbut )
$oscanbut = ObjCreateInterface( $pscanbut, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
If Not IsObj( $oscanbut ) Then Return ConsoleWrite( "$oscanbut ERR" & @CRLF )
ConsoleWrite( "$oscanbut OK" & @CRLF )


#############################

  $oSaveAs.GetCurrentPattern( $UIA_InvokePatternId, $pInvoke )
  $oInvoke = ObjCreateInterface( $pInvoke, $sIID_IUIAutomationInvokePattern, $dtagIUIAutomationInvokePattern )
  If Not IsObj( $oInvoke ) Then Return ConsoleWrite( "$oInvoke ERR" & @CRLF )
  ConsoleWrite( "$oInvoke OK" & @CRLF )
  $oInvoke.Invoke()
  Sleep( 1000 )


Local $pInvoke, $oInvoke
$oscanbut.GetCurrentPattern( $UIA_InvokePatternId, $pInvoke )
$oInvoke = ObjCreateInterface( $pInvoke, $sIID_IUIAutomationInvokePattern, $dtagIUIAutomationInvokePattern )
If Not IsObj( $oInvoke ) Then Return ConsoleWrite( "$oInvoke ERR" & @CRLF )
ConsoleWrite( "$oInvoke OK" & @CRLF )
 $oInvoke.Invoke()

UIA_MouseClick ($oInvoke)
EndFunc

 

Edited by MightyWeird
format
Link to comment
Share on other sites

In general closing a popup I just would do with a winclose and no need for UIA functions.

Apparently you include files in such a way you get a 2nd definition of a constant $UI_InvokePatternID. You could just comment the 2nd definition (the line where it fails on)

 

 

 

Link to comment
Share on other sites

You might want to make sure all your includes files say #include-once

All my code provided is Public Domain... but it may not work. ;) Use it, change it, break it, whatever you want.

Spoiler

My Humble Contributions:
Personal Function Documentation - A personal HelpFile for your functions
Acro.au3 UDF - Automating Acrobat Pro
ToDo Finder - Find #ToDo: lines in your scripts
UI-SimpleWrappers UDF - Use UI Automation more Simply-er
KeePass UDF - Automate KeePass, a password manager
InputBoxes - Simple Input boxes for various variable types

Link to comment
Share on other sites

$PropertyConditionFlags_MatchSubstring

On 10/22/2021 at 12:38 PM, MightyWeird said:

I read somewhere uiautomation does not support wildcards!?, is there a workaround to get the full classnameproperty based on wildcard something like: "MainWindows_QMLTYPE_*"  ???

It's true that wildcards aren't supported. On the other hand, it's possible to search for substrings in string properties through the Windows 10 1809 update: 

; enum PropertyConditionFlags
Global Const $PropertyConditionFlags_None           = 0
Global Const $PropertyConditionFlags_IgnoreCase     = 1
Global Const $PropertyConditionFlags_MatchSubstring = 2 ; Windows 10-1809

To use the $PropertyConditionFlags_MatchSubstring constant, it's necessary to use a version of the UIA code that includes this Windows update. To do this, select the correct Windows Mode in UIASpy:

0lNwVt5.png

Now the code generated automatically by UIASpy will look like this: 

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

;#AutoIt3Wrapper_UseX64=n ; If target application is running as 32 bit code
;#AutoIt3Wrapper_UseX64=y ; If target application is running as 64 bit code

#include "UIA_Constants.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_Functions.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_SafeArray.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_Variant.au3" ; Can be copied from UIASpy Includes folder

Opt( "MustDeclareVars", 1 )

Example()

Func Example()
    ; Create UI Automation object
    Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation8, $sIID_IUIAutomation6, $dtag_IUIAutomation6 )
    If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
    ConsoleWrite( "$oUIAutomation OK" & @CRLF )

    ; Get Desktop element
    Local $pDesktop, $oDesktop
    $oUIAutomation.GetRootElement( $pDesktop )
    $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement9, $dtag_IUIAutomationElement9 )
    If Not IsObj( $oDesktop ) Then Return ConsoleWrite( "$oDesktop ERR" & @CRLF )
    ConsoleWrite( "$oDesktop OK" & @CRLF )
EndFunc

; --- Find window/control ---

ConsoleWrite( "--- Find window/control ---" & @CRLF )

Local $pCondition0
$oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "Notepad", $pCondition0 )
If Not $pCondition0 Then Return ConsoleWrite( "$pCondition0 ERR" & @CRLF )
ConsoleWrite( "$pCondition0 OK" & @CRLF )

Local $pWindow1, $oWindow1
$oDesktop.FindFirst( $TreeScope_Children, $pCondition0, $pWindow1 )
$oWindow1 = ObjCreateInterface( $pWindow1, $sIID_IUIAutomationElement9, $dtag_IUIAutomationElement9 )
If Not IsObj( $oWindow1 ) Then Return ConsoleWrite( "$oWindow1 ERR" & @CRLF )
ConsoleWrite( "$oWindow1 OK" & @CRLF )

; --- Element Properties ---

ConsoleWrite( "--- Element Properties ---" & @CRLF )

Local $sName1
$oWindow1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName1 )
ConsoleWrite( "$sName1 = " & $sName1 & @CRLF )

In UIASpy, you generate the above code as follows:

  • Sample code menu | Initial code | Complete code
  • Click Notepad window in treeview to switch to info listview
  • Right-click the $UIA_ClassNamePropertyId line | Create sample code
  • Sample code menu | Properties ... | Right-click the $UIA_NamePropertyId line | Create sample code
  • Right click code listview | Copy all items
  • Paste the code into your editor

Edit the code to make it runnable. And replace CreatePropertyCondition() with CreatePropertyConditionEx() to apply PropertyConditionFlags: 

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

;#AutoIt3Wrapper_UseX64=n ; If target application is running as 32 bit code
;#AutoIt3Wrapper_UseX64=y ; If target application is running as 64 bit code

#include "UIA_Constants.au3"  ; Can be copied from UIASpy Includes folder
;#include "UIA_Functions.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_SafeArray.au3" ; Can be copied from UIASpy Includes folder
;#include "UIA_Variant.au3"   ; Can be copied from UIASpy Includes folder

Opt( "MustDeclareVars", 1 )

Example()

Func Example()
  ; Create UI Automation object
  Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation8, $sIID_IUIAutomation6, $dtag_IUIAutomation6 )
  If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
  ConsoleWrite( "$oUIAutomation OK" & @CRLF )

  ; Get Desktop element
  Local $pDesktop, $oDesktop
  $oUIAutomation.GetRootElement( $pDesktop )
  $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement9, $dtag_IUIAutomationElement9 )
  If Not IsObj( $oDesktop ) Then Return ConsoleWrite( "$oDesktop ERR" & @CRLF )
  ConsoleWrite( "$oDesktop OK" & @CRLF )

  ; --- Find window/control ---

  ConsoleWrite( "--- Find window/control ---" & @CRLF )

  Local $pCondition0
  ;$oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "Notepad", $pCondition0 )
  $oUIAutomation.CreatePropertyConditionEx( $UIA_ClassNamePropertyId, "Note", $PropertyConditionFlags_MatchSubstring, $pCondition0 )
  If Not $pCondition0 Then Return ConsoleWrite( "$pCondition0 ERR" & @CRLF )
  ConsoleWrite( "$pCondition0 OK" & @CRLF )

  Local $pWindow1, $oWindow1
  $oDesktop.FindFirst( $TreeScope_Children, $pCondition0, $pWindow1 )
  $oWindow1 = ObjCreateInterface( $pWindow1, $sIID_IUIAutomationElement9, $dtag_IUIAutomationElement9 )
  If Not IsObj( $oWindow1 ) Then Return ConsoleWrite( "$oWindow1 ERR" & @CRLF )
  ConsoleWrite( "$oWindow1 OK" & @CRLF )

  ; --- Element Properties ---

  ConsoleWrite( "--- Element Properties ---" & @CRLF )

  Local $sName1
  $oWindow1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName1 )
  ConsoleWrite( "$sName1 = " & $sName1 & @CRLF )
EndFunc

SciTE output: 

$oUIAutomation OK
$oDesktop OK
--- Find window/control ---
$pCondition0 OK
$oWindow1 OK
--- Element Properties ---
$sName1 = Untitled - Notepad

 

Example 16

On 11/4/2021 at 11:30 AM, MightyWeird said:

I tried to use   "UIA_MouseClick "  (see example 16), but when I run the example script it jumps to "UIA_Constants" with a lot of "errors: $UIA_InvokePatternId previously declared as a 'Const'." errors....

Example 16 includes the original CUIAutomation2.au3 created by junkew, which contains all the Windows 7 constants. In newer versions of the UIA code I use UIA_Constants.au3 which contains all Windows 7 constants as well as all constants added in Windows 8, 8.1 and 10. The reason for the error in your code is that both CUIAutomation2.au3 and UIA_Constants.au3 is included. Only UIA_Constants.au3 should be included.

Example 16 is an old example from March 9, 2019. Unfortunately, it's not possible for me to update all examples every time I update the UIA code. And the Windows 7 code still works, unless you need the newer code added in later Windows versions.

 

On 11/4/2021 at 11:30 AM, MightyWeird said:

Anyone got any hints?

To close a window with UIA code, use the Close method of the IUIAutomationWindowPattern interface. See examples 2 and 3 in Patterns (actions). Note that these are also old examples from March 9, 2019.

If the Close method doesn't work, you can e.g. try using the UIA_MouseClick() function, which almost always works. UIA_MouseClick() is found in two different UDFs: UIA_Functions.au3, which is the new version based on UIA_Constants.au3, and UIA_Functions-a.au3, which is the old version based on CUIAutomation2.au3. Use the new version. 

An obvious error in your code is that you use the UIA_MouseClick() function with a pattern (action) object as input parameter. The correct input parameter is a window or control (a UIA element) object. Only UIA elements can be clicked. Not patterns (actions).

Link to comment
Share on other sites

  • 2 months later...

awesome tool!  thank you!

I am trying to capture the text inside the windows notification.  This successfully finds the Window: New Notification. But it looks like I need to somehow identify the child Window: New Notification. I have been playing with this all afternoon. I was able to get the Notepad example to work. but this is a little different because of the tree structure (i think)

ConsoleWrite( "--- Find window/control ---" & @CRLF )

  Local $pCondition0
  $oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "Windows.UI.Core.CoreWindow", $pCondition0 )
  If Not $pCondition0 Then Return ConsoleWrite( "$pCondition0 ERR" & @CRLF )
  ConsoleWrite( "$pCondition0 OK" & @CRLF )

  Local $pWindow1, $oWindow1
  $oDesktop.FindFirst( $TreeScope_Children, $pCondition0, $pWindow1 )
  $oWindow1 = ObjCreateInterface( $pWindow1, $sIID_IUIAutomationElement9, $dtag_IUIAutomationElement9 )
  If Not IsObj( $oWindow1 ) Then Return ConsoleWrite( "$oWindow1 ERR" & @CRLF )
  ConsoleWrite( "$oWindow1 OK" & @CRLF )

 

sample discord window notification.PNG

Link to comment
Share on other sites

When you need to find a control in a child window with UIA code, the technique is first to find the top window (just below the Desktop), then the child window and finally the control in the child window. It's a 3-step procedure where each step is pretty much the same except for the type of UIA element.

You can see a recent example of the technique in this post in the Element structure and UI Automation code sections.

Add a new post if you need more help. Then either junkew or I can usually find time to help (currently mostly junkew, thank you very much junkew).

Link to comment
Share on other sites

  • 5 months later...

I'm a newb at this and for someone who has never done this before is having extreme difficulty creating my first ui automation. Was hoping they'd be some video instructions which would validate the instruction 100%. I do appreciate the work with autoit and I think it's going to take me some time. I've already spent a week but no success.

Link to comment
Share on other sites

UIA is probably not for a newbie. sorry.

You could start here 

but this thread also teaches the basics with the created spy spitting the code out for you.

The examples in the original uiawrappers thread you just should start trying one by one. First control notepad before continuing with more complex  userinterfaces.

 

Link to comment
Share on other sites

Two quick problems. I'm trying to read Windows 10 notifications.

If notification is on screen - I can read main "window" and title + message together (contains mutiple commas, cannot split it easily).

#include <Timers.au3>
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

#include "CUIAutomation2.au3" ; Get proper version in UIASpy Includes folder

Opt( "MustDeclareVars", 1 )
Opt( "TrayIconDebug", 1 )

  ; Create UI Automation object
  global $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $dtagIUIAutomation )
    If Not IsObj( $oUIAutomation ) Then
      ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
    Else
        ConsoleWrite( "$oUIAutomation OK" & @CRLF )
    EndIf

  ; Get Desktop element
  global $pDesktop, $oDesktop
  $oUIAutomation.GetRootElement( $pDesktop )
  $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
    If Not IsObj( $oDesktop ) Then
        ConsoleWrite( "$oDesktop ERR" & @CRLF )
    Else
        ConsoleWrite( "$oDesktop OK" & @CRLF )
    EndIf


; --- Find window/control ---

ConsoleWrite( "--- Find window/control ---" & @CRLF )

global $pCondition0
$oUIAutomation.CreatePropertyCondition( $UIA_AutomationIdPropertyId, "NormalToastView", $pCondition0 )
If Not $pCondition0 Then
    ConsoleWrite( "$pCondition0 ERR" & @CRLF )
Else
    ConsoleWrite( "$pCondition0 OK" & @CRLF )
EndIf

global $pWindow1, $oWindow1
Global $start = _Timer_Init()
ConsoleWrite( "step 1 ************ " & _Timer_Diff($start)/1000 & @CRLF )
$oDesktop.FindFirst( $TreeScope_Descendants, $pCondition0, $pWindow1 )
ConsoleWrite( "step 2 ************ " & _Timer_Diff($start)/1000 & @CRLF )
$oWindow1 = ObjCreateInterface( $pWindow1, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
ConsoleWrite( "step 3 ************ " & _Timer_Diff($start)/1000 & @CRLF )
If Not IsObj( $oWindow1 ) Then
    ConsoleWrite( "$oWindow1 ERR" & @CRLF )
else
    ConsoleWrite( "$oWindow1 OK" & @CRLF )
    Global $sName1
    $oWindow1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName1 )
    ConsoleWrite( "$sName1 = " & $sName1 & @CRLF )
EndIf

Output is this:

$oUIAutomation OK
$oDesktop OK
--- Find window/control ---
$pCondition0 OK
step 1 ************ 1.2e-05
step 2 ************ 0.0070798
step 3 ************ 0.0076971
$oWindow1 OK
$sName1 = New notification from Windows PowerShell, WHATS, UPPP THIS IS A PARAGRAPH!, wzzup, this is a title.

Problem 1:

How do I read title and message texts separately?

Check attached screenshot

Problem 2:

If notification is NOT displayed, the above script runs for 26 seconds and find no "window" - which is normal.

How do I make it run & exit instantly, just like above?

Weird side effect - during this time, my Firefox window is frozen. It records operatons (like click, scroll) and performs all said operations after AutoIt script stops running. All other windows are fine (total commander, autoit help, paintshop pro, winscp....)

Output:

$oUIAutomation OK
$oDesktop OK
--- Find window/control ---
$pCondition0 OK
step 1 ************ 1.22e-05
step 2 ************ 26.8370791
step 3 ************ 26.837575
$oWindow1 ERR

Thank you.

2022-06-28 14_35_21-Window.png

Edited by queensoft
Link to comment
Share on other sites

Proplem 1: Just iterate over the childcontrols either with a treewalker or a findall. See examples and/or uiawrappers source.

Problem 2: Just iterate childcontrols not all descendants, limiting number of controls will speedup things 

Alternative: user the findall as that one returns relatively quick all controls as long as you donot scan whole hierarchy. This can be much quicker then the 26 seconds before it times out.

Link to comment
Share on other sites

  • 3 months later...

Hi I could use some help / Example as well reading all TV elements of a control in a window;

This is how far I got :

I can find the TV Control but need all elements as output in the TV. I checked the ElementArray.au3 example but no luck so far ?

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

#include "..\..\Includes\CUIAutomation2.au3"

Opt( "MustDeclareVars", 1)

Example()

Func Example()
  ; Create UI Automation object
  Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $dtagIUIAutomation )
  If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF )
  ConsoleWrite( "$oUIAutomation OK" & @CRLF )

  ; Get Desktop element
  Local $pDesktop, $oDesktop
  $oUIAutomation.GetRootElement( $pDesktop )
  $oDesktop = ObjCreateInterface( $pDesktop, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
  If Not IsObj( $oDesktop ) Then Return ConsoleWrite( "$oDesktop ERR" & @CRLF )

  ConsoleWrite( "$oDesktop OK" & @CRLF )

;~ $UIA_ClassNamePropertyId                            MixxxMainWindow

  ConsoleWrite( "--- Mixxx window ---" & @CRLF )

  Local $pCondition ; Note that $UIA_ClassNamePropertyId maybe ia a CASE SENSITIVE condition
  $oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "MixxxMainWindow", $pCondition )
  If Not $pCondition Then Return ConsoleWrite( "$pCondition ERR" & @CRLF )

  ConsoleWrite( "$pCondition Mixxx OK" & @CRLF )

  Local $pMixxx, $oMixxx
  $oDesktop.FindFirst( $TreeScope_Descendants, $pCondition, $pMixxx )
  $oMixxx = ObjCreateInterface( $pMixxx, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
  If Not IsObj( $oMixxx ) Then Return ConsoleWrite( "$oMixxx ERR" & @CRLF )

  ConsoleWrite( "$oMixxx OK" & @CRLF )

  Local $pCondition0

  $oUIAutomation.CreatePropertyCondition( $UIA_ClassNamePropertyId, "WLibrarySidebar", $pCondition0 )
  If Not $pCondition0 Then Return ConsoleWrite( "$pCondition0 ERR" & @CRLF )

  ConsoleWrite( "$pCondition0 OK" & @CRLF )

  Local $pTree1, $oTree1, $dtag_IUIAutomationElement
  $oMixxx.FindFirst( $TreeScope_Descendants, $pCondition0, $pTree1 )
  $oTree1 = ObjCreateInterface( $pTree1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
  If Not IsObj( $oTree1 ) Then Return ConsoleWrite( "$oTree1 ERR" & @CRLF )

  ConsoleWrite( "$oTree1 WLibrarySidebar OK" & @CRLF )


; --- Find Track Element ---

ConsoleWrite( "--- Find window/control ---" & @CRLF )

Local  $pCondition1, $pAndCondition1
$oUIAutomation.CreatePropertyCondition( $UIA_ControlTypePropertyId, $UIA_TreeItemControlTypeId, $pCondition0 )
$oUIAutomation.CreatePropertyCondition( $UIA_NamePropertyId, "Tracks", $pCondition1 )
$oUIAutomation.CreateAndCondition( $pCondition0, $pCondition1, $pAndCondition1 )
If Not $pAndCondition1 Then Return ConsoleWrite( "$pAndCondition1 ERR" & @CRLF )
ConsoleWrite( "$pAndCondition1 OK" & @CRLF )

Local $pTreeItem1, $oTreeItem1
$oMixxx.FindFirst( $TreeScope_Descendants, $pAndCondition1, $pTreeItem1 )
$oTreeItem1 = ObjCreateInterface( $pTreeItem1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
If Not IsObj( $oTreeItem1 ) Then Return ConsoleWrite( "$oTreeItem1 ERR" & @CRLF )
ConsoleWrite( "$oTreeItem1 OK" & @CRLF )

;~  ----- Get TV Elements -----

  Local $pElements
  $oMixxx.FindAll( $TreeScope_Descendants, $pCondition, $pElements )
  ConsoleWrite( "$$oTree1.FindAll()" & @CRLF )

;~  ---- Array -----
  Local $oUIElementArray1, $iLength1 ; $pElements is a pointer to an UI Automation element array
  $oUIElementArray1 = ObjCreateInterFace( $pElements, $sIID_IUIAutomationElementArray, $dtagIUIAutomationElementArray )
  $oUIElementArray1.Length( $iLength1 )
  If Not $iLength1 Then Return ConsoleWrite( "$iLength1 = 0 ERR" & @CRLF )
  ConsoleWrite( "$iLength1 = " & $iLength1 & @CRLF )


  Local $pElement1, $oElement1, $sValue1
  For $i = 0 To $iLength1 - 1
    $oUIElementArray1.GetElement( $i, $pElement1 )
    $oElement1 = ObjCreateInterface( $pElement1, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
    ;$oElement1.GetCurrentPropertyValue( $UIA_ClassNamePropertyId, $sValue1 ) ; $UIA_ClassNamePropertyId is used as example
    ;ConsoleWrite( "$sValue1 = " & $sValue1 & @CRLF )
    $oElement1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sValue1 )
    ConsoleWrite( "$sValue1 = " & $sValue1 & @CRLF )
  Next


  EndFunc

The is the Scite Output :

Quote

>Running:(3.3.14.5):C:\Program Files (x86)\AutoIt3\autoit3.exe "C:\_\Apps\AutoIT3\COM OO\_IUIAutomation Native\Mixxx TEST.au3"    
--> Press Ctrl+Alt+Break to Restart or Ctrl+Break to Stop
$oUIAutomation OK
$oDesktop OK
--- Mixxx window ---
$pCondition Mixxx OK
$oMixxx OK
$pCondition0 OK
$oTree1 WLibrarySidebar OK
--- Find window/control ---
$pAndCondition1 OK
$oTreeItem1 OK
$$oTree1.FindAll()
$iLength1 = 0 ERR
+>15:52:34 AutoIt3.exe ended.rc:0

This part is not working 

;~  ---- Array -----

  Local $oUIElementArray1, $iLength1 ; $pElements is a pointer to an UI Automation element array
  $oUIElementArray1 = ObjCreateInterFace( $pElements, $sIID_IUIAutomationElementArray, $dtagIUIAutomationElementArray )
  $oUIElementArray1.Length( $iLength1 )
  If Not $iLength1 Then Return ConsoleWrite( "$iLength1 = 0 ERR" & @CRLF )
  ConsoleWrite( "$iLength1 = " & $iLength1 & @CRLF )


  Local $pElement1, $oElement1, $sValue1
  For $i = 0 To $iLength1 - 1
    $oUIElementArray1.GetElement( $i, $pElement1 )
    $oElement1 = ObjCreateInterface( $pElement1, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
    ;$oElement1.GetCurrentPropertyValue( $UIA_ClassNamePropertyId, $sValue1 ) ; $UIA_ClassNamePropertyId is used as example
    ;ConsoleWrite( "$sValue1 = " & $sValue1 & @CRLF )
    $oElement1.GetCurrentPropertyValue( $UIA_NamePropertyId, $sValue1 )
    ConsoleWrite( "$sValue1 = " & $sValue1 & @CRLF )
  Next

Much appreciated !

Link to comment
Share on other sites

It looks like you initially find the first treeview item named "Tracks" with this code:

Local  $pCondition1, $pAndCondition1
$oUIAutomation.CreatePropertyCondition( $UIA_ControlTypePropertyId, $UIA_TreeItemControlTypeId, $pCondition0 )
$oUIAutomation.CreatePropertyCondition( $UIA_NamePropertyId, "Tracks", $pCondition1 )
$oUIAutomation.CreateAndCondition( $pCondition0, $pCondition1, $pAndCondition1 )
If Not $pAndCondition1 Then Return ConsoleWrite( "$pAndCondition1 ERR" & @CRLF )
ConsoleWrite( "$pAndCondition1 OK" & @CRLF )

Local $pTreeItem1, $oTreeItem1
$oMixxx.FindFirst( $TreeScope_Descendants, $pAndCondition1, $pTreeItem1 )
$oTreeItem1 = ObjCreateInterface( $pTreeItem1, $sIID_IUIAutomationElement, $dtag_IUIAutomationElement )
If Not IsObj( $oTreeItem1 ) Then Return ConsoleWrite( "$oTreeItem1 ERR" & @CRLF )
ConsoleWrite( "$oTreeItem1 OK" & @CRLF )

Then you'll find all treeview items (regardless of names) with this code:

Local $pElements
$oMixxx.FindAll( $TreeScope_Descendants, $pCondition, $pElements )

But shouldn't it be $pCondition0 (finds treeview items) instead of $pCondition (finds Mixxx window)?

I don't understand why you use $oMixxx and not $oTree1. I assume that $oTree1 is the referred treeview while $oMixxx is the main window. So it seems best to use $oTree1.

If the code still doesn't work, print the entire control structure of the Mixxx window as described in this post.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...