I have to develop a program which runs on a local pc as a service an deliver couple of user status to a server. At the beginning I have to detect the user logon and logoff.
My idea was to use the ManagementEventWatcher
class and to query the Win32_LogonSession
to be notified if something changed.
My first test works well, here is the code part (This would executed as a thread from a service):
private readonly static WqlEventQuery qLgi = new WqlEventQuery("__InstanceCreationEvent", new TimeSpan(0, 0, 1), "TargetInstance ISA \"Win32_LogonSession\"");
public EventWatcherUser() {
}
public void DoWork() {
ManagementEventWatcher eLgiWatcher = new ManagementEventWatcher(EventWatcherUser.qLgi);
eLgiWatcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
eLgiWatcher.Start();
}
private void HandleEvent(object sender, EventArrivedEventArgs e)
{
ManagementBaseObject f = (ManagementBaseObject)e.NewEvent["TargetInstance"];
using (StreamWriter fs = new StreamWriter("C:\\status.log", true))
{
fs.WriteLine(f.Properties["LogonId"].Value);
}
}
But I have some understanding problems and I’m not sure if this is the common way to solve that task.
If I query Win32_LogonSession
I get several records which are
associated to the same user. For example I get this IDs 7580798 and
7580829 and if I query
ASSOCIATORS OF {Win32_LogonSession.LogonId=X} WHERE ResultClass=Win32_UserAccount
I get the same record for different IDs. (Win32_UserAccount.Domain="PC-Name",Name="User1")
Why are there several logon session with the same user? What is the common way to get the current signed in user? Or better how to get notified correctly by the login of a user?
I thought I could use the same way with __InstanceDeletionEvent
to
determine if a user is log off. But I guess if the event is raised, I
cant query Win32_UserAccount
for the username after that. I’m right?
I’m at the right direction or are there better ways? It would be awesome if you could help me!
Edit Is the WTSRegisterSessionNotification class the correct way? I don't know if it's possible, because in a service I haven't a window handler.
I use ServiceBase.OnSessionChange to catch the different user events and load the necessary information afterwards.
protected override void OnSessionChange(SessionChangeDescription desc)
{
var user = Session.Get(desc.SessionId);
}
To load the session information I use the WTS_INFO_CLASS. See my example below:
internal static class NativeMethods
{
public enum WTS_INFO_CLASS
{
WTSInitialProgram,
WTSApplicationName,
WTSWorkingDirectory,
WTSOEMId,
WTSSessionId,
WTSUserName,
WTSWinStationName,
WTSDomainName,
WTSConnectState,
WTSClientBuildNumber,
WTSClientName,
WTSClientDirectory,
WTSClientProductId,
WTSClientHardwareId,
WTSClientAddress,
WTSClientDisplay,
WTSClientProtocolType,
WTSIdleTime,
WTSLogonTime,
WTSIncomingBytes,
WTSOutgoingBytes,
WTSIncomingFrames,
WTSOutgoingFrames,
WTSClientInfo,
WTSSessionInfo
}
[DllImport("Kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
public static extern bool WTSQuerySessionInformation(IntPtr hServer, Int32 sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out Int32 pBytesReturned);
[DllImport("Wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr pointer);
}
public static class Status
{
public static Byte Online
{
get { return 0x0; }
}
public static Byte Offline
{
get { return 0x1; }
}
public static Byte SignedIn
{
get { return 0x2; }
}
public static Byte SignedOff
{
get { return 0x3; }
}
}
public static class Session
{
private static readonly Dictionary<Int32, User> User = new Dictionary<Int32, User>();
public static bool Add(Int32 sessionId)
{
IntPtr buffer;
int length;
var name = String.Empty;
var domain = String.Empty;
if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTS_INFO_CLASS.WTSUserName, out buffer, out length) && length > 1)
{
name = Marshal.PtrToStringAnsi(buffer);
NativeMethods.WTSFreeMemory(buffer);
if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTS_INFO_CLASS.WTSDomainName, out buffer, out length) && length > 1)
{
domain = Marshal.PtrToStringAnsi(buffer);
NativeMethods.WTSFreeMemory(buffer);
}
}
if (name == null || name.Length <= 0)
{
return false;
}
User.Add(sessionId, new User(name, domain));
return true;
}
public static bool Remove(Int32 sessionId)
{
return User.Remove(sessionId);
}
public static User Get(Int32 sessionId)
{
if (User.ContainsKey(sessionId))
{
return User[sessionId];
}
return Add(sessionId) ? Get(sessionId) : null;
}
public static UInt32 GetActiveConsoleSessionId()
{
return NativeMethods.WTSGetActiveConsoleSessionId();
}
}
public class AvailabilityChangedEventArgs : EventArgs
{
public bool Available { get; set; }
public AvailabilityChangedEventArgs(bool isAvailable)
{
Available = isAvailable;
}
}
public class User
{
private readonly String _name;
private readonly String _domain;
private readonly bool _isDomainUser;
private bool _signedIn;
public static EventHandler<AvailabilityChangedEventArgs> AvailabilityChanged;
public User(String name, String domain)
{
_name = name;
_domain = domain;
if (domain.Equals("EXAMPLE.COM"))
{
_isDomainUser = true;
}
else
{
_isDomainUser = false;
}
}
public String Name
{
get { return _name; }
}
public String Domain
{
get { return _domain; }
}
public bool IsDomainUser
{
get { return _isDomainUser; }
}
public bool IsSignedIn
{
get { return _signedIn; }
set
{
if (_signedIn == value) return;
_signedIn = value;
OnAvailabilityChanged(this, new AvailabilityChangedEventArgs(IsSignedIn));
}
}
protected void OnAvailabilityChanged(object sender, AvailabilityChangedEventArgs e)
{
if (AvailabilityChanged != null)
{
AvailabilityChanged(this, e);
}
}
}
The following code use the static AvailabilityChanged
event from User
, which gets fired as soon as the session state changes. The arg e
contains the specific user.
public Main()
{
User.AvailabilityChanged += UserAvailabilityChanged;
}
private static void UserAvailabilityChanged(object sender, AvailabilityChangedEventArgs e)
{
var user = sender as User;
if (user == null) return;
System.Diagnostics.Debug.WriteLine(user.IsSignedIn);
}