Fast User Switching and Session Changes in WPF

While I haven’t been working on C#/WPF in awhile, I figured I’d go ahead and complete a draft that’s been sitting in my queue for awhile.

Let's switch. Your pink background makes my eyes bleed.

Let's switch. Your pink background makes my eyes bleed.

Fast user switching is a concept Microsoft first included with Windows XP that allows the OS to quickly switch to another user and back by keeping the first user’s applications running while the second uses the computer.

This sounds like a great idea, and for the most part, it is; however, it places a new responsibility on application developers, because the context in which an application is running could change at any time.

That is, user1 could be using app.exe, switch to user2, and user2 could use app.exe. Then you have two users using the same executable at the same time, and user1 expects that app.exe will retain his place in the app for when he returns.

Now, before you start shouting, “Anarchy!”, note that there are a variety of things you can do to address this responsibility.

You can just tell user2 that he can’t use the application until user1 closes it. This is a poor approach, but you can feel free to annoy your users with how lazy you are if you like.

Alternatively, you can set your application to begin listening for window events that signal a change in the computer’s session.

Just as we have before, we’ll be using our friend, WndProc, to capture messages that WPF doesn’t fire for us. The one we’re looking for this time is WM_WTSSESSION_CHANGE, which will notify the application that the Windows session has changed. In order to receive notifications for this event, we’ll have to register using the function WTSRegisterSessionNotification.

Let’s kick things off with some imports for the Win32 APIs we’ll be using and the constants associated with them (see the MSDN links in the previous paragraph).

    [DllImport("WtsApi32.dll")]
    private static extern bool WTSRegisterSessionNotification(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)]int dwFlags);
    [DllImport("WtsApi32.dll")]
    private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);
    [DllImport("kernel32.dll")]
    public static extern int WTSGetActiveConsoleSessionId();
 
    // dwFlags options for WTSRegisterSessionNotification
    const int NOTIFY_FOR_THIS_SESSION = 0;     // Only session notifications involving the session attached to by the window identified by the hWnd parameter value are to be received.
    const int NOTIFY_FOR_ALL_SESSIONS = 1;     // All session notifications are to be received.
 
    // session change message ID
    const int WM_WTSSESSION_CHANGE = 0x2b1;
 
    public enum WTSMessage
    {
        // WParam values that can be received:
        WTS_CONSOLE_CONNECT = 0x1, // A session was connected to the console terminal.
        WTS_CONSOLE_DISCONNECT = 0x2, // A session was disconnected from the console terminal.
        WTS_REMOTE_CONNECT = 0x3, // A session was connected to the remote terminal.
        WTS_REMOTE_DISCONNECT = 0x4, // A session was disconnected from the remote terminal.
        WTS_SESSION_LOGON = 0x5, // A user has logged on to the session.
        WTS_SESSION_LOGOFF = 0x6, // A user has logged off the session.
        WTS_SESSION_LOCK = 0x7, // A session has been locked.
        WTS_SESSION_UNLOCK = 0x8, // A session has been unlocked.
        WTS_SESSION_REMOTE_CONTROL = 0x9 // A session has changed its remote controlled status.
    }

The first thing we’ll need to do is register for notifications. You’ll probably want this near the start of your application, in case your user is fast and/or part of your QA. After registration is successful, we want to capture the initial session ID to use for comparisons later on.

    private int initialSessionId;
        if (!WTSRegisterSessionNotification((new WindowInteropHelper(this)).Handle, NOTIFY_FOR_THIS_SESSION))
        {
            // throw exception - registration has failed!
        }
        initialSessionId = Process.GetCurrentProcess().SessionId;

When our WndProc is executed, we can check the message to see if it corresponds to the WM_WTSSESSION_CHANGE we’ve defined (confused? should’ve clicked that link earlier…).

We once again get the active session ID for the event to allow us to compare against our initial value. After all, we may not want to do anything if the session ID hasn’t changed.

