How to retrieve the username of the user who logged onto Windows from windows service

Recently, I have been tasked to write a .NET application that will prepare the operating system environment for a user who had logged on a Windows 7 machine. As a first step, the application should grab some settings from the network by providing the username of the user to a server application. The application will then prepare the operating system environment based on the settings received.

Writing the application as a windows service is a favorable option. This is because a windows service application can be configured to run automatically and will run before any user logs into the operating system. Furthermore, a windows service can run as Local System, which mean that it can execute most operating system facilities without facing permission issues.

Detecting user login event

Detecting user login event from a windows service is easy. By overriding the OnSessionChange method from the ServiceBase class, we can detect when a user log onto windows.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;

namespace MyWindowsService
{
    public partial class MyWindowsService : ServiceBase
    {
        public MyWindowsService()
        {
            // Generated by Visual Studios
            InitializeComponent();

            // This is needed for this window service to detect session changes
           base.CanHandleSessionChangeEvent = true;
        }

        protected override void OnStart(string[] args)
        {
            // Code to execute when windows service starts
        }

        protected override void OnStop()
        {
            // Code to execute when windows service stops
        }

        protected override void OnSessionChange(SessionChangeDescription
            changeDescription)
        {
            // Use a switch block to segment the actions to perform for
            // different session changes
            switch (changeDescription.Reason)
            {
                case SessionChangeReason.SessionLogon:
                    // Retrieve the username of the user who had just logged on
                    string username = Machine.getInstance().getUsername();
                break;

                case SessionChangeReason.SessionLogoff:
                    // Could perform any restoration of the operating system
                    // environment here.
                break;

                case SessionChangeReason.ConsoleLogon:
                    // Triggered whenever a user logs in after
                    // switching user accounts
                    // Triggered once by the windows service
                    // account whenever a user logs in
                break;
            }

            // Inform the base class of the change as well
            base.OnSessionChange(changeDescription);
        }
    }
}

As long as the windows service is started automatically, the OnSessionChange method block will be called whenever there are changes in the user session. This includes the case when a user logs on to windows, in which Machine.getInstance().getUsername() will be called. Note that besides log on and log off events, there are several other events defined in the SessionChangeReason enumeration that you can handle from your windows service.

Attempts made to retrieve the username

It makes sense for me to implement the Machine class as a Singleton. Machine.getInstance() will return me the Machine Singleton. The getUsername instance method will return the username of the user who is currently logged on to windows.

Environment.UserName and WindowsIdentity.GetCurrent().Name does not work

My first intuition to implementing the getUsername() method is to utilitise facilities from the .NET framework, namely the UserName property from the Environment class or the Name property of the WindowsIdentity class.  However, since the windows service is running as Local System , which belongs to a separate environment from the logged on user, the string "SYSTEM" was returned instead.

Looking for the right username with WMI

Windows Management Instrumentation (WMI) was my next hope. A couple of google searches brought me two methods for retrieving the right username.

Using the Win32_Process WMI class

The first method utilises the Win32_Process WMI class to inspect the owners of the explorer.exe process. Although I could get the list of usernames who are logged onto the machine, I felt that too much work is required to get the user who is currently active.

ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery
    ("select * from Win32_Process where name = 'explorer.exe'");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
ManagementObjectCollection returnCollection = searcher.Get();

foreach (ManagementObject mo in returnCollection)
{
    string[] argList = new string[] { string.Empty };
    int returnVal = Convert.ToInt32( mo.InvokeMethod("GetOwner", argList));
    if (returnVal == 0)
    {
        Console.WriteLine("User name:");
        Console.WriteLine(argList[0].ToString());
    }
}

Using the Win32_ComputerSystem WMI class

The second method utilises the Win32_ComputerSystem WMI class:

ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_ComputerSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach(ManagementObject mo in searcher.Get())
{
    Console.WriteLine(mo["UserName"].ToString());
}

This method is much better than the previous one in that there is only at most one instance of the Win32_ComputerSystem WMI class. Whenever a user logged onto windows, the Username attribute will contain the username of the user. In the case when there are no users in the windows system, there will be no instances of the Win32_ComputerSystem class. These properties make Win32_ComputerSystem my ideal choice in implementing the getUsername method for my Machine class.

