【问题标题】:Getting user Idle time in C#?在 C# 中获取用户空闲时间?
【发布时间】:2011-09-23 13:53:51
【问题描述】:

我找到了这篇关于如何获取用户 Idle Time 的空闲时间的教程。

问题是它只有在应用程序在用户身上运行时才能工作。

我的应用程序在 SYSTEM 上运行。

如何获得空闲时间?或者如果 PC 空闲?

【问题讨论】:

  • 由于在 SYSTEM 下运行的服务种类繁多,它实际上很少是“IDLE”,因此这个数字几乎肯定为零。你能告诉我们你想用这个号码做什么吗?你更大的目标是什么?
  • 我想更大的目标是查看连接的鼠标和键盘设备的空闲情况。
  • 如果你想获得登录用户的空闲时间,你应该知道在现代 Windows 中没有这样的东西,快速用户切换或终端服务(我可以多次登录用户)。

标签: c# .net windows-services system


【解决方案1】:

我知道这个答案已经被接受,但我只是想添加这个,以防人们想在终端服务环境中做同样的事情。

Cassia 是一个开源库,它在 Windows 终端服务 API 周围放置 .NET 包装器。我已经将它用于我的服务器管理,并且效果非常好。您可以通过调用ITerminalServicesSession.LastInputTime获取任何会话的空闲时间

