Alternative way to check if CTRL+ALT+DEL screen is visible


Currently I'm using the below code to test if the CTRL+ALT+DEL screen is visible and it is working as expected. The problem is that polling this info consumes CPU and I'm looking for an event based option. Does anyone know another way to detect if this screen is visible?

I only need to know when this screen is closed. I don't really care when it opens. Just that it had been open and is now closed.

To be honest, I found this code and I'm not exactly sure how it is specific, if at all, to the screen I'm referring to. It appears to look for any process creation and deletion events. What this means is that this screen must be opening a new process. Knowing that process name would be helpful too.

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

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

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

What is the name of this screen? And how do I detect this type of window? It seems like the same type as the login window.

enter image description here


Answers:


When you press CTRL+ALT+DEL, Windows switches to a another special virtual desktopa that hosts the winlogon process that is responsible for user login/logoff/lock etc. actions. By using the WinAPI function SetWinEventHook with the EVENT_SYSTEM_DESKTOPSWITCH argument you can set up a callback function that is called whenever such a desktop switch occurs:

//Store the callback in a variable so that it is not GC'd
private static readonly WinEventDelegate callback = EventCallback;
static void StartListeningForDesktopSwitch()
{
    SetWinEventHook(EVENT_SYSTEM_DESKTOPSWITCH, EVENT_SYSTEM_DESKTOPSWITCH,
        IntPtr.Zero, callback, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD);
}

static void EventCallback(IntPtr hWinEventHook, uint eventType,
       IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
    Console.WriteLine("Desktop switched");
}

Note: If you want to use this in a console application, you have to add a message loop by adding a hidden Form:

static void Main(string[] args)
{        
    StartListeningForDesktopSwitch(); 

    // Run message loop
    Application.Run(new HiddenForm());
}

private class HiddenForm : Form
{
    public HiddenForm()
    {
        this.FormBorderStyle = FormBorderStyle.None;
        this.WindowState = FormWindowState.Minimized;
        this.ShowInTaskbar = false;
    }
}

delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
    IntPtr hwnd, int idObject, int idChild, uint dwEventThread,
    uint dwmsEventTime);

[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
    hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
    uint idThread, uint dwFlags);

const uint WINEVENT_OUTOFCONTEXT = 0x0000;
const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
const uint EVENT_SYSTEM_DESKTOPSWITCH = 0x0020;

Further: The desktop switch also occurs when the user pressed Win+L or a UAC window pops up. Thus, we need a way to detect these other cases. The UAC case is rather trivial, it is enough to check if the process consent.exe is running during the callback function:

var processes = Process.GetProcessesByName("consent");
if (processes.Length == 0)
    Console.WriteLine("This is not a UAC prompt");

The other case, unfortunately, is a bit more complicated. I have only managed to detect wheter a user returns from a lock screen, but not whether they enter it (as you said, this is not relevant for you, but I wanted to mention it anyway).

Detecting whether the session is locked can be done by listening for the SystemEvents.SessionSwitch event in our HiddenForm. The SessionSwitchEventArgs.Reason property is set to SessionSwitchReason.SessionLock if this is a lock event, and to SessionSwitchReason.SessionUnlock if the user unlocks. We only can tell whether a desktop switch was not to the lock screen desktop when we are switching back to the default desktop since the switch desktop event callbacks are called before a session lock and after a session unlock. This leads to the following code for a sample console application:

private static readonly WinEventDelegate callback = EventCallback;
static void Main(string[] args)
{
    SetWinEventHook(EVENT_SYSTEM_DESKTOPSWITCH,
        EVENT_SYSTEM_DESKTOPSWITCH, IntPtr.Zero, callback, 0, 0,
        WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD);

    Application.Run(new HiddenForm());
}

private class HiddenForm : Form
{
    public HiddenForm()
    {
        this.FormBorderStyle = FormBorderStyle.None;
        this.WindowState = FormWindowState.Minimized;
        this.ShowInTaskbar = false;
        SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
    }

    private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
    {
        if (e.Reason == SessionSwitchReason.SessionUnlock)
            wasUnlocked = true;
    }
}

static bool wasUnlocked = false;
static bool wasOpened = false;

static void EventCallback(IntPtr hWinEventHook, uint eventType,
    IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
    // Check if UAC dialog is being shown
    var processes = Process.GetProcessesByName("consent");
    if (processes.Length == 0)
    {
        if (wasOpened)
        {
            if (!wasUnlocked)
                Console.WriteLine("Exited from CTRL+ALT+DEL");
            wasUnlocked = false;
            wasOpened = false;
        }
        else
            wasOpened = true;
    }
}

delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
    IntPtr hwnd, int idObject, int idChild, uint dwEventThread,
    uint dwmsEventTime);

[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
    hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
    uint idThread, uint dwFlags);

const uint WINEVENT_OUTOFCONTEXT = 0x0000;
const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
const uint EVENT_SYSTEM_DESKTOPSWITCH = 0x0020;

a This type of virtual desktop has nothing to do with the newly introduced "virtual desktop" feature in Windows 10