Determine if Window is Open Using UIAutomation - excel

I am writing an excel plugin that outputs an edge list and a graphml sheet that are later used by the program yED to make a bitmap of the graph for the overall output of the plugin. I am using a shell command to open the appropriate file in yED, and UIAutomation to send the commands to yED.
When there is a window of yED already open, the code executes just fine. The window is found by the polling, then set active and the commands sent over. Where it goes wrong is when the shell command causes a new window of yED to be launched. There is a splash screen for yED as it is loading in that takes a few seconds to get through, and shares the same Name and Class as the window that I am looking for. The HWND is different between the two.
My code will error out whenever there is a new window of yED launching. The error reads:
Run-time error '-2147467259 (80004005)': Automation error Unspecified
error
Reference code:
Function FindyEdByClass() As IUIAutomationElement
Dim oUIAutomation As New CUIAutomation
Dim oUIADesktop As IUIAutomationElement
Dim allChilds As IUIAutomationElementArray
Dim oUIAyED As IUIAutomationElement
Dim i As Integer
Dim Timer As Date
Set oUIADesktop = oUIAutomation.GetRootElement
Set oUIAyED = oUIADesktop
Timer = Now
RestartLoop:
Set allChilds = oUIADesktop.FindAll(TreeScope_Children, oUIAutomation.CreateTrueCondition)
Debug.Print "StartLoop" & vbCrLf;
For i = 0 To allChilds.Length - 1
'EDIT: the following line is the one that errors out.
If allChilds.GetElement(i).CurrentName = "Graph.graphml - yEd" And allChilds.GetElement(i).CurrentClassName = "SunAwtFrame" Then
Debug.Print "Found Child - yED" & vbCrLf;
Set oUIAyED = allChilds.GetElement(i)
End If
Next
If Now() > (Timer + TimeValue("00:00:10")) Then GoTo NoyED
If oUIAyED.CurrentName = "Desktop" Then GoTo RestartLoop
EndOFLoop:
Debug.Print oUIAyED.CurrentName & " " & oUIAyED.CurrentClassName & vbCrLf;
Set FindyEdByClass = oUIAyED
Exit Function
NoyED:
MsgBox "No yED Window Found"
End
End Function
The line it errors out on the If statement. Through use of Debug.Print and FindWindowEx I have discovered that the object it errors out on is not the yED window I am looking for, but the yED splash screen that precedes it. I assume that the error is caused by the splash screen disappearing after its load time is up.
How do I go about finding the window I am looking for in this case? I need to be able to find the window without seeing the splash screen that will cause the error, and I need to be able to differentiate between the two by something other than class or name.
Note: I want to compare the windows by HWND but I don't know how to find it without the class/name, and it is never the same between two runs.

Related

Determine Process ID with VBA

Situation - I have a macro where I need to send keystrokes to two Firefox windows in order. Unfortunately both windows have the same title. To handle this I have activated the window, sent my keystrokes, then used F6 to load the URL of the second window and then send the keystrokes then use F6 to return it to the original page.
The issue is that loading the webpages is unreliable. Page load speeds vary so much that using a wait command is not consistent or reliable to ensure the keystroke makes it to the second window.
Question -
I've read a scattering of posts that mentioned that app activate will work with Process ID's. Since each window would have its own PID that would be an ideal way to handle 2 windows with the same title. I am unable to find information specifically how to determine the PID of each window with a given name.
You can use something like the following. You'll have to tinker about with the different info available in the Win32_Process class to figure out which window is which. It's also important to keep in mind that one window could mean many processes.
Public Sub getPID()
Dim objServices As Object, objProcessSet As Object, Process As Object
Set objServices = GetObject("winmgmts:\\.\root\CIMV2")
Set objProcessSet = objServices.ExecQuery("SELECT ProcessID, name FROM Win32_Process WHERE name = ""firefox.exe""", , 48)
'you may find more than one processid depending on your search/program
For Each Process In objProcessSet
Debug.Print Process.ProcessID, Process.Name
Next
Set objProcessSet = Nothing
End Sub
Since you'll probably want to explore the options with WMI a bit, you may want to add a Tools>>References to the Microsoft WMI library so you don't have to deal with Dim bla as Object. Then you can add breakpoints and see what's going on in the Locals pane.
After adding the reference:
Public Sub getDetailsByAppName()
Dim objProcessSet As WbemScripting.SWbemObjectSet
Dim objProcess As WbemScripting.SWbemObject
Dim objServices As WbemScripting.SWbemServices
Dim objLocator As WbemScripting.SWbemLocator
'set up wmi for local computer querying
Set objLocator = New WbemScripting.SWbemLocator
Set objServices = objLocator.ConnectServer(".") 'local
'Get all the gory details for a name of a running application
Set objProcessSet = objServices.ExecQuery("SELECT * FROM Win32_Process WHERE name = ""firefox.exe""", , 48)
RecordCount = 1
'Loop through each process returned
For Each objProcess In objProcessSet
'Loop through each property/field
For Each Field In objProcess.Properties_
Debug.Print RecordCount, Field.Name, Field.Value
Next
RecordCount = RecordCount + 1
Next
Set objProcessSet = Nothing
Set objServices = Nothing
Set objLocator = Nothing
End Sub
That will print out every property of every process found for the name 'firefox.exe'.

