1

I am trying to automate a web based application using a simple VBA Macro. First of all it is for a company that I work for, hence the Application is in secured citrix environment. I launch it using "App Launcher" program.

I was trying my hand to use the windows API. The one important limitation of citrix environment, is that I can not install spy++ on it. Because company does not allow it. Hence I use another VBA code that I found on internet to actually list all the windows, objects , handles on my machine. This code is written by Mark Rowlinson.

Now, using this limited information I am trying to automate the GUI access.

Step 1: Launch the application using App Launcher Program. enter image description here

Step 2: After launching the swift login page, I am clicking on HSM Box option.

enter image description here

Step 3: After clicking on HSM Box, we need to fill up username, password and session pin on below screen. enter image description here

Here, I have automated the step 2, where I launch the swift login (Manual Click) and click on the HSM Box option (Automated using VBA). But after it on step 3, I am not able to move ahead because I am not able to locate correct handle. As already stated, I can not use spy ++ because it is not allowed to install any third party software. Hence I am using another VBA code just to list down all the handles on a given page. Then how can I identify which element to click in such case ?

Below are the codes that I am using.

My VBA code to automate step 2. Which is working fine.

Private Declare PtrSafe Function apiGetClassName Lib "User32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function apiGetDesktopWindow Lib "User32" Alias "GetDesktopWindow" () As Long
Private Declare PtrSafe Function apiGetWindow Lib "User32" Alias "GetWindow" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Declare PtrSafe Function apiGetWindowLong Lib "User32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function apiGetWindowText Lib "User32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal aint As Long) As Long
Private Declare PtrSafe Function FindWindow Lib "User32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe Function FindWindowEx Lib "User32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function SendMessage Lib "User32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare PtrSafe Function SendMessageByString Lib "User32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Private Declare Function GetWindowText Lib "User32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Private Const mcGWCHILD = 5
Private Const mcGWHWNDNEXT = 2
Private Const mcGWLSTYLE = (-16)
Private Const mcWSVISIBLE = &H10000000
Private Const mconMAXLEN = 255
Private Const BM_CLICK As Integer = &HF5
Private Const WM_ACTIVATE As Integer = &H6
Private Const WA_ACTIVE As Integer = &H1

Sub ClickPrintButtonWindowsAPI()
    
    Dim hw As Long, mxtextbox As Long, tokenbutton As Long
    Dim pwd As String

    hw = FindWindow(vbNullString, "xxxxx xxx SWIFT Login")
    MsgBox hw
    tokenbutton = FindWindowEx(hw, 0&, "Button", "HSM Box")
    MsgBox tokenbutton
    Call SendMessage(tokenbutton, WM_ACTIVATE, 0, 0)
    Call SendMessage(tokenbutton, BM_CLICK, 0, 0)
    
    subwindow = FindWindowEx(hw, 0&, "ComboBox", vbNullString)
    MsgBox subwindow
    pwdTxtbox = FindWindowEx(subwindow, 0&, "Edit", vbNullString)
    MsgBox pwdTxtbox
    Call SendMessage(pwdTxtbox, WM_ACTIVATE, 0, 0)
    Call SendMessageByString(pwdTxtbox, WM_SETTEXT, 0, "xxxxxxxx")
    
End Sub

The VBA code that I am using from Mark Rowlinson to list all the windows, handles, objects etc.

Option Explicit
 
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
 
Private x As Integer
 
 'Used a user defined type here rather than Enum so that it works on 97
Private Type winEnum
    winHandle As Integer
    winClass As Integer
    winTitle As Integer
    winHandleClass As Integer
    winHandleTitle As Integer
    winHandleClassTitle As Integer
End Type
Dim winOutputType As winEnum
 
Public Sub GetWindows()
    x = 0
    winOutputType.winHandle = 0
    winOutputType.winClass = 1
    winOutputType.winTitle = 2
    winOutputType.winHandleClass = 3
    winOutputType.winHandleTitle = 4
    winOutputType.winHandleClassTitle = 5
     
    GetWinInfo 0&, 0, winOutputType.winHandleClassTitle
