Hi all.

I'll outline my situation first of all:
I work at a university, and we have a problem where students tend to lock their PCs and, being the free-minded youths that they are, just walk away and leave them locked, thus requiring an admin to go and unlock it. This is especially a problem at exam time as we frequently run out of available PCs.

So the solution is thus - devise a method for logging the student out if the PC is left locked for more than 15 minutes. This was simple enough, using a service to catch the OnSessionChange event and setting up a timer to do the rest.

The problem now is how to notify the user about this. Ideally, a Yes/No box when they go to lock the PC, either via Win+L or Ctrl-Alt-Del->Lock Computer, would be great, but I'm not sure if that is possible. I experimented with creating an application and using Global Keyboard Hooks, but this was fairly useless when going through C-A-D and I had trouble getting it to recognise both the Windows key and L simultaneously.

Failing that, the option to display a warning message after the PC has been locked would suffice. I have been able to make a message box appear with no problems, but "the powers that be" want a proper full screen warning about what will happen if the PC is left locked. I have tried creating a form to appear, but it doesn't seem to want to play when the PC is locked. I've even tried getting the service to run an external application that would display the message, but to no avail.

So...yeah. What I am asking is:
1. Is what I want actually possible? Or is a little message box the best I'm going to get?
2. Is there a way to get an image to display on screen when the PC is locked?


Many thanks in advance for your help - I can provide some code that I have already tried if need be. :)

Paste the code of how you are having the service display the message box. I know there are options for services to display messages to the currently logged in profile.

I have an idea :)

protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            if (changeDescription.Reason == SessionChangeReason.SessionLock)
            {
                // Start timer before showing message box
                logoutTimer.Enabled = true;
                MessageBox.Show("If you leave this PC locked for more than 15 minutes, you will be automatically logged out.", "Locking PC", MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
            }
            else if (changeDescription.Reason == SessionChangeReason.SessionUnlock)
            {
                // reset timer
                logoutTimer.Enabled = false;
            }
        }

That is pretty much the bulk of the whole class - the rest is for initialising the timer and setting up the events.

Well here is my idea. You can show a MessageBox() that fills up the entire screen per the powers that be. You have to get an instance of graphics and figure out what font the MessageBox() will render in then you can create a string wide enough to fill the entire string. Likewise add enough line breaks to make it the height of the screen. This is what I started on but I think the Font i'm calling back is wrong .. I will have to look in to it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime;
using System.Drawing;
using System.Windows.Forms;

namespace daniweb
{
  public static class BigMessageBox
  {
    public static void WarnUser(Graphics g)
    {
      Font f = GetCaptionFont();
      StringBuilder sb = new StringBuilder();
      SizeF sz = g.MeasureString("!", f);
      string fullWidth = new string('!', Convert.ToInt32(((SystemInformation.PrimaryMonitorSize.Width-60) / sz.Width)));
      MessageBox.Show(fullWidth);
    }

    private static Font GetCaptionFont()
    {
      NONCLIENTMETRICS ncm = new NONCLIENTMETRICS();
      ncm.cbSize = Marshal.SizeOf(typeof(NONCLIENTMETRICS));
      try
      {
        bool result = SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
                                           ncm.cbSize, ref ncm, 0);

        if (result)
        {
          return Font.FromLogFont(ncm.lfCaptionFont);
        }
        else
        {
          int lastError = Marshal.GetLastWin32Error();
          return null;
        }
      }
      catch (Exception Ex)
      {
        //Obviously you want to do something here

        if (Ex == null) throw; //stop the compiler from complaining
        System.Diagnostics.Debugger.Break();
      }

      return null;
    }

    private const int SPI_GETNONCLIENTMETRICS = 41;
    private const int LF_FACESIZE = 32;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct LOGFONT
    {
      public int lfHeight;
      public int lfWidth;
      public int lfEscapement;
      public int lfOrientation;
      public int lfWeight;
      public byte lfItalic;
      public byte lfUnderline;
      public byte lfStrikeOut;
      public byte lfCharSet;
      public byte lfOutPrecision;
      public byte lfClipPrecision;
      public byte lfQuality;
      public byte lfPitchAndFamily;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
      public string lfFaceSize;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct NONCLIENTMETRICS
    {
      public int cbSize;
      public int iBorderWidth;
      public int iScrollWidth;
      public int iScrollHeight;
      public int iCaptionWidth;
      public int iCaptionHeight;
      public LOGFONT lfCaptionFont;
      public int iSmCaptionWidth;
      public int iSmCaptionHeight;
      public LOGFONT lfSmCaptionFont;
      public int iMenuWidth;
      public int iMenuHeight;
      public LOGFONT lfMenuFont;
      public LOGFONT lfStatusFont;
      public LOGFONT lfMessageFont;
    }

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool SystemParametersInfo(int uiAction,
       int uiParam, ref NONCLIENTMETRICS ncMetrics, int fWinIni);
  }
}