【讨论】:

    【解决方案2】:

    据我了解,您可以接受 GetLastInputInfo 函数的结果。因此,我会提出以下建议。

    现在,假设您有在本地系统帐户下运行的可执行文件 A。创建将收集相关系统信息的可执行文件 B(在 GetLastInputInfo 的帮助下)。接下来,使用这个类从可执行文件 A 运行可执行文件 B,该类使用 CreateProcessAsUser 函数和记录的用户令牌(不幸的是,我无法找到发布它的 stackoverflow 问题):

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    
    namespace Helpers
    {
        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        internal struct SECURITY_ATTRIBUTES
        {
            public uint nLength;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public uint cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public uint dwX;
            public uint dwY;
            public uint dwXSize;
            public uint dwYSize;
            public uint dwXCountChars;
            public uint dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }
    
        internal enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }
    
        internal enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }
    
        public class ImpersonateProcessAsLoggedUser
        {
            private const short SW_SHOW = 5;
            private const uint TOKEN_QUERY = 0x0008;
            private const uint TOKEN_DUPLICATE = 0x0002;
            private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
            private const int GENERIC_ALL_ACCESS = 0x10000000;
            private const int STARTF_USESHOWWINDOW = 0x00000001;
            private const int STARTF_FORCEONFEEDBACK = 0x00000040;
            private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
    
            [DllImport("advapi32.dll", SetLastError = true)]
            private static extern bool CreateProcessAsUser(
                IntPtr hToken,
                string lpApplicationName,
                string lpCommandLine,
                ref SECURITY_ATTRIBUTES lpProcessAttributes,
                ref SECURITY_ATTRIBUTES lpThreadAttributes,
                bool bInheritHandles,
                uint dwCreationFlags,
                IntPtr lpEnvironment,
                string lpCurrentDirectory,
                ref STARTUPINFO lpStartupInfo,
                out PROCESS_INFORMATION lpProcessInformation);
    
    
            [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
            private static extern bool DuplicateTokenEx(
                IntPtr hExistingToken,
                uint dwDesiredAccess,
                ref SECURITY_ATTRIBUTES lpThreadAttributes,
                Int32 ImpersonationLevel,
                Int32 dwTokenType,
                ref IntPtr phNewToken);
    
    
            [DllImport("advapi32.dll", SetLastError = true)]
            private static extern bool OpenProcessToken(
                IntPtr ProcessHandle,
                UInt32 DesiredAccess,
                ref IntPtr TokenHandle);
    
            [DllImport("userenv.dll", SetLastError = true)]
            private static extern bool CreateEnvironmentBlock(
                ref IntPtr lpEnvironment,
                IntPtr hToken,
                bool bInherit);
    
    
            [DllImport("userenv.dll", SetLastError = true)]
            private static extern bool DestroyEnvironmentBlock(
                IntPtr lpEnvironment);
    
            [DllImport("kernel32.dll", SetLastError = true)]
            private static extern bool CloseHandle(
                IntPtr hObject);
    
    
            private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
            {
                bool result = false;
    
    
                var pi = new PROCESS_INFORMATION();
                var saProcess = new SECURITY_ATTRIBUTES();
                var saThread = new SECURITY_ATTRIBUTES();
                saProcess.nLength = (uint) Marshal.SizeOf(saProcess);
                saThread.nLength = (uint) Marshal.SizeOf(saThread);
    
                var si = new STARTUPINFO();
                si.cb = (uint) Marshal.SizeOf(si);
    
    
                //if this member is NULL, the new process inherits the desktop
                //and window station of its parent process. If this member is
                //an empty string, the process does not inherit the desktop and
                //window station of its parent process; instead, the system
                //determines if a new desktop and window station need to be created.
                //If the impersonated user already has a desktop, the system uses the
                //existing desktop.
    
                si.lpDesktop = @"WinSta0\Default"; //Modify as needed
                si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
                si.wShowWindow = SW_SHOW;
                //Set other si properties as required.
    
                result = CreateProcessAsUser(
                    token,
                    null,
                    cmdLine,
                    ref saProcess,
                    ref saThread,
                    false,
                    CREATE_UNICODE_ENVIRONMENT,
                    envBlock,
                    null,
                    ref si,
                    out pi);
    
    
                if (result == false)
                {
                    int error = Marshal.GetLastWin32Error();
                    string message = String.Format("CreateProcessAsUser Error: {0}", error);
                    Debug.WriteLine(message);
                }
    
                return result;
            }
    
    
            private static IntPtr GetPrimaryToken(int processId)
            {
                IntPtr token = IntPtr.Zero;
                IntPtr primaryToken = IntPtr.Zero;
                bool retVal = false;
                Process p = null;
    
                try
                {
                    p = Process.GetProcessById(processId);
                }
    
                catch (ArgumentException)
                {
                    string details = String.Format("ProcessID {0} Not Available", processId);
                    Debug.WriteLine(details);
                    throw;
                }
    
    
                //Gets impersonation token
                retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
                if (retVal)
                {
                    var sa = new SECURITY_ATTRIBUTES();
                    sa.nLength = (uint) Marshal.SizeOf(sa);
    
                    //Convert the impersonation token into Primary token
                    retVal = DuplicateTokenEx(
                        token,
                        TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
                        ref sa,
                        (int) SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                        (int) TOKEN_TYPE.TokenPrimary,
                        ref primaryToken);
    
                    //Close the Token that was previously opened.
                    CloseHandle(token);
                    if (retVal == false)
                    {
                        string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
                        Debug.WriteLine(message);
                    }
                }
    
                else
                {
                    string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
                    Debug.WriteLine(message);
                }
    
                //We'll Close this token after it is used.
                return primaryToken;
            }
    
            private static IntPtr GetEnvironmentBlock(IntPtr token)
            {
                IntPtr envBlock = IntPtr.Zero;
                bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
                if (retVal == false)
                {
                    //Environment Block, things like common paths to My Documents etc.
                    //Will not be created if "false"
                    //It should not adversley affect CreateProcessAsUser.
    
                    string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
                    Debug.WriteLine(message);
                }
                return envBlock;
            }
    
            public static bool Launch(string appCmdLine /*,int processId*/)
            {
                bool ret = false;
    
                //Either specify the processID explicitly
                //Or try to get it from a process owned by the user.
                //In this case assuming there is only one explorer.exe
    
                Process[] ps = Process.GetProcessesByName("explorer");
                int processId = -1; //=processId
                if (ps.Length > 0)
                {
                    processId = ps[0].Id;
                }
    
                if (processId > 1)
                {
                    IntPtr token = GetPrimaryToken(processId);
    
                    if (token != IntPtr.Zero)
                    {
                        IntPtr envBlock = GetEnvironmentBlock(token);
                        ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
                        if (envBlock != IntPtr.Zero)
                            DestroyEnvironmentBlock(envBlock);
    
                        CloseHandle(token);
                    }
                }
                return ret;
            }
        }
    }
    

    接下来,您需要找到一种将收集到的信息从可执行文件 B 发送到可执行文件 A 的方法。有很多方法可以做到这一点。其中之一是.Net Remoting。但是,您可以创建中间 XML 甚至文本文件。

    也许这不是解决您的问题的最佳方式,但如果您需要更多本地系统 记录用户交互,您可以遵循一个模式。

    【讨论】:

    • 嗯,关键是可执行文件 B 将在当前用户上下文中运行。这就是为什么 GetLastInputInfo 应该返回该特定用户的统计信息。此类现在在我的系统中运行,该系统由在本地系统帐户下运行的 Windows 服务和从该服务注入记录的用户上下文的可执行文件组成。通信渠道是.Net Remoting。看看 Launch 方法和这一行: IntPtr token = GetPrimaryToken(processId); UPD:哇,我在回复什么? :-)
    • 很抱歉,我更仔细地查看了代码并意识到您在做什么,您的描述并没有使解决方案的这方面变得清晰。但是,我怀疑这在终端服务环境中是否会非常有效,因为您不知道您实际正在与哪个会话进行通信,当然这可能是一个可以接受的限制,具体取决于用例场景,所以我只是指出这一点仅供参考。
    • 谢谢,克里斯。对不起描述,我的英语不太好。我没有在终端服务环境中尝试过代码,但就我而言,它就像一个魅力。实际上,建议的解决方案在您的场景中可能不起作用的唯一原因是错误的资源管理器令牌。此案需要进一步调查。
    • @Virtuality 嘿,您认为在 Windows 服务(在系统上运行)和正常进程(在当前用户上运行)之间进行通信的最佳方式。顺便说一句,你的代码太棒了!它适用于所有平台吗? (XP/Vista/7)?
    • @Danpe 好吧,谢谢,但这不是我的,我在 StackOverflow 的某个地方找到了它。据我所知,它在 XP/Vista/7 上运行没有问题。当我开始我提到的那些项目时,我使用的是 .Net Remoting,但现在不确定这是最好的选择。如果您在 .NET 4 上进行开发,根据您的要求,我建议您查看 1) WCF(tcp 或命名管道端点),2) NamedPipes(据我所知,.NET 4 具有本机支持)。当然,这些只是其中的一部分。
    【解决方案3】:

    执行此类操作的推荐方法是运行一个单独的应用程序,将用户会话数据传递给服务。您可以在用户会话开始时配置应用程序,方法是将其添加到HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run 下的注册表中

    您可以在以下知识库文章中阅读有关此方法的更多详细信息

    http://support.microsoft.com/kb/308403

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 2011-04-24
      • 1970-01-01
      • 2011-02-05
      • 1970-01-01
      相关资源
      最近更新 更多