获取远程计算机上的最后活动时间

时间:2017-05-13 00:37:21

标签: c# powershell winapi pinvoke powershell-remoting

我需要从键盘鼠标最后一次激活时获得时间 - 但是在远程计算机上。 - 它需要适用于任何用户,即使他们通过远程桌面连接。

我已经能够在C#控制台应用程序和PowerShell脚本中通过P /调用来自User32.dll的GetLastInputInfo在本地计算机(甚至是RDP会话)上获取此信息

然而,通过远程PowerShell(或通过后台服务)提供相同的代码会给我错误的信息(它基本上只是给我机器的正常运行时间,好像没有处理任何输入事件)。

如何从远程计算机获取上次键盘/鼠标活动的正确时间? (即,从远程PowerShell上下文或Windows服务上下文中运行的代码)。

也许有一些设置我可以更改为后台进程访问当前登录的用户会话? - 或者我可以更改注册表设置? - 或许有一种方法可以枚举活动会话并将会话对象传递给某种GetLastInputInfoEx函数或其他东西? - 我怎样才能继续前进?

我当前的PowerShell脚本看起来像这样(我使用的C#版本也应该很明显):

$signature = @'
[DllImport("User32.dll")]
private static extern bool GetLastInputInfo(ref LastInputInfo plii);

public struct LastInputInfo
{
    public uint Size;

    public uint Time;
}

public static TimeSpan? GetIdleTime()
{
    LastInputInfo lastInput = new LastInputInfo();
    lastInput.Size = (uint)Marshal.SizeOf(lastInput);

    uint envTicks = (uint)Environment.TickCount;
    if (GetLastInputInfo(ref lastInput))
    {
        double ms = envTicks - lastInput.Time;
        if (ms >= 0)
        {
            return TimeSpan.FromMilliseconds(ms);
        }
    }

    return null;
}
'@

$type = Add-Type -Name "User32Utils" -Namespace "InteropHelpers" -MemberDefinition $signature -PassThru

return [InteropHelpers.User32Utils]::GetIdleTime();

1 个答案:

答案 0 :(得分:1)

感谢comment above Scott Chamberlain,我能够找到使用Cassia的解决方案(我通过他们的NuGet Package消费)。

免责声明:请注意,这仅适用于RDP会话。 (当我测试Cassia时,它无法返回非RDP会话的IdleTimeLastInputTime数据.YMMV,但它适用于我,因为我编写代码来监视VM只能通过RDP访问。

此外,虽然不是绝对必要,但我选择通过编写一个快速的PowerShell Cmdlet来展示Cassia提供的数据(它的Class Library名为" CassiaCmdlet.dll& #34;其中包含一个单词):

using System;
using System.Management.Automation;
using Cassia;

namespace CassiaCmdlet
{
    [Cmdlet("Get", "TsSession")]
    public class GetTsSession : Cmdlet
    {
        [Parameter(Position = 0)]
        [Alias("ServerName", "MachineName", "Name")]
        public string ComputerName { get; set; }

        /// <summary>
        /// Processes the record.
        /// </summary>
        protected override void ProcessRecord()
        {
            using (var server = GetServer())
            {
                server.Open();
                foreach (var session in server.GetSessions())
                {
                    WriteObject(session);
                }
                server.Close();
            }
        }

        private ITerminalServer GetServer()
        {
            var mgr = new TerminalServicesManager();

            if (string.IsNullOrWhiteSpace(ComputerName))
            { 
                // Local Machine
                return mgr.GetLocalServer(); 
            }
            else
            {
                // Remote Machine
                return mgr.GetRemoteServer(ComputerName);
            }
        }
    }
}

然后从PowerShell:

[Fubar]: PS C:\Scripts\Cmdlets> Import-Module .\CassiaCmdlet.dll
[Fubar]: PS C:\Scripts\Cmdlets> Get-TsSession

<... snip ...>

ClientDisplay     : Cassia.Impl.ClientDisplay
ClientBuildNumber : 0
Server            : Cassia.Impl.TerminalServer
ClientIPAddress   : 
WindowStationName : 
DomainName        : Fubar
UserAccount       : Fubar\Spock
ClientName        : 
ConnectionState   : Disconnected
ConnectTime       : 5/15/2017 11:03:07 PM
CurrentTime       : 5/15/2017 11:25:51 PM
DisconnectTime    : 5/15/2017 11:04:56 PM
LastInputTime     : 5/15/2017 11:04:56 PM
LoginTime         : 5/15/2017 10:44:34 PM
IdleTime          : 00:20:54.7733923
SessionId         : 2
UserName          : Spock

<... snip ...>

值得注意的是,对于远程 PowerShell系统,会话对象中的日期为本地时间。因此,您可能希望在使用它们之前将它们转换为通用时间。

另外值得注意的是,已连接的会话将有WindowStationName类似&#34; RDP *&#34;,但同一会话一旦断开连接,将具有空名称(它仍然具有{{1虽然!)。

使用它的一个简单示例如下:

LastInputTime

这过滤了&#34; RDP *&#34;或&#34;&#34 ;;理想情况下,您只需过滤掉PowerShell会话。 (我认为默认名称为&#34; Console&#34;,但是如果您从C#连接,则可以将其命名为任何名称,因此我保持代码简单。)