If your computers are all set up about the same I guess you could hard core it.

Eh actually now that I think about it the MessageBox will wrap text. You could just send a lot of spaces to get the width I think.

Is this an 'acceptable' solution or do you want something very user appealing? I think if you create a dialog you should be able to get it shown where the form wouldn't.

The more "user appealing" the better - will this solution make the font bigger? It really needs to be as obvious as possible!

Just tried a simple solution - padding the message box with line breaks and spaces. It works, after a fashion, but it does lead to a very large message box with small text... XD

Sorry for the triple post, but I can't edit a post after 30 minutes.

Is it possible to display a form in the same desktop environment that is used for MessageBox? I'm just wondering why every other form I try refused to be displayed until the PC is unlocked.

Here is the MessageBox class from the .NET framework. You can create your own message box but unfortunately the code sample I have for it is proprietary and I can't post it. Something in here makes it 'special' where it can show when a form can't.

public class MessageBox
{
    // Fields
    private const int HELP_BUTTON = 0x4000;
    [ThreadStatic]
    private static HelpInfo[] helpInfoTable;
    private const int IDABORT = 3;
    private const int IDCANCEL = 2;
    private const int IDIGNORE = 5;
    private const int IDNO = 7;
    private const int IDOK = 1;
    private const int IDRETRY = 4;
    private const int IDYES = 6;

    // Methods
    private MessageBox()
    {
    }

    private static void PopHelpInfo()
    {
        if (helpInfoTable != null)
        {
            if (helpInfoTable.Length == 1)
            {
                helpInfoTable = null;
            }
            else
            {
                int length = helpInfoTable.Length - 1;
                HelpInfo[] destinationArray = new HelpInfo[length];
                Array.Copy(helpInfoTable, destinationArray, length);
                helpInfoTable = destinationArray;
            }
        }
    }

    private static void PushHelpInfo(HelpInfo hpi)
    {
        HelpInfo[] infoArray;
        int length = 0;
        if (helpInfoTable == null)
        {
            infoArray = new HelpInfo[length + 1];
        }
        else
        {
            length = helpInfoTable.Length;
            infoArray = new HelpInfo[length + 1];
            Array.Copy(helpInfoTable, infoArray, length);
        }
        infoArray[length] = hpi;
        helpInfoTable = infoArray;
    }

