And*_*ter 21 c# authentication events logoff
我必须开发一个程序,它在本地PC上作为服务运行,为服务器提供几个用户状态.一开始我必须检测用户登录和注销.
我的想法是使用ManagementEventWatcher该类并查询Win32_LogonSession是否有更改的通知.
我的第一个测试运行良好,这是代码部分(这将作为服务的线程执行):
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);
}
}
Run Code Online (Sandbox Code Playgroud)
但我有一些理解问题,我不确定这是否是解决该任务的常用方法.
如果我查询Win32_LogonSession我得到几个与同一用户相关联的记录.例如,我得到这个ID 7580798和7580829,如果我查询
ASSOCIATORS OF {Win32_LogonSession.LogonId = X} WHERE ResultClass = Win32_UserAccount
我获得了不同ID的相同记录.(Win32_UserAccount.Domain = "PC-名称",名称= "用户1")
为什么有多个与同一用户的登录会话?获取当前用户签名的常用方法是什么?或者更好的方法是如何通过用户登录正确收到通知?
我以为我可以使用相同的方式__InstanceDeletionEvent来确定用户是否注销.但我想如果事件被提出,我就无法Win32_UserAccount在那之后查询用户名.我是正确的?
我是在正确的方向还是有更好的方法?如果你可以帮助我,那真是太棒了!
编辑 WTSRegisterSessionNotification类是否正确?我不知道是否可能,因为在服务中我没有窗口处理程序.
Lor*_*tté 18
由于您使用的是服务,因此可以直接获取会话更改事件.
您可以注册自己接收SERVICE_CONTROL_SESSIONCHANGE活动.特别是,您需要查找原因WTS_SESSION_LOGON和WTS_SESSION_LOGOFF原因.
有关MSDN文档的详细信息和链接,请查看我昨天写的这个答案.
在C#中它更容易,因为ServiceBase已经包装了服务控制例程并将事件公开为可覆盖的OnSessionChange方法.请参阅ServiceBase的MSDN文档,并且不要忘记将该CanHandleSessionChangeEvent属性设置为true以启用此方法的执行.
当框架调用您的OnSessionChange覆盖时,您得到的是SessionChangeDescription结构,其中包含原因(注销,登录,...)和可用于获取信息的会话ID,例如,用户登录/注销(请参阅链接到我的热门答案了解详情)
编辑:示例代码
public class SimpleService : ServiceBase {
...
public SimpleService()
{
CanPauseAndContinue = true;
CanHandleSessionChangeEvent = true;
ServiceName = "SimpleService";
}
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
EventLog.WriteEntry("SimpleService.OnSessionChange", DateTime.Now.ToLongTimeString() +
" - Session change notice received: " +
changeDescription.Reason.ToString() + " Session ID: " +
changeDescription.SessionId.ToString());
switch (changeDescription.Reason)
{
case SessionChangeReason.SessionLogon:
EventLog.WriteEntry("SimpleService.OnSessionChange: Logon");
break;
case SessionChangeReason.SessionLogoff:
EventLog.WriteEntry("SimpleService.OnSessionChange Logoff");
break;
...
}
Run Code Online (Sandbox Code Playgroud)
Sim*_*ier 15
您可以使用作为Windows一部分的系统事件通知服务技术.它具有ISensLogon2接口,可提供登录/注销事件(以及其他事件,如远程会话连接).
这是一段代码(示例控制台应用程序),演示了如何执行此操作.您可以使用来自另一台计算机的远程桌面会话对其进行测试,例如,这将触发SessionDisconnect,SessionReconnect事件.
此代码应支持从XP到Windows 8的所有Windows版本.
添加对名为COM + 1.0 Admin Type Library(即COMAdmin)的COM组件的引用.
注意务必将Embed Interop Types设置为'False',否则您将收到以下错误:"无法嵌入Interop类型'COMAdminCatalogClass'.请改用相应的接口."
与互联网上有关在.NET中使用此技术的其他文章相反,它不引用Sens.dll,因为它在Windows 8上似乎不存在(我不知道为什么).然而,该技术似乎得到支持,并且SENS服务确实安装并在Windows 8上正常运行,因此您只需手动声明接口和guids(如本示例中所示),或引用在早期版本的Windows上创建的互操作程序集(它应该工作正常,因为guids和各种接口没有改变).
class Program
{
static SensEvents SensEvents { get; set; }
static void Main(string[] args)
{
SensEvents = new SensEvents();
SensEvents.LogonEvent += OnSensLogonEvent;
Console.WriteLine("Waiting for events. Press [ENTER] to stop.");
Console.ReadLine();
}
static void OnSensLogonEvent(object sender, SensLogonEventArgs e)
{
Console.WriteLine("Type:" + e.Type + ", UserName:" + e.UserName + ", SessionId:" + e.SessionId);
}
}
public sealed class SensEvents
{
private static readonly Guid SENSGUID_EVENTCLASS_LOGON2 = new Guid("d5978650-5b9f-11d1-8dd2-00aa004abd5e");
private Sink _sink;
public event EventHandler<SensLogonEventArgs> LogonEvent;
public SensEvents()
{
_sink = new Sink(this);
COMAdminCatalogClass catalog = new COMAdminCatalogClass(); // need a reference to COMAdmin
// we just need a transient subscription, for the lifetime of our application
ICatalogCollection subscriptions = (ICatalogCollection)catalog.GetCollection("TransientSubscriptions");
ICatalogObject subscription = (ICatalogObject)subscriptions.Add();
subscription.set_Value("EventCLSID", SENSGUID_EVENTCLASS_LOGON2.ToString("B"));
subscription.set_Value("SubscriberInterface", _sink);
// NOTE: we don't specify a method name, so all methods may be called
subscriptions.SaveChanges();
}
private void OnLogonEvent(SensLogonEventType type, string bstrUserName, uint dwSessionId)
{
EventHandler<SensLogonEventArgs> handler = LogonEvent;
if (handler != null)
{
handler(this, new SensLogonEventArgs(type, bstrUserName, dwSessionId));
}
}
private class Sink : ISensLogon2
{
private SensEvents _events;
public Sink(SensEvents events)
{
_events = events;
}
public void Logon(string bstrUserName, uint dwSessionId)
{
_events.OnLogonEvent(SensLogonEventType.Logon, bstrUserName, dwSessionId);
}
public void Logoff(string bstrUserName, uint dwSessionId)
{
_events.OnLogonEvent(SensLogonEventType.Logoff, bstrUserName, dwSessionId);
}
public void SessionDisconnect(string bstrUserName, uint dwSessionId)
{
_events.OnLogonEvent(SensLogonEventType.SessionDisconnect, bstrUserName, dwSessionId);
}
public void SessionReconnect(string bstrUserName, uint dwSessionId)
{
_events.OnLogonEvent(SensLogonEventType.SessionReconnect, bstrUserName, dwSessionId);
}
public void PostShell(string bstrUserName, uint dwSessionId)
{
_events.OnLogonEvent(SensLogonEventType.PostShell, bstrUserName, dwSessionId);
}
}
[ComImport, Guid("D597BAB4-5B9F-11D1-8DD2-00AA004ABD5E")]
private interface ISensLogon2
{
void Logon([MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
void Logoff([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
void SessionDisconnect([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
void SessionReconnect([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
void PostShell([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
}
}
public class SensLogonEventArgs : EventArgs
{
public SensLogonEventArgs(SensLogonEventType type, string userName, uint sessionId)
{
Type = type;
UserName = userName;
SessionId = sessionId;
}
public string UserName { get; private set; }
public uint SessionId { get; private set; }
public SensLogonEventType Type { get; private set; }
}
public enum SensLogonEventType
{
Logon,
Logoff,
SessionDisconnect,
SessionReconnect,
PostShell
}
Run Code Online (Sandbox Code Playgroud)
注意:通过右键单击Visual Studio快捷方式并单击run as administrator,确保Visual Studio以管理员权限运行,否则System.UnauthorizedAccessException将在程序运行时抛出.
我使用ServiceBase.OnSessionChange捕获不同的用户事件并随后加载必要的信息。
protected override void OnSessionChange(SessionChangeDescription desc)
{
var user = Session.Get(desc.SessionId);
}
Run Code Online (Sandbox Code Playgroud)
为了加载会话信息,我使用WTS_INFO_CLASS。请参阅下面我的示例:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下代码使用AvailabilityChanged来自 的静态事件User,一旦会话状态发生更改,该事件就会被触发。arge包含特定用户。
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);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
21005 次 |
| 最近记录: |