Link to home
Create AccountLog in
Avatar of sgaggerj
sgaggerjFlag for United States of America

asked on

Close (click 'ok' button) on a window based on the title or process

Hi all,

I need to create a vb.net app that runs in the background and checks when a certain window pops - either based on it's title (which is always the same) or based on it's process (the .exe) that gets run.
when it is found i need to a) get the text in the window that is shown and b) click the ok button.

how can i do this?
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

That sounds like it is possible, but I don't have the specific steps.

Possibility:

1) Process.GetProcessesByName
2) Get the MainWindowHandle
3) Enumerate the child windows for the main window to find the OK button
4) Simulate a mouse click on the OK button.

Bob
Adding to Bob's comments...

Using straight VB.Net you can't get "notified" of when the window pops up...you would have to POLL with a Timer.

Can you give us more details about the target app/window?  It's hard to give example code for these types of problems because the code has to be very SPECIFIC to that apps control hierarchy.

WinSpector (Free) is often helpful in determing the controls necessary to traverse to get a handle to a desired control:
http://www.windows-spy.com/
Avatar of sgaggerj

ASKER

it's actually the visual studio just-in-time debugger. (vsjitdebugger.exe)
i can't uninstall it because it will remove SQL server - and thus none of my apps will run.

i am running a couple of other programs that occasionally crash - and restart themselves when they do.  however the jit debugger window halts the re-start process.

the title of the window is
'Visual Studio Just-In-Time Debugger'
the exe that shows up in task manager is 'vsjitdebugger.exe'

i don't need to click the ok button - the process can be killed - but it's nicer to click ok.

i do however need to try and get the text that is displayed in the window, and log it.

polling with a timer wouldn't be bad - and it can poll every 5 minutes or so.



Idle_Mind - (this pertains to the timer)

I found your post here:
https://www.experts-exchange.com/questions/21202677/Task-Timer-system.html?sfQueryTermInfo=1+add+dynam+timer

and am implementing a timer/task system in another project, but my problem will be applicable also to this one.

using the following code, the timer pops only once.....  eventually the msgbox will be replaced with a form asking the user to dismiss or snooze.

what am i missing?

Private Sub Timer_Tick(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)
        Dim t As System.Timers.Timer = CType(sender, System.Timers.Timer)
        Dim al As Alarm = TimedEvents(t)
        If al.AlarmDate < Now Then
            MsgBox(al.Notes)
        End If

    End Sub
re the timer - i figured it out
the .AutoReset of the timer must be true

i've got a poll through the processes working:

Dim p() As System.Diagnostics.Process = System.Diagnostics.Process.GetProcesses()
For i As Integer = 0 To p.GetUpperBound(0)
            If LCase(p(i).ProcessName) = "vsjitdebugger.exe" Then
               
            End If
Next

just need to get the window text.
Instead of iterating thru ALL the processes, just get a list of those you are interested in: (Bob's #1 comment)

    Dim ps() As Process = Process.GetProcessesByName("vsjitdebugger") ' No Extension Here!
    If ps.Length > 0 Then
        Dim p As Process = ps(0)
        ' Do something with "p.MainWindowHandle()"...
    End If
I don't have the JIT Debugger on this machine...maybe I'll fire up the other one and play with it later today...   =)
Ok - here's what i've got so far...... looks like it's working for killing the process(es).

Now to get the text.....
i have no idea where to go from here....
Module Module1
 
    Private mThread As System.Threading.Thread
    Private sleep_val As Integer = 10000 '300000
    Sub Main()
restart:
        mThread = New System.Threading.Thread(AddressOf Go)
        mThread.Priority = Threading.ThreadPriority.Lowest
        mThread.IsBackground = True
        mThread.Start()
        System.Threading.Thread.Sleep(sleep_val)
        GoTo restart
    End Sub
 
    Private Sub Go()
        ' TODO: Add date/timestamp
        Console.WriteLine("Pop: ")
        Dim ps() As Process = Process.GetProcessesByName("vsjitdebugger")
        If ps.Length > 0 Then
            For i As Integer = 0 To ps.Length
                With ps(i)
                    ' Do something with "ps(i).MainWindowHandle()"...
                    .Kill()
                End With
            Next
        End If
    End Sub
End Module

Open in new window

SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
you're right - i intended to use

for i as integer = 0 to ps.length - 1
with ps(i)...


but i'm going to end up using for....each


i'll try this out and see what i can come up with - just gotta wait until one of the apps crashes!
Hehe  =)  

I would just write a small app that produces an exception for ya!
You know, it sounds like disabling the JITDebugger in the registry would be a better approach, or adding a global exception handler to deal with unhandled exceptions.

