3

I need to know when the user switches to the logon screen (as triggered by ctrl-alt-del) in order to circumvent a pesky bug in WPF. I want to work around this bug by reinitializing my GUI after returning from the logon screen. Currently it works, but I have to trigger it manually.

I have found SystemEvents.SessionSwitch, but unfortunately this is only triggered when logging off.

How can I detect when the logon screen is displayed by forming ctrl-alt-del?

Community
  • 1
  • 1
Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161

2 Answers2

4

The tricky thing is that this is not a session change, but rather just a desktop change. In particular, Ctrl+Alt+Del switches to a secured desktop associated with Winlogon.

I don't think you're really supposed to detect this kind of thing (that is, after all, the whole point of having a "secure desktop"), but you could probably do it using an Active Accessibility hook. Call the SetWinEventHook function to install an event hook for the EVENT_SYSTEM_DESKTOPSWITCH event and see what notifications you receive.

To get it going, you'll need to do the following:

  • Ensure that you're pumping a message loop on your client thread in order to receive event notifications. This shouldn't be a problem for a standard WPF application.
  • Make sure that you specify the WINEVENT_OUTOFCONTEXT flag, considering that you're working from managed code. You don't want the system to attempt to inject your DLL that contains the callback function into every process. Instead, this will cause the callback function to be called asynchronously from a queue; much safer from the land of managed code.
  • A little bit of P/Invoke magic. To get you started…

    const uint WINEVENT_OUTOFCONTEXT = 0x0;
    const uint EVENT_SYSTEM_DESKTOPSWITCH = 0x0020;
    
    [DllImport("user32.dll")]
    static extern IntPtr SetWinEventHook(uint eventMin,
                                         uint eventMax,
                                         IntPtr hmodWinEventProc,
                                         WinEventDelegate lpfnWinEventProc,
                                         uint idProcess,
                                         uint idThread,
                                         uint dwFlags);
    
    delegate void WinEventDelegate(IntPtr hWinEventHook,
                                   uint event,
                                   IntPtr hwnd,
                                   int idObject,
                                   int idChild,
                                   uint dwEventThread,
                                   uint dwmsEventTime);
    
    [DllImport("user32.dll")]
    static extern bool UnhookWinEvent(IntPtr hWinEventHook);
    
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Great, that seems like one possible solution. Currently I am exploring whether somehow tracking LogonUI.exe might work. If I hit a wall there I'll give this a try. – Steven Jeuris May 15 '12 at 00:07
  • I ended up with [a rather quick non P/Invoke implementation](http://stackoverflow.com/a/10607062/590790). Unless you see any problems with it I'll keep that one as the accepted answer. It's working fine for me so far. Still, this answer is much appreciated! – Steven Jeuris May 17 '12 at 01:06
  • 1
    @Steven No, nothing wrong with WMI. It's often an alternative to hooks, sometimes more convenient to code, especially in .NET languages where most of the infrastructure is already there (i.e., they've hidden the P/Invoke code inside of the BCL). The only concern that comes to mind is that it requires all target machines have the WMI service turned on, but if you're running an installer or something, you can ensure that they're configured appropriately at that time. – Cody Gray - on strike May 17 '12 at 10:44
3

The process which gets started to show the logon screen seems to be called LogonUI.exe.

Using the Windows Management Instrumentation (WMI) infrastructure you can listen for processes which start and shut down. You will need to reference the System.Management assembly.

var interval = new TimeSpan( 0, 0, 1 );
const string isWin32Process = "TargetInstance isa \"Win32_Process\"";

// Listen for started processes.
WqlEventQuery startQuery
    = new WqlEventQuery( "__InstanceCreationEvent", interval, isWin32Process );
_startWatcher = new ManagementEventWatcher( startQuery );
_startWatcher.Start();
_startWatcher.EventArrived += OnStartEventArrived;

// Listen for closed processes.
WqlEventQuery stopQuery
    = new WqlEventQuery( "__InstanceDeletionEvent", interval, isWin32Process );
_stopWatcher = new ManagementEventWatcher( stopQuery );
_stopWatcher.Start();
_stopWatcher.EventArrived += OnStopEventArrived;

Handling these events, you can get information about the started or closed process. This way you can verify when LogonUI.exe was shut down, and subsequently trigger the required actions.

void OnStopEventArrived( object sender, EventArrivedEventArgs e )
{
    var o = (ManagementBaseObject)e.NewEvent[ "TargetInstance" ];
    string name = (string)o[ "Name" ];

    ...
}
Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161