Saturday, May 18, 2013

Place Outlook Reminders as top window using C#


Place Outlook Reminders as top window using C#

This program subscribes to WMI events and therefore must be run as an admin account.  It can be added to scheduled jobs so it runs automatically.

It is created as a console application and hides its console window to run in the background.
Basically it watches for whenever Outlook kicks off a new thread and if the window heading has Reminder in it, it forces the window to be on top of all others.

using System;
using System.Management;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace OutlookReminder
{
    class Program
    {
        ManagementEventWatcher startThreadWatch;

        static void Main(string[] args)
        {
            //create a processwatcher that will watch for outlook programs that will be getting opened.
            ProcessWatcher processWatcher = new ProcessWatcher("OUTLOOK.EXE");
            //create a list of threadWatchers for each outlook program that is currently open, however...there really should only be one open at a time.
            List<ThreadsWatcher> threadsWatcher = Process.GetProcessesByName("OUTLOOK").ToList().Select(x => new ThreadsWatcher(x.Id)).ToList();
            
            //Hide this console window and let it live in the background.
            WindowControl.ShowWindow(Process.GetCurrentProcess().MainWindowHandle, (int)WindowControl.WindowState.Hide);
            Console.WriteLine("Press ENTER to stop the Outlook reminders to be forced as the top most window.");
            Console.ReadLine();

            //Dispose of the process watcher that is watching for the outlook program to get opened.
            processWatcher.Dispose();
            //Dispose of all the threads that are not null and not disposed of yet.
            threadsWatcher.Where(x => x != null && !x.disposed).ToList().ForEach(x => x.Dispose());
        }
    }
    #region watchers
    public class ProcessWatcher : IDisposable
    {
        ManagementEventWatcher startProcessWatch;
        ManagementEventWatcher stopProcessWatch;
        ThreadsWatcher threadWatcher;
        public string processName { get; private set; }
        public bool disposed { get; private set; }
        public ProcessWatcher(string _processName)
        {
            processName = _processName;
            disposed = false;
            startProcessWatch = new ManagementEventWatcher(new WqlEventQuery(string.Format("Select * from Win32_ProcessStartTrace WHERE ProcessName='{0}'", processName)));
            startProcessWatch.EventArrived += new EventArrivedEventHandler(startProcessWatch_EventArrived);
            startProcessWatch.Start();
            stopProcessWatch = new ManagementEventWatcher(new WqlEventQuery(string.Format("Select * from Win32_ProcessStopTrace WHERE ProcessName='{0}'", processName)));
            stopProcessWatch.EventArrived += new EventArrivedEventHandler(stopProcessWatch_EventArrived);
            stopProcessWatch.Start();
        }
        #region disposeAndFinalize
        ~ProcessWatcher()
        {
            if (startProcessWatch != null)
            {
                startProcessWatch.Stop();
            }
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    startProcessWatch.Stop();
                    startProcessWatch.Dispose();
                    if (threadWatcher != null && !threadWatcher.disposed)
                    {
                        threadWatcher.Dispose();
                    }
                }
                disposed = true;
            }
        }
        #endregion disposeAndFinalize
        void stopProcessWatch_EventArrived(object sender, EventArrivedEventArgs e)
        {
            if (threadWatcher != null && !threadWatcher.disposed)
            {
                threadWatcher.Dispose();
            }
        }
        void startProcessWatch_EventArrived(object sender, EventArrivedEventArgs e)
        {
            int processID = 0;
            int.TryParse(e.NewEvent.Properties["ProcessID"].Value.ToString(), out processID);
            threadWatcher = new ThreadsWatcher(processID);
        }
    }
    public class ThreadsWatcher : IDisposable
    {
        ManagementEventWatcher startThreadWatch;
        public bool disposed { get; private set; }
        public int processID { get; private set; }
        #region constructors
        public ThreadsWatcher(int _processID)
        {
            disposed = false;
            processID = _processID;
            startThreadWatch = new ManagementEventWatcher(new WqlEventQuery("Select * from Win32_ThreadStartTrace WHERE ProcessID=" + processID));
            startThreadWatch.EventArrived += new EventArrivedEventHandler(startThreadWatch_EventArrived);
            startThreadWatch.Start();
        }
        #endregion constructors
        #region disposeAndFinalize
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    startThreadWatch.Stop();
                    startThreadWatch.Dispose();
                }
                disposed = true;
            }
        }
        ~ThreadsWatcher()
        {
            if (startThreadWatch != null)
            {
                startThreadWatch.Stop();
            }
        }
        #endregion disposeAndFinalize
        void startThreadWatch_EventArrived(object sender, EventArrivedEventArgs e)
        {
            int processID = 0;
            int.TryParse(e.NewEvent.Properties["ProcessID"].Value.ToString(), out processID);
            Process outlookProcess = Process.GetProcessById(processID);

            if (outlookProcess.MainWindowTitle.Contains("Reminder"))
            {
                //Place window as TopMost window.
                WindowControl.SetWindowPos(outlookProcess.MainWindowHandle, WindowControl.SpecialWindowHandles.TopMost, 0, 0, 0, 0, WindowControl.SetWindowPosFlags.TopMostFlag);

                //The next 2 commands are use to ensure that if the window is minimized it will still be maximized and displayed.
                //force the window as the foreground window.
                WindowControl.SetForegroundWindow(outlookProcess.MainWindowHandle);
                //Restore the window to be maximized 
                WindowControl.ShowWindow(outlookProcess.MainWindowHandle, (int)WindowControl.WindowState.Restore);
            }
            outlookProcess.Dispose();
        }
    }
    #endregion watchers
    static class WindowControl
    {
        #region WindowControlEnums
        //First few classes are defined to replace an enum however defining it this way we can load any type.
        public static class SpecialWindowHandles
        {
            public static IntPtr
            NoTopMost = new IntPtr(-2),
            TopMost = new IntPtr(-1),
            Top = new IntPtr(0),
            Bottom = new IntPtr(1);
        }
        public static class SetWindowPosFlags
        {
            public static readonly uint
            NoSize = 0x0001,
            NoMove = 0x0002,
            NoZOrder = 0x0004,
            NoRedraw = 0x0008,
            NoActivate = 0x0010,
            DrawFrame = 0x0020,
            FrameChanged = 0x0020,
            ShowsWindow = 0x0040,
            HideWindow = 0x0080,
            NoCopyBits = 0x0100,
            NoOwnerZOrder = 0x0200,
            NoRrPosition = 0x0200,
            NoSendChanging = 0x0400,
            DeferErase = 0x2000,
            AsyncWindowPos = 0x4000,
            TopMostFlag = NoMove | NoSize;
        }

        //Loaded this great enum values from http://www.pinvoke.net/default.aspx/user32.showwindow.  It loads the XML descriptions so you can read
        //the description in the code when you are using it.
        public enum WindowState :uint
        {
            Hide = 0,
            /// <summary>Activates and displays a window. If the window is minimized 
            /// or maximized, the system restores it to its original size and 
            /// position. An application should specify this flag when displaying 
            /// the window for the first time.</summary>
            /// <remarks>See SW_SHOWNORMAL</remarks>
            ShowNormal = 1,
            /// <summary>Activates the window and displays it as a minimized window.</summary>
            /// <remarks>See SW_SHOWMINIMIZED</remarks>
            ShowMinimized = 2,
            /// <summary>Activates the window and displays it as a maximized window.</summary>
            /// <remarks>See SW_SHOWMAXIMIZED</remarks>
            ShowMaximized = 3,
            /// <summary>Maximizes the specified window.</summary>
            /// <remarks>See SW_MAXIMIZE</remarks>
            Maximize = 3,
            /// <summary>Displays a window in its most recent size and position. 
            /// This value is similar to "ShowNormal", except the window is not 
            /// actived.</summary>
            /// <remarks>See SW_SHOWNOACTIVATE</remarks>
            ShowNormalNoActivate = 4,
            /// <summary>Activates the window and displays it in its current size 
            /// and position.</summary>
            /// <remarks>See SW_SHOW</remarks>
            Show = 5,
            /// <summary>Minimizes the specified window and activates the next 
            /// top-level window in the Z order.</summary>
            /// <remarks>See SW_MINIMIZE</remarks>
            Minimize = 6,
            /// <summary>Displays the window as a minimized window. This value is 
            /// similar to "ShowMinimized", except the window is not activated.</summary>
            /// <remarks>See SW_SHOWMINNOACTIVE</remarks>
            ShowMinNoActivate = 7,
            /// <summary>Displays the window in its current size and position. This 
            /// value is similar to "Show", except the window is not activated.</summary>
            /// <remarks>See SW_SHOWNA</remarks>
            ShowNoActivate = 8,
            /// <summary>Activates and displays the window. If the window is 
            /// minimized or maximized, the system restores it to its original size 
            /// and position. An application should specify this flag when restoring 
            /// a minimized window.</summary>
            /// <remarks>See SW_RESTORE</remarks>
            Restore = 9,
            /// <summary>Sets the show state based on the SW_ value specified in the 
            /// STARTUPINFO structure passed to the CreateProcess function by the 
            /// program that started the application.</summary>
            /// <remarks>See SW_SHOWDEFAULT</remarks>
            ShowDefault = 10,
            /// <summary>Windows 2000/XP: Minimizes a window, even if the thread 
            /// that owns the window is hung. This flag should only be used when 
            /// minimizing windows from a different thread.</summary>
            /// <remarks>See SW_FORCEMINIMIZE</remarks>
            ForceMinimized = 11
        }

        #endregion

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.I4)]
        public static extern int SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
    }
}