Bob
lol - that would work too!

Bob - i tried that.  Before it would give the opportunity to start the vs debugger - now it just comes up saying that something crashed w/ an ok button.
hah -

sub main()
kill("c:\dummy.txt")
end sub

worked perfectly!  

the class of the text is "Static" (in WinSpy there are actually two 'Static' entries - one w/o text and one w/ text (that i want to get)

and the class of the button is "Button"

changing my code to the following finds the process - but i can't seem to get the text - (working at this low level is new to me)

Tried with "Button" also and it doesn't work.

how would i make a 2nd call to FindWindowEx? call it with the current edit_handle value?



Module Module1
 
    Private Const WM_GETTEXT As Integer = &HD
    Private Const WM_GETTEXTLENGTH As Integer = &HE
 
    Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
            (ByVal hWnd1 As IntPtr, ByVal hWnd2 As IntPtr, ByVal lpsz1 As String, _
            ByVal lpsz2 As String) As Integer
 
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _
        ByVal wParam As Integer, ByVal lParam As Integer) As Integer
 
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _
        ByVal wParam As Integer, ByVal lParam As System.Text.StringBuilder) As Integer
 
    Private Function GetWindowText(ByVal hWnd As IntPtr) As String
        Dim textLength As Integer = SendMessage(hWnd, WM_GETTEXTLENGTH, 0, 0) + 1
        Dim sb As New System.Text.StringBuilder(textLength)
        If textLength > 0 Then
            Call SendMessage(hWnd, WM_GETTEXT, textLength, sb)
        End If
        Return sb.ToString
    End Function
 
    Private mThread As System.Threading.Thread
    Private sleep_val As Integer = 10000 '300000
    Sub Main()
restart:
        mThread = New System.Threading.Thread(AddressOf Go)
        mThread.Priority = Threading.ThreadPriority.Lowest
        mThread.IsBackground = True
        mThread.Start()
        System.Threading.Thread.Sleep(sleep_val)
        GoTo restart
 
    End Sub
 
    Private Sub Go()
        Console.WriteLine("Pop: ")
        Dim edit_handle As IntPtr
 
        For Each p As Process In Process.GetProcessesByName("vsjitdebugger")
            edit_handle = FindWindowEx(p.MainWindowHandle, IntPtr.Zero, "Static", "")
            If Not edit_handle.Equals(IntPtr.Zero) Then
                Dim strText As String = GetWindowText(edit_handle)
                Console.WriteLine(strText)
            End If
        Next
    End Sub
End Module

Open in new window

have been playing around with this a bit - and still can't get edit_handle to a value of anything other than intptr.zero when running it on anything other than notepad.

with the vsjitdebugger I've tried "Button", "Static", and no luck.


i've also tried it on an app i had written using what Winspector showed :"WindowsForms10.EDIT.app.0....." and no luck.


i feel like i'm missing something very simple

Can you show me a screen shot of the dialog that you are trying to auto-close?

Bob
If there is more than one window of the same ClassName then you can use the GetWindow() API with the GW_HWNDNEXT flag to find the next window at the same level that has the same name.  So you get a handle to the first window using FindWindowEx(), then pass that to GetWindow() with GW_HWNDNEXT to ge the next one.
Bob - here's the screenshots


Idle_Mind - i'll see if i can get that to work....


shot1.JPG
shot2.JPG
ASKER CERTIFIED SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
still not picking up anything
it's not getting past

If Not edit_handle.Equals(IntPtr.Zero) Then

edit_handle seems to be intptr.zero always

SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
Bob - I was able to get the text i needed with that - however it gets all windows that are available

i guess the easiest thing would be to change the code to the following, unless you guys have a better idea to stop looping through everything and go directly to the process


is p.kill acceptable or is there a better way to close the window (ie force a 'click' of the ok button)?
Private Sub Go()
 
        For Each p As Process In Process.GetProcessesByName(SelectedProcess)
            WriteLog("Crash found at " & Now.ToShortDateString & ", " & Now.ToShortTimeString)
            WriteLog(p.ProcessName & " id =  [" & p.Id & "]")
            Dim enumerator As New WindowsEnumerator()
 
            For Each top As ApiWindow In enumerator.GetTopLevelWindows()
                If top.MainWindowTitle = "Visual Studio Just-In-Time Debugger" Then
                    WriteLog("Top.MainWindowTitle: " & top.MainWindowTitle)
                    For Each child As ApiWindow In enumerator.GetChildWindows(top.hWnd)
                        If child.MainWindowTitle <> "" And child.MainWindowTitle <> "OK" Then
                            WriteLog("Child.MainWindowTitle: " & child.MainWindowTitle)
                        End If
                    Next child
                End If
            Next top
            p.kill
        Next
    End Sub

