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.
Step 2: After launching the swift login page, I am clicking on HSM Box option.
Step 3: After clicking on HSM Box, we need to fill up username, password and session pin on below screen.
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.
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 ?