This was quite possible in Windows 2000 / Xp. Unfortunately, in Windows 7 this is no longer feasible.
The trick was simple: you need to find the handle of the tray window:
static IntPtr GetSystemTrayHandle() { IntPtr hWndTray = FindWindow("Shell_TrayWnd", null); if (hWndTray != IntPtr.Zero) { hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null); if (hWndTray != IntPtr.Zero) { hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null); if (hWndTray != IntPtr.Zero) { hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null); return hWndTray; } } } return IntPtr.Zero; }
Since this window is ToolbarWindow32, you need to list all the buttons using WinAPI. The only problem is that all the structures used for this should be allocated to the address space of the target process, so you need to use something like this:
private static unsafe bool GetTBButton(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle) { // One page const int BUFFER_SIZE = 0x1000; byte[] localBuffer = new byte[BUFFER_SIZE]; UInt32 processId = 0; UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId); IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId); if (hProcess == IntPtr.Zero) { Debug.Assert(false); return false; } IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx( hProcess, IntPtr.Zero, new UIntPtr(BUFFER_SIZE), MemAllocationType.COMMIT, MemoryProtection.PAGE_READWRITE); if (ipRemoteBuffer == IntPtr.Zero) { Debug.Assert(false); return false; } // TBButton fixed (TBBUTTON* pTBButton = &tbButton) { IntPtr ipTBButton = new IntPtr(pTBButton); int b = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer); if (b == 0) { Debug.Assert(false); return false; } // this is fixed Int32 dwBytesRead = 0; IntPtr ipBytesRead = new IntPtr(&dwBytesRead); bool b2 = Kernel32.ReadProcessMemory( hProcess, ipRemoteBuffer, ipTBButton, new UIntPtr((uint)sizeof(TBBUTTON)), ipBytesRead); if (!b2) { Debug.Assert(false); return false; } } // button text fixed (byte* pLocalBuffer = localBuffer) { IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer); int chars = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tbButton.idCommand, ipRemoteBuffer); if (chars == -1) { Debug.Assert(false); return false; } // this is fixed Int32 dwBytesRead = 0; IntPtr ipBytesRead = new IntPtr(&dwBytesRead); bool b4 = Kernel32.ReadProcessMemory( hProcess, ipRemoteBuffer, ipLocalBuffer, new UIntPtr(BUFFER_SIZE), ipBytesRead); if (!b4) { Debug.Assert(false); return false; } text = Marshal.PtrToStringUni(ipLocalBuffer, chars); if (text == " ") text = String.Empty; }
Unfortunately, in Windows 7, tbButton.dwData is 0, so you cannot find any connection between NotifyIcon and the target process.