It’s also useful to check the wParamValue in order to understand the type of session change that has occurred. If a user logs out, we can perform auto-save functions or some clean up to get rid of unneeded resources as a preparation for the new user to log in. Alternatively, we can expand the use of this feature and show low-res images to make repaints faster if it’s a remote connection.

    protected override void WndProc(ref Message m)
    {
        int evtSessionID = WTSGetActiveConsoleSessionId();
 
        switch (m.Msg)
        {
            case WM_WTSSESSION_CHANGE:
                {
                    WTSMessage wParamValue = (WTSMessage)m.WParam.ToInt32();
                    Console.WriteLine("Session message " + wParamValue + " Active Session ID:" + evtSessionID + " Current Process Session ID:" + initialSessionId);
                    // do something useful
                }
                break;
        }
        base.WndProc(ref m);
    }

Lastly, let’s be a good neighbor and unregister our subscription for notifications using WTSUnRegisterSessionNotification. Be sure to do this before your window handle is destroyed, such as in your Window_Closing event.

    WTSUnRegisterSessionNotification((new WindowInteropHelper(this)).Handle);

All done! Now, let me know how you use it.

Sending Commands/Events to Another Application

In order to send a keystroke to another application, you can just utilize Win32’s PostMessage.

Windows is very event-driven.  Anytime anything changes within a window, an event is fired.  If you click your mouse button, an event is fired.  If you resize the window, an event is fired.  If you press a key, an event is fired.

Quiz time.  What happens when you move your mouse within a window?
If you guessed “The pointer moves”, you are technically correct but not getting what I’m trying to say.
If you guessed “A bobcat will charge out of my monitor and eat my childhood friend”, you’re way off.
If you guessed “An event is fired”, you’ve somehow retained the knowledge I bestowed upon you 1.2 seconds ago!

All pretty easy – but how is your window notified of these events?  Windows sends messages to the window with codes regarding the type of command that has occurred, along with any information related to that command.

PostMessage() essentially does the same thing.  Alternatively, you can use SendMessage (to post the message, then wait until the message is received), SendMessageTimeout (to do the same but timeout after a given time).  Here’s PostMessage’s syntax:

BOOL PostMessage(
    HWND hWnd,        // the handle of the window the message is to be posted to
    UINT Msg,            // the message code
    WPARAM wParam,  // message-specific information
    LPARAM lParam      // message-specific information
);

The window handle is how Windows references application windows.  If you’re using .NET, you can use the System.Diagnostics.Process.MainWindowHandle property with the application’s process.

The message code is of course specific to the type of message that you want.  For example:

  • If you want to close a window, use WM_COMMAND (0x0112), with wParam = WM_CLOSE (0xF060).
  • If you want to send keystrokes to a window, use a call to PostMessage with WM_KEYDOWN (0x100) and WM_KEYUP (0x101) as message codes with the VK key code as the WPARAM (for a list of VK key codes, check this page out).

For more Windows Messages, check out this random list of Windows Messages.

So, say we want to close Notepad if it is open.  Using C#, here’s a complete example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
 
namespace TestCmd
{
    class Program
    {
        // Import the library containing PostMessage
        [DllImport ("user32.dll")]
        public static extern bool PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
 
        public const int WM_COMMAND = 0x0112;	   // Code for Windows command
		public const int WM_CLOSE = 0xF060;		 // Command code for close window
 
        static void Main(string[] args)
        {
            try
            {
                IntPtr hWndNotepad = Process.GetProcessesByName("notepad")[0].MainWindowHandle;
                Console.WriteLine("Ready to tell Notepad to close.  Press enter to send the command.");
                Console.ReadLine();
                if (hWndNotepad != null)
                {
                    // Close Window
                    PostMessage(hWndNotepad, WM_COMMAND, WM_CLOSE, 0);
                }
                Console.WriteLine("Command sent.  Press enter to close.");
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine ("Notepad not running.  Press enter to close.");
                Console.Read();
            }
        }
    }
}

Take that, Notepad! Now to make a tray application to close iTunes everytime it starts.

Wait…

No.  Please, code responsibly.

–Andrew