views:

234

answers:

2

I'm writing a Windows Service using C# .NET 2005. How can I determine who the currently logged-on user is (if any)? Also is there a way to be notified when a user logs on?

Alternatively, is there a way to know who has recently used the machine?

I need to know the currently logged on user so I can cache some data for that user. Operating in a corporate environment there are thousands of potential users but it only makes sense to cache data for someone who uses that machine.

UPDATE:

This solution works well. Also see this pinvoke.net example which uses the extended structure to also retrieve domain name.

In combination with this I'm using the SystemEvents class to be notified when a user logs on to the machine. See example 2 here for a good example - note that you need to use a hidden form from a service in order to be able to use SystemEvents from a service.

+2  A: 

You can use P/Invoke to call NetWkstaUserEnum, which will enumerate the currently logged on users. Keep in mind that there might be more than one user in case there are terminal server sessions, and that not all users returned is a "real" user. As the documentation states:

"This list includes interactive, service and batch logons."

Here is a complete working code example in C# on how to call NetWkstaUserEnum:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace EnumerateUsers
{
    class Program
    {
        static void Main(string[] args)
        {
            var ue = new UserEnumerator();
            foreach(string userName in ue.GetLoggedOnUsers(null))
            {
                Console.WriteLine(userName);
            }
        }

    }

    class UserEnumerator
    {
        public IEnumerable<string> GetLoggedOnUsers(string host)
        {
            int entriesRead, totalEntries, resumeHandle = 0;
            IntPtr pBuffer = IntPtr.Zero;
            try
            {
                int result = NetWkstaUserEnum(host, 0, out pBuffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, ref resumeHandle);
                if (result != NERR_Success)  
                    throw new ApplicationException(String.Format("Failed to enumerate users, error code {0}", result));

                return GetUsersFromStruct(pBuffer, entriesRead).ToList();
            }
            finally
            {
                if (pBuffer != IntPtr.Zero)
                    NetApiBufferFree(pBuffer);
                }

        }

        private IEnumerable<string> GetUsersFromStruct(IntPtr pBuffer, int count)
        {
            for (int i = 0; i < count; i++)
            {
                var user = (WKSTA_USER_INFO_0)Marshal.PtrToStructure(pBuffer, typeof(WKSTA_USER_INFO_0));
                yield return user.username;
                pBuffer = IntPtr.Add(pBuffer, user.username.Length * 2);                
            }
        }
        [DllImport("netapi32.dll")]
        private static extern int NetWkstaUserEnum(string host, int level, out IntPtr pBuffer, int prefMaxLength, out int entriesRead,
                                     out int totalEntries, ref int resumeHandle);

        [DllImport("netapi32.dll")]
        private static extern int NetApiBufferFree(IntPtr buffer);

        private const int MAX_PREFERRED_LENGTH = -1;

        private const int NERR_Success = 0;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct WKSTA_USER_INFO_0
    { 
        [MarshalAs(UnmanagedType.LPTStr)]
        internal string username;
    }
}
driis
Thanks, this is great. I noticed that it can return the same user twice so I'd add a de-dup step if unique values are desired.
Rory
A: 

As you know, there may not be a currently logged on user within a Windows service. Why not not add a small utility program to the startup logon process on the machine, that will run whenever someone logs on, that will execute a method that calls the data caching functionality in the service.. That way that utility will have access to the logged on users' windows principle Identity.

Charles Bretana
While that's probably simpler to build, it means there's more deployment and runtime dependencies to the solution and therefore more to go wrong: what if someone changes the startup logon processes so the utility is disabled, for instance. If there's a standard programmatic solution that'd be better.
Rory
@Rory, I was guessing this is a server, where such concerns are less of an issue.... i.e., except for rare cases, the only people logging on are logging on remotely using Remote desktop.. Also, by putting the functionality in the logon process, means you don't have to deal with multiple concurrent logs on. But either approach will work...
Charles Bretana