How can I use installscript to detect Excel.exe running?

Ive been trying to detect the excel process in my installshield installer.
I have a custom action that runs after appsearch and pops a window if it finds the process and displays a warning to the user.
I have tried using some old examples I found on installsite.org and using the findWindow() call. Neither seems to find excel.exe in the process list.
Here is a snippet of code I was using when trying the findwindow
export prototype MyTestFunction(HWND);
function MyTestFunction(hMSI)
HWND nHwnd;
begin
nHwnd = FindWindow("EXCEL", "");
if (nHwnd != 0) then
MessageBox("found excel", WARNING);
SendMessage(nHwnd, WM_CLOSE, 0, 0);
else
MessageBox("cant find excel", WARNING);
endif;
end;
Note that only the else block ever seems to fire regardless of the application being open or closed.
I have tried several different variants of this mostly just replacing the "excel" with different capitalization, extensions and versions. Nothing seems to detect the window. I used Spy++ and it reported that the window is named after the name of the currently opened notebook which complicates things since I have no way of knowing what a user could have opened.
I am open to suggestions here. The only requirement for this solution is that it has to be able to run as either a custom action or part of an install condition from within Installshield.
You could use a vbscript Custom Action.
You can run this CA at the begining of UISequence or ExecuteSequence (or both) If you want it as a part of the Install condition.
Add the code in a vbscript function and configure "Return Processing" Option for the Custom Action to "Synchonous (Check exit code)" if you want to stop the installation process.
Here is my script:
Public Function StopProcess
Dim objWMIService, objProcess, colProcess
Dim strComputer, executableFileName
Const IDABORT = 3
strComputer = "."
executableFileName = "excel.exe"
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" & executableFileName & "'")
For Each objProcess in colProcess
objProcess.Terminate()
' OR
StopProcess = IDABORT
Exit for
Next
End function
Obviously trying to figure out if a process is running by finding the associated window has its pitfalls.
My suggestion is to detect if the process for Excel.exe is running. It would involve enumerating the processes on the system. Modify your code accordingly. Its easier to do it using C++ but there are numerous examples available which show you how to achieve what i have just stated.
https://community.flexerasoftware.com/archive/index.php?t-162141.html
https://community.flexerasoftware.com/archive/index.php?t-188807.html
Take
We can write a InstallScript code as well to achieve this. Please refer the code below :
function CheckRunningProcessAndTerminate(hMSI)
// To Do: Declare local variables.
OBJECT wmi,objProc;
STRING szProcessName;
begin
// To Do: Write script that will be executed when MyFunction is called.
szProcessName = "Excel.exe";
set wmi = CoGetObject("winmgmts://./root/cimv2", "");
set objProc = wmi.ExecQuery("Select * from Win32_Process where Name = '" + szProcessName + "'");
if (objProc.count > 0) then
MessageBox("Process is running.", INFORMATION);
//kill proces
TerminateProcesses(szProcessName);
//objProc.Terminate(); //I tried this, but it didn't worked.
else
MessageBox("Process is not running.", INFORMATION);
endif;
end;

vb6 trying to get old program working with excel 2013