Open in new window

ok ... after doing a little more refining and testing, i've found out that a crash on a machine w/o VS installed will start a process called 'DW20.EXE' with the title being the app that caused the crash (with the option to send error report or not)

on my development machine DW20.exe starts and there's a button called 'Debug' that if clicked will run the 'vsjitdebugger.exe' process to allow the user to select the debugging machine.

on the machine that this app will eventually run on, the debugger is disabled, but the vsjitdebugger window (see screenshot above) pops up when a crash occurs. (actually on this machine a .net app brings up DW20 with an option to Debug or Close.  Close closes the DW20 window and leaves the app hung, Debug brings up the screenshot above.  The actual apps that i'm gong to be monitoring bring up the screenshot above right away)

so i need to look for DW20 and vsjitdebugger (just in case) - which isn't a problem. i can either loop through all processes, or get the process by name if it exists.

narrowing down the vsjitdebugger window is easy, because the MainWindowTitle is always "Visual Studio Just-In-Time Debugger"

the DW20, however is the name of the app that crashed.

i also noticed that top.hWnd is not the same as p.Handle or p.MainWindowHandle
p.MainWindowHandle always seems to be 0 (for both vsjitdebugger and DW20)

for DW20, i could get a list of all processes, and see if that list contains top.MainWindowTitle..... however that seems kind of hokey.... but it'd work

actually, now that I think of it - the DW20 window doesn't contain any useful information, so really I could kill that process without a problem, but that brings me back to my previous question:

is p.kill acceptable or is there a better/proper way to close the window (ie force a 'click' of the OK button)? (or in this case - "Don't Send"
ok... so i think i've got it for the vsjitdebugger part....
now i need to do the DW20....

the following code seems to work for vsjitdebugger,
however it doesnt work for the dw20 part

meaning that when i run it, the code enters the if lcase(p.processname)=dw_proc then,
writes the process name and id, and then runs through the loop but produces nothing

i've enumerated and printed out the windows - dw20 doesn't show, neither does the name of the app that crashed.

i've also tried running through each window and enumerating it's child windows and displaying the text.
nothing is shown for the DW20 window..... what am i missing?
Dim vsjit As String = "visual studio just-in-time debugger"
        Dim vs_proc As String = "vsjitdebugger"
 
        Dim dw20 As String = "exceptioncreater"
        Dim dw_proc As String = "dw20"
 
        For Each p As Process In Process.GetProcesses
            If LCase(p.ProcessName) = dw_proc Then
                WriteLog("Crash found at " & Now.ToShortDateString & ", " & Now.ToShortTimeString)
                WriteLog(p.ProcessName & " id =  [" & p.Id & "]")
                Dim enumerator As New WindowsEnumerator()
                For Each top As ApiWindow In enumerator.GetTopLevelWindows()
                    If LCase(top.MainWindowTitle) = dw20 Then
                        WriteLog("Window: " & top.MainWindowTitle)
                        For Each child As ApiWindow In enumerator.GetChildWindows(top.hWnd)
                            If child.MainWindowTitle.Contains("has encountered a problem") Then
                                WriteLog("Text: " & child.MainWindowTitle)
                                Try
                                    Dim button_handle As IntPtr = FindWindowEx(top.hWnd, 0, "Button", "Don't Send")
                                    SendMessage(button_handle, BM_CLICK, 0, 0)
                                Catch ex As Exception
                                    WriteLog("Error killing process: " & ex.ToString)
                                End Try
                            End If
                        Next child
                    End If
                Next top
 
            ElseIf LCase(p.ProcessName) = vs_proc Then
                WriteLog("Crash found at " & Now.ToShortDateString & ", " & Now.ToShortTimeString)
                WriteLog(p.ProcessName & " id =  [" & p.Id & "]")
                Dim enumerator As New WindowsEnumerator()
                For Each top As ApiWindow In enumerator.GetTopLevelWindows()
                    If LCase(top.MainWindowTitle) = vsjit Then
                        WriteLog("Window: " & top.MainWindowTitle)
                        For Each child As ApiWindow In enumerator.GetChildWindows(top.hWnd)
                            If child.MainWindowTitle <> "" And child.MainWindowTitle <> "OK" Then
                                WriteLog("Text: " & child.MainWindowTitle)
                                Try
                                    Dim button_handle As IntPtr = FindWindowEx(top.hWnd, 0, "Button", "OK")
                                    SendMessage(button_handle, BM_CLICK, 0, 0)
                                Catch ex As Exception
                                    WriteLog("Error killing process: " & ex.ToString)
                                End Try
                            End If
                        Next child
                    End If
                Next top
            End If
        Next

Open in new window

Maybe you are looking for a ghost in the machine.  Maybe that is not a real window that you can enumerate.

Bob
hmmmm - so any idea how would i go about emulating a 'click' on that window then?
I got it - however it's a little more 'hard coded' than i wanted

the following code will click the "OK" button if the vsjitdebugger process is found
and will click the "Don't Send" button if the DW20 process is found *only* if the app that crashed was named 'zone'

i'd like it to be able to click the 'Don't Send' button regardless of what the app's title is.... i guess looping through all the windows and their child's to see if it's present is possible

any other ideas?
        Dim vsjit_title As String = "visual studio just-in-time debugger"
        Dim vsjit_process As String = "vsjitdebugger"
 
        Dim dw20_title As String = "zone"
        Dim dw20_process As String = "dw20"
        Dim button_title As String = "&Don't Send"
 
 
        For Each p As Process In Process.GetProcesses
            If LCase(p.ProcessName) = dw20_process Then
                WriteLog("Crash found at " & Now.ToShortDateString & ", " & Now.ToShortTimeString)
                WriteLog(p.ProcessName & " id =  [" & p.Id & "]")
                Dim enumerator As New WindowsEnumerator()
                For Each c As ApiWindow In enumerator.GetChildWindows(p.MainWindowHandle)
                    If LCase(c.MainWindowTitle) = dw20_title Then
                        For Each child As ApiWindow In enumerator.GetChildWindows(c.hWnd)
                            If child.MainWindowTitle = button_title Then
                                Dim button_handle As IntPtr = FindWindowEx(c.hWnd, 0, "Button", button_title)
                                SendMessage(button_handle, BM_CLICK, 0, 0)
                            End If
 
                        Next
                    End If
                Next
            ElseIf LCase(p.ProcessName) = vsjit_process Then
                WriteLog("Crash found at " & Now.ToShortDateString & ", " & Now.ToShortTimeString)
                WriteLog(p.ProcessName & " id =  [" & p.Id & "]")
                Dim enumerator As New WindowsEnumerator()
                For Each top As ApiWindow In enumerator.GetTopLevelWindows()
                    If LCase(top.MainWindowTitle) = vsjit_title Then
                        WriteLog("Window: " & top.MainWindowTitle)
                        For Each child As ApiWindow In enumerator.GetChildWindows(top.hWnd)
                            If child.MainWindowTitle <> "" And child.MainWindowTitle <> "OK" Then
                                WriteLog("Text: " & child.MainWindowTitle)
                                Try
                                    Dim button_handle As IntPtr = FindWindowEx(top.hWnd, 0, "Button", "OK")
                                    SendMessage(button_handle, BM_CLICK, 0, 0)
                                Catch ex As Exception
                                    WriteLog("Error killing process: " & ex.ToString)
                                End Try
                            End If
                        Next child
                    End If
                Next top
            End If
        Next

Open in new window

No, I don't have any other ideas.  It is funny, because I don't think that I would have ever gone down this road if I hadn't even read this question.  I would have addressed the JIT compiler window from a different perspective.

Bob
how would you have handled it?
I said "I don't think".  I believe that I would have disabled the JIT debugger in the registry.

Bob
ok - assuming I had done that instead, I would still be left with dealing with the Windows Error Reporting (DW20.exe) right?

True, but not having to deal with auto-closing a dialog box.

Bob
Actually i'd have to auto close that box too - so, in one way or another i'd be looking to auto close some dialog box, because as far as I can tell - the Windows Error Reporting holds the crashed process too until the user clicks on Debug (if there's a debugger installed), Send Error Report, or Don't Send.

Anyhoo... Thanks for the enumeration help!  Unless IdleMind has any final thoughts/ideas, i'll be wrapping this one up!

No further ideas...sometimes it's just harder to navigate directly to a specific window since you need to know the correct parent process/window and then the correct hierarchy etc...which can be difficult for us when we can't play with it in front of us.  ;)
Yea that I understand, though like you said - creating an app that throws an exception was the way I ended up testing it as it was easier than waiting for an app to crash!

Thanks again guys!
Actually Bob's enumeration & usage should have been the accepted solutions w/ Idle_Mind's as the assisted... If any mod's are watching, can you swap them?

Thanks,

Cheers!

J