    public static DialogResult Show(string text)
    {
        return ShowCore(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(string text, string caption)
    {
        return ShowCore(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(IWin32Window owner, string text)
    {
        return ShowCore(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons)
    {
        return ShowCore(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption)
    {
        return ShowCore(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
    {
        return ShowCore(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons)
    {
        return ShowCore(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
    {
        return ShowCore(null, text, caption, buttons, icon, defaultButton, 0, false);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
    {
        return ShowCore(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, 0, false);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options)
    {
        return ShowCore(null, text, caption, buttons, icon, defaultButton, options, false);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
    {
        return ShowCore(owner, text, caption, buttons, icon, defaultButton, 0, false);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, bool displayHelpButton)
    {
        return ShowCore(null, text, caption, buttons, icon, defaultButton, options, displayHelpButton);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath);
        return ShowCore(null, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options)
    {
        return ShowCore(owner, text, caption, buttons, icon, defaultButton, options, false);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, string keyword)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath, keyword);
        return ShowCore(null, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath, navigator);
        return ShowCore(null, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath);
        return ShowCore(owner, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator, object param)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath, navigator, param);
        return ShowCore(null, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, string keyword)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath, keyword);
        return ShowCore(owner, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath, navigator);
        return ShowCore(owner, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator, object param)
    {
        HelpInfo hpi = new HelpInfo(helpFilePath, navigator, param);
        return ShowCore(owner, text, caption, buttons, icon, defaultButton, options, hpi);
    }

    private static DialogResult ShowCore(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, bool showHelp)
    {
        DialogResult result;
        if (!ClientUtils.IsEnumValid(buttons, (int) buttons, 0, 5))
        {
            throw new InvalidEnumArgumentException("buttons", (int) buttons, typeof(MessageBoxButtons));
        }
        if (!WindowsFormsUtils.EnumValidator.IsEnumWithinShiftedRange(icon, 4, 0, 4))
        {
            throw new InvalidEnumArgumentException("icon", (int) icon, typeof(MessageBoxIcon));
        }
        if (!WindowsFormsUtils.EnumValidator.IsEnumWithinShiftedRange(defaultButton, 8, 0, 2))
        {
            throw new InvalidEnumArgumentException("defaultButton", (int) defaultButton, typeof(DialogResult));
        }
        if (!SystemInformation.UserInteractive && ((options & (MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly)) == 0))
        {
            throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
        }
        if ((owner != null) && ((options & (MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly)) != 0))
        {
            throw new ArgumentException(SR.GetString("CantShowMBServiceWithOwner"), "options");
        }
        if (showHelp && ((options & (MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly)) != 0))
        {
            throw new ArgumentException(SR.GetString("CantShowMBServiceWithHelp"), "options");
        }
        if ((options & ~(MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign)) != 0)
        {
            IntSecurity.UnmanagedCode.Demand();
        }
        IntSecurity.SafeSubWindows.Demand();
        int type = showHelp ? 0x4000 : 0;
        type |= ((buttons | ((MessageBoxButtons) ((int) icon))) | ((MessageBoxButtons) ((int) defaultButton))) | ((MessageBoxButtons) ((int) options));
        IntPtr zero = IntPtr.Zero;
        if (showHelp || ((options & (MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly)) == 0))
        {
            if (owner == null)
            {
                zero = UnsafeNativeMethods.GetActiveWindow();
            }
            else
            {
                zero = Control.GetSafeHandle(owner);
            }
        }
        IntPtr userCookie = IntPtr.Zero;
        if (Application.UseVisualStyles)
        {
            userCookie = UnsafeNativeMethods.ThemingScope.Activate();
        }
        Application.BeginModalMessageLoop();
        try
        {
            result = Win32ToDialogResult(SafeNativeMethods.MessageBox(new HandleRef(owner, zero), text, caption, type));
        }
        finally
        {
            Application.EndModalMessageLoop();
            UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
        }
        UnsafeNativeMethods.SendMessage(new HandleRef(owner, zero), 7, 0, 0);
        return result;
    }

    private static DialogResult ShowCore(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, HelpInfo hpi)
    {
        DialogResult none = DialogResult.None;
        try
        {
            PushHelpInfo(hpi);
            none = ShowCore(owner, text, caption, buttons, icon, defaultButton, options, true);
        }
        finally
        {
            PopHelpInfo();
        }
        return none;
    }

    private static DialogResult Win32ToDialogResult(int value)
    {
        switch (value)
        {
            case 1:
                return DialogResult.OK;

            case 2:
                return DialogResult.Cancel;

            case 3:
                return DialogResult.Abort;

            case 4:
                return DialogResult.Retry;

            case 5:
                return DialogResult.Ignore;

            case 6:
                return DialogResult.Yes;

            case 7:
                return DialogResult.No;
        }
        return DialogResult.No;
    }

    // Properties
    internal static HelpInfo HelpInfo
    {
        get
        {
            if ((helpInfoTable != null) && (helpInfoTable.Length > 0))
            {
                return helpInfoTable[helpInfoTable.Length - 1];
            }
            return null;
        }
    }
}

This part my be what you need:

if (showHelp || ((options & (MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly)) == 0))
        {
            if (owner == null)
            {
                zero = UnsafeNativeMethods.GetActiveWindow();
            }
            else
            {
                zero = Control.GetSafeHandle(owner);
            }
        }

Thanks for that - gives me a better idea of how the messagebox class actually works. :)

I've just tried to use the UnsafeNativeMethods, but it says that it is inaccessible due to its protection level; any idea why this might be? (I'm using Visual Studio 2008 by the way)

The major problem with message boxes is that they use the default Windows theme setup, so the only way to alter the font is through that setting, which of course is less than ideal as all message boxes would then appear with a large font!

>> inaccessible due to its protection level
If a method is marked "internal" so only code in the same assembly can call it, then you will receive that message when calling it from another assembly. The access modifiers on the .NET framework is what you're having an issue with.

Have you made any more progress on this issue?

Well, I've been asked to go down a different track now.

Instead of trying to intercept the command or deal with it afterwards, it has been suggested that the lock functionality is disabled via group policy. Unfortunately, this disables the user32.dll,LockWorkStation() method that I was using to lock the PC.

Are there any other methods for locking the computer apart from using the dll? I guess a faux lock screen could be created which traps all keyboard input, kind of like the current Windows locked screen, but this then raises the issue of managing the username and password for them to be able to log back in again.

No... at this point you're getting in to more detailed win32 than I really know. You could always use your LDAP server for authentication with their username and password.

That's a good idea - I might look into creating my own lock screen then.

Thank you very much for your help. :)

You're welcome

Please mark this thread as solved if you have found (enough of) an answer to your question and good luck!

This question has already been answered. Start a new discussion instead.