End Sub
 
 
Private Sub GetWinInfo(hParent As Long, intOffset As Integer, OutputType As Integer)
     'Sub to recursively obtain window handles, classes and text
     'given a parent window to search
     'Written by Mark Rowlinson
     'www.markrowlinson.co.uk - The Programming Emporium
    Dim hWnd As Long, lngRet As Long, y As Integer
    Dim strText As String
    hWnd = FindWindowEx(hParent, 0&, vbNullString, vbNullString)
    While hWnd <> 0
        Select Case OutputType
        Case winOutputType.winClass
            strText = String$(100, Chr$(0))
            lngRet = GetClassName(hWnd, strText, 100)
            Range("a1").Offset(x, intOffset) = Left$(strText, lngRet)
        Case winOutputType.winHandle
            Range("a1").Offset(x, intOffset) = hWnd
        Case winOutputType.winTitle
            strText = String$(100, Chr$(0))
            lngRet = GetWindowText(hWnd, strText, 100)
            If lngRet > 0 Then
                Range("a1").Offset(x, intOffset) = Left$(strText, lngRet)
            Else
                Range("a1").Offset(x, intOffset) = "N/A"
            End If
        Case winOutputType.winHandleClass
            Range("a1").Offset(x, intOffset) = hWnd
            strText = String$(100, Chr$(0))
            lngRet = GetClassName(hWnd, strText, 100)
            Range("a1").Offset(x, intOffset + 1) = Left$(strText, lngRet)
        Case winOutputType.winHandleTitle
            Range("a1").Offset(x, intOffset) = hWnd
            strText = String$(100, Chr$(0))
            lngRet = GetWindowText(hWnd, strText, 100)
            If lngRet > 0 Then
                Range("a1").Offset(x, intOffset + 1) = Left$(strText, lngRet)
            Else
                Range("a1").Offset(x, intOffset + 1) = "N/A"
            End If
        Case winOutputType.winHandleClassTitle
            Range("a1").Offset(x, intOffset) = hWnd
            strText = String$(100, Chr$(0))
            lngRet = GetClassName(hWnd, strText, 100)
            Range("a1").Offset(x, intOffset + 1) = Left$(strText, lngRet)
            strText = String$(100, Chr$(0))
            lngRet = GetWindowText(hWnd, strText, 100)
            If lngRet > 0 Then
                Range("a1").Offset(x, intOffset + 2) = Left$(strText, lngRet)
            Else
                Range("a1").Offset(x, intOffset + 2) = "N/A"
            End If
        End Select
         'check for children
        y = x
        Select Case OutputType
        Case Is > 4
            GetWinInfo hWnd, intOffset + 3, OutputType
        Case Is > 2
            GetWinInfo hWnd, intOffset + 2, OutputType
        Case Else
            GetWinInfo hWnd, intOffset + 1, OutputType
        End Select
         'increment by 1 row if no children found
        If y = x Then
            x = x + 1
        End If
         'now get next window
        hWnd = FindWindowEx(hParent, hWnd, vbNullString, vbNullString)
    Wend
     
End Sub

Below is output in excel after running the above code.

enter image description here

If you can see here, below are the handle names and values for Username and Password. How can I select the "Passowrd" edit box ? It has the value N/A.

  • 1640324 Static Username
  • 722818 Edit N/A
  • 722816 Static Password
  • 722814 Edit N/A

I need a way out here.

Also the first step to parse throgh the tree structure of App Launcher program is also not possible, because of the same limitation of spy ++. What shall I do ?

aditya lele
  • 95
  • 1
  • 7
  • Just an idea, may be sending TAB keys would be enough to loop thru controls – sarh Mar 13 '23 at 18:53
  • Maybe: SendMessage( hWndOfPassword, WM_SETFOCUS, 0, 0 ); (WM_SETFOCUS = &H7) – ΑΓΡΙΑ ΠΕΣΤΡΟΦΑ Mar 13 '23 at 19:24
  • 1
    You have marked all your functions `PtrSafe` without putting a single `LongPtr` to where it should be. You [have lied](https://stackoverflow.com/a/63801528/11683) to the compiler. – GSerg Mar 13 '23 at 19:48
  • Thanks GSerg for the hint. Also I solved it myself, by recursrively doing the referencing. e.g. something like below firstedit = FindWindowEx(hw, 0&, "Edit", vbNullString) secondedit = FindWindowEx(hw, firstedit, "Edit", vbNullString .... By this I can parse throgh all the edit handles at same level. – aditya lele Mar 16 '23 at 16:19

1 Answers1

0

Thanks GSerg for the hint. Also I solved it myself, by recursrively doing the referencing. e.g. something like below

firstedit = FindWindowEx(hw, 0&, "Edit", vbNullString) 
secondedit = FindWindowEx(hw, firstedit, "Edit", vbNullString .... 

By this I can parse through all the edit handles at same level until I found the correct handle.

aditya lele
  • 95
  • 1
  • 7