Implementing the Machine class

public class Machine
{
    private static Object _classLocker = new Object();
    private static Machine _machine; 

    private Machine()
    {
    } // end private Machine()

    public static Machine getInstance()
    {
        if (_machine == null)
        {
            lock (_classLocker)
            {
                if (_machine == null)
                {
                    _machine = new Machine();
                }
            }
        }
        return _machine;
    } // end public static Machine getInstance()

    public String getUsername()
    {
        string username = null;
        try
        {
            // Define WMI scope to look for the Win32_ComputerSystem object
            ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
            ms.Connect();

            ObjectQuery query = new ObjectQuery
                    ("SELECT * FROM Win32_ComputerSystem");
            ManagementObjectSearcher searcher =
                    new ManagementObjectSearcher(ms, query);

            // This loop will only run at most once.
            foreach (ManagementObject mo in searcher.Get())
            {
                // Extract the username
                username = mo["UserName"].ToString();
            }
            // Remove the domain part from the username
            string[] usernameParts = username.Split('\\');
            // The username is contained in the last string portion.
            username = usernameParts[usernameParts.Length - 1];
        }
        catch (Exception)
        {
            // The system currently has no users who are logged on
            // Set the username to "SYSTEM" to denote that
            username = "SYSTEM";
        }
        return username;
    } // end String getUsername()
} // end class Machine

Some posts that may interest you

Advertisements

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people.

8 Comments

  • jehanzaib
    May 31, 2011 at 8:58 pm

    boss you done a great job 😀 live long

  • TWiStErRob
    March 22, 2012 at 10:47 pm

    What about the case when multiple users are logged on? For example both via remote desktop on Windows Servers.

    • March 23, 2012 at 7:52 am

      Hey, this is a good question.

      When I was still with my previous company, I had tried to get username of a user logged in via remote desktop.

      Although I was able to detect the login from windows service, I discovered that remote desktop accesses were not being recorded in the Win32_ComputerSystem WMI class. When I tried to read from the Win32_ComputerSystem instance, the username was empty.

      I guess the username attribute Win32_ComputerSystem will be updated only for users who had physically logged into the computer.

      Here are some options that I could think of to get the username of those remote users:

      Reading the Success Audit entries in the Security log, I think you would need to enable log audit and if there is a domain level setting on log audits, you will not be able to get it from your physical machine.
      Create a windows application and put it in the startup folder.

      You could make your windows service serve HTTP requests.

      When the application start, send the value of Environment.UserName that is retrieved via your windows application via HTTP get or post to your windows service. You could reference from this post to do HTTP get or post in C#.

      I am not sure how to detect switched logon from the windows application though, but I feel that it is possible.

      Get those details from your active domain server. I think it is possible to get all the user login details happening in the domain from the active directory. But you will need to own the active directory server.

      I am not able to try out these options though, since I no longer have all the equipment to try this out.

      Perhaps you can try them out or think of more ways to achieve your objective? If you managed to get a solution, feel free to post it here as a comment so that others can benefit from it.

  • William Byrd
    January 16, 2013 at 10:09 pm

    Your first method will return the usernames for those logged in via remote desktop. You get a list of users who own explorer.exe processes, that includes those logged in via remote desktop, but also those who connected via remote desktop, then just closed their RDC connection without using the Start>Disconnect route. Nothing perfect, but it works.

    • Clivant
      January 19, 2013 at 5:30 pm

      Hi William,

      Thank you for coming by and your feedback. 🙂

  • shashank
    January 30, 2013 at 8:05 pm

    this is not working when the wmi classes are not registered on a windows xp machine with 512 mb of ram

    • Clivant
      January 31, 2013 at 10:15 pm

      Hi Shashank,

      Yes, you will need WMI classes to be registered for this solution to work

  • Owe
    June 13, 2013 at 3:23 pm

    You could also use Cassia.dll – you will get a list of TS users, loop through them comparing sessionid will give you the username

Advertisements
Advertisements