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;
}
}
}