views:

68

answers:

3

Here’s the situation:

We have a 3rd party application that intermittently displays an error message. Usually the error occurs when no one is in the office, so we don’t know exactly what time the message box is popping up. We are working with our vendor to determine what the issue is, and they want to know exactly when the error is occurring so we can provide them with network conditions etc… at the time of the error. The error message is not logged anywhere by the application, so I have been tasked with somehow logging when it occurs.

I have C#.NET as my tool. So far the closest things I have found to a solution are FindWindow and EnumChildWindows or hooking into Windows messages. I am a recent college grad and just started my job so both of these routes will be fairly complicated for me. Before I invest a lot of time trying to learn what I need to try to make one of those methods work, I wanted to check here and see if there was a simpler solution.

Really all I need is to log when a message box appears and some identifying information about the message box. It isn’t necessary to log only messages from the application in question.

Thank you for your help. Please let me know if I need to clarify anything.

EDIT:

I've tried to code something with Hans' suggestions and references. I relied pretty heavily on his code sample. Right now I have a form that will accept a process name. Clicking a button will create an instance of the following class. I did some testing with Notepad, but it just cycled through the findMessageBox method even when I had a dialog box open. I tried using EnumChildWindows instead of EnumThreadWindows and the same thing happened. I confirmed that the program had the correct PID with Spy++. I'd appreciate any suggestions on what I need to fix.

EDIT:

It's working now. Thank you so much for your help. I was passing the wrong value from GetWindowProcessThreadId to EnumThreadWindows. I still will be working on it some as I can clean it up some and I don't want an open dialog logged continuously, but those are trivial things. I've posted the code I have now with the primary funcionality just in case anyone else has to do a similar thing in the future:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

namespace LogDialog
{
class DialogLogger
{
    TextWriter log = new StreamWriter(@"C:\ErrorLog\ErrorLog.txt");
    private Timer mTimer;
    private uint lpdwProcessId;
    private IntPtr mhwnd;
    uint threadID;

    //*************P/Invoke Declarations*************//

    private delegate bool EnumThreadWndProc(IntPtr hwnd, IntPtr lp);
    private delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lp);

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern bool EnumThreadWindows(int dwThreadId, EnumThreadWndProc callback, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern int GetClassName(IntPtr hwnd, StringBuilder buffer, int buflen);

    //Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window. 
    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint lpdwProcessId);

    //***********************************************//

    //Constructor; initiates timer
    public DialogLogger(string processName)
    {
        setWindowProcessID(processName);                    //set process ID
        mTimer = new Timer();                               //create timer for logging
        mTimer.Interval = 50;                               //set interval
        mTimer.Enabled = true;                              //enable
        mTimer.Tick += new EventHandler(findMessageBox);    //set event handler
    }

    private void setWindowProcessID(string processName)
    {
        mhwnd = Process.GetProcessesByName(processName)[0].MainWindowHandle;
        threadID = GetWindowThreadProcessId(mhwnd, out lpdwProcessId);
    }

    //Enumerates windows to find a message box
    private void findMessageBox(object sender, EventArgs e)
    {
        EnumThreadWndProc callback = new EnumThreadWndProc(checkDialogWindow);
        EnumThreadWindows((int)threadID, callback, IntPtr.Zero);
        GC.KeepAlive(callback);
    }

    //Checks if hwnd is a dialog
    private bool checkDialogWindow(IntPtr hwnd, IntPtr lp)
    {
        StringBuilder sb = new StringBuilder(260);
        GetClassName(hwnd, sb, sb.Capacity);

        if (sb.ToString() != "#32770") return true;

        log.WriteLine("Error Logged: {0}", DateTime.Now.ToLongTimeString());

        return false;
    }
}
}
A: 

How about this simple approach: create a screen capture every 2 minutes and just check the image files the next day?

klausbyskov
I talked to my manager about this possibility. It turns he wants the message logged even for when people are working on the system. Some people might just close the dialog before a screen capture can take place. He mentioned that he would probably try FindWindow with Win32 programming. I'm looking into that now but it is brand new to me.
Matt
+1  A: 

They are jerking you around, knowing full well that this is not that easy to implement. It is utterly trivial for them to add the logging, they should already have done so if their app is that sensitive to network conditions. The most positive view on this request is that they'll hope you'll figure this out by yourself from trying to make this work. Could happen. Talk to your supervisor and point this out.

Hans Passant
Probably so, but as this is one of my first projects I'm going to try for awhile before throwing in the towel.
Matt
Yes, they probably know that too. Well, you'll know a lot more about process interop when you're done, can't hurt too much. You need Process.MainWindowHandle, pinvoke GetWindowThreadProcessId, EnumThreadWindows, GetClassName to look for "#32770", the class name for a windows dialog like MessageBox. Relevant code snippet is here, although that's in-process: http://stackoverflow.com/questions/2659708/how-to-automate-response-to-msgbox/2660324#2660324
Hans Passant
@Hans Passant, very good advice! +1
klausbyskov
Thanks. I tried working with the example you gave but it isn't catching dialog boxes. I edited my post to include the code.
Matt
You are not using GetWindowThreadProcessId and EnumThreadWindows like I recommended. The message box is *not* a child window of the main window, EnumChildWindows cannot work.
Hans Passant
I reverted the code back to the way it was before I tried EnumChildWindows. How should I be using GetWindoeThreadProcessId? Do I need to modify the block with EnumThreadWindows?
Matt
GWTPI gets you the thread ID for the window. Which you need for ETW. Look this up in the MSDN library. And start a new thread if you have more questions about it.
Hans Passant
A: 

If you can attach a debugger, it should be rather trivial :)

leppie