I have this code from a program that was created to take data and export it into excel it is in vb6 and I dont know much about vb6 I started coding in vb.net could someone tell me why this isnt working with excel 2013 it opens but then closes right away and i am unsure as to why.
Sub GetExcel()
Dim MyExcel As Object ' Variable to hold reference
' to Microsoft Word.
Dim ExcelWasNotRunning As Boolean ' Flag for final release.
' Test to see if there is a copy of Microsoft Excel already running.
10 On Error Resume Next ' Defer error trapping.
' Getobject function called without the first argument returns a
' reference to an instance of the application. If the application isn't
' running, an error occurs.
20 Set MyExcel = GetObject(, "XLMAIN")
30 If Err.Number <> 0 Then ExcelWasNotRunning = True
40 Err.Clear ' Clear Err object in case error occurred.
' Check for Microsoft Excel. If Microsoft Excel is running,
' enter it into the Running Object table.
50 DetectExcel
' Set the object variable to reference the file you want to see.
60 Set MyExcel = GetObject(App.Path & "\test.xls")
' Show Microsoft Word through its Application property. Then
' show the actual window containing the file using the Windows
' collection of the MyWord object reference.
' MyExcel.Application.Visible = True
' MyExcel.document(1).Visible = True
70 MyExcel.Show , f1
'//////////////////////////////////////////////
' Do manipulations of your file here.
'//////////////////////////////////////////////
' ...
' If this copy of Microsoft Excel was not running when you
' started, close it using the Application property's Quit method.
' Note that when you try to quit Microsoft Excel, the
' title bar blinks and a message is displayed asking if you
' want to save any loaded files.
80 If ExcelWasNotRunning = True Then
90 MyExcel.Application.Quit
100 End If
110 Set MyExcel = Nothing ' Release reference to the
' application and spreadsheet.
End Sub
Sub DetectExcel()
' Procedure dectects a running Word and registers it.
Const WM_USER = 1024
Dim hwnd As Long
' If Excel is running this API call returns its handle.
10 hwnd = FindWindow("XLMAIN", 0)
20 If hwnd = 0 Then ' 0 means Word not running.
30 Exit Sub
40 Else
' Word is running so use the SendMessage API
' function to enter it in the Running Object Table.
50 SendMessage hwnd, WM_USER + 18, 0, 0
60 End If
End Sub
even if i can get some direction on how to rewrite this it would be appreciated.
It's been a long time since I've used it, so you may need to address some of the finer points... But:
First and foremost - get rid of the On Error Resume Next it masks whatever happens next.
This is not a situation where you want to have errors ignored. Capture the error, show something useful (not debug information and not details that can be used for hacking) to the user, and resume or return (after cleanup of assigned object variables to avoid memory leaks).
Then step through the code to see what errors you get.
Some of the things that may help:
Change the specification in your GetObject to be "Excel.Application" i.e.
Set MyExcel = GetObject(, "Excel.Application")
Instead of MyExcel.Show which may or may not be supported, use actual Excel objects and their methods and properties, for instance the commented MyExcel.Application.Visible = True works, while I question the MyExcel.document(1).Visible = True.
Look up the Excel object model help for details. And never hardcode the index - obtain the actual reference that you want and use it.
You can still find articles about Excel and VB6 on line. Use your search engine of choice and good luck.
Beware of those who say something is impossible - first see if it works. Some say you can's install VB6 on a 64-bit system - that is not true, but there are some issues getting it to work. Some say you can't interact with 64-bit Office from VB6, but that is not true. Maybe you can't do some things - I haven't done much with it, just enough to know it is possible to do some things.
Consider developing and testing with typed objects in VB6. It can be very helpful, but to make the application version independent, you will need to remove the object typing before final test and deployment.

VBscript and Excel: Opening and closing Excel objects - Arraylist preventing closing of object

I've got a problem with a vbscript which creates Excel objects and reads from an Excel file.
The vbscript is executed from an Excel macro, and then creates and opens the Excel file and reads from it. The problem is that the Excel object isn't allways closed, even though I'm trying to to it.
Here's the code from the vbscript:
Set ExcelObject = createobject("Excel.Application")
ExcelObject.workbooks.open testWorkBookPath
Set testActionArray = CreateObject( "System.Collections.ArrayList" )
Function getTestsCaseActions (testsPath, esheet, row, col)
Set my_sheet = ExcelObject.sheets.item(esheet)
tempArray = array(my_sheet.cells(row, col-2), "")
testActionArray.Add tempArray
Do While my_sheet.cells(row, col).Value <> ""
tempArray = array(my_sheet.cells(row, col), my_sheet.cells(row+1, col))
testActionArray.Add tempArray
col = col+1
Loop
End Function
getTestsCaseActions testWorkBookPath, testCaseSheet, 3, 4
ExcelObject.Quit
Now, if I run the above code and watch the process explorer, a new Excel process is spawned when the script is started, and then closes, as expected.
However, if I insert this code after running the function, before the ExcelObject.Quit line:
For Each ArrayItem in testActionArray
IF ArrayItem(1) = "" Then
Wscript.Echo ArrayItem(0)
Else
Wscript.Echo ArrayItem(0) & " -> " & ArrayItem(1)
End If
Next
ExcelObject.Quit (STILL HERE)
then the spawned process does NOT quit, and the process list grows until Excel goes completely bananas.
I don't understand this; All the last bit of code does is loop through the ArrayList and print the contents. Why's not the process quitting?
EDIT: At seems that at least some of the Excel objects eventually disappear from the Process Explorer, but this takes about 20-30 minutes. And it's just a few of them - most are still there. At least my list at the moment has shrinked some, but there are still about 15 Excel processes running.
Also, suddenly this message appears:
File Now Available
'filename.xlsm ' is now available for editing. Choose Read-Write to open it for editing.
This line seems to help. It doesn't completely remove all extra processes, but the number grows to five, then goes back to two, and so on. So it works pretty well.
dim book: for each book in ExcelObject.Workbooks: book.saved = true: next

OLE Excel object manipulation causes run-time error '91'

I am maintaining an application that was written in Visual Basic 6.0 and makes use of the several OLE controls with Excel.Sheet.8 class objects. Several users are getting the following error when they reach a point in code that attempts to manipulate the excel objects.
Run-time error '91': Object variable or With block variable not set
Below are examples of the code that trigger this error. I believe that the issue happens at:
Set oExcel = oleXl.object
Here are the points in the code where it happens:
Private Sub Form_Load()
Dim i As Integer
Dim j As Integer
Dim sTempStringA As String
Dim sTempStringB As String
'Set up excel sheet
centerform Me
Set oOutGrid = oleXlOutput.object
...
Private Sub Form_Load()
centerform Me
Set oOtherFx = oleXlFx.object
...
Private Sub Form_Load()
Dim iRet As Integer
Dim i As Integer
On Error GoTo Err_Handler
centerform Me
Call InitArray
Me.Caption = "TJUJ | Version " & version & " | Enter Custom Fx"
Set oBook = oleExcel.object
...
Is there a specific situation or environment in which this error would be generated from this line of code OR a way that I can ensure the object will always be accessible at this point in the code?
The error only happens occasionally, and I can't reproduce it on my developer machine at all. I also do not have access to the machines that it is happening on, but it seems to be encountered when there is an instance of the EXCEL.EXE process running.
When you get runtime-error 91, you can bet there's an uninitialized object somewhere in the statement. In other words, you are trying to use the properties or methods of a variable/object with a value of Nothing.
In your examples, oleXl, oleXlFx, and oleExcel are probably Nothing. So when you refer to their .object property, you trigger the RTE.
Somewhere in your code these variables have to be initialized to something. Look for statements like Set oleXl = CreateObject("Excel.Application") or Set oleXl = New Excel.Application
One suggestion; when you find the statements that actually initialize those OLE objects, check to see how the error-handling is coded. If you see things like this:
On Error Resume Next
Set oleXl = CreateObject(...
add a test to make sure the object was instantiated
On Error Resume Next
Set oleXl = CreateObject(...
If oleXl Is Nothing Then
MsgBox "Hey, my object is Nothing!"
End If
Microsoft suggests that we can fix error 91 by creating a new registry key. To create a new key follow the steps below.
Click on the Windows Start menu
Type Regedit in the search box
Press Enter
Locate the following entry in the registry. HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Transaction Server
Now select the transaction server and right click on it
Select New and then choose Key
Name the key as Debug
Right click on the Debug key and choose New
Now select Key and name the key as RunWithoutContext
Ref: http://backspacetab.com/error-91/

Resources