You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过AWS Lambda或SSM可靠获取Windows EC2实例全会话类型的实时用户活动(鼠标/键盘)时间戳?

如何通过AWS Lambda或SSM可靠获取Windows EC2实例全会话类型的实时用户活动(鼠标/键盘)时间戳?

这个问题我之前帮客户处理过,核心原因就是SSM Run Command的执行上下文——它默认跑在Windows的Session 0(服务会话),而GetLastInputInfo会话隔离的API,只能获取当前运行会话的用户输入状态。Session 0是后台服务专用的,没有任何用户交互,所以你每次拿到的都是同一个静态值,完全反映不了桌面会话的真实活动。

下面给你几个可行的解决方案,从快速调整到长期可靠方案都有:

方案一:让SSM命令在用户的交互式会话中执行(临时应急)

如果不想部署代理,可以先通过枚举所有活跃的交互式会话,再在每个会话中单独执行空闲时间检测逻辑。这样能绕过Session 0的限制,拿到真实的用户活动数据。

具体步骤:

  1. 先在EC2实例上提前部署Sysinternals的PsExec.exe(可以通过SSM State Manager批量推送,或者手动下载到C:\Tools\目录),它能帮我们在指定的用户会话中启动进程。
  2. 用以下PowerShell脚本作为SSM Run Command的执行内容:
# 1. 获取所有活跃的交互式会话(排除Session 0)
$activeSessions = quser | Where-Object { $_ -notmatch '^USERNAME' -and $_ -notmatch 'Disc' } | ForEach-Object {
    $parts = $_ -split '\s{2,}'
    [PSCustomObject]@{
        Username = $parts[0]
        SessionId = $parts[2]
    }
}

# 2. 补充NICE DCV会话(如果quser没识别到)
if (Get-Command "dcv" -ErrorAction SilentlyContinue) {
    $dcvSessions = dcv list-sessions | Where-Object { $_ -match '^[a-f0-9-]+' } | ForEach-Object {
        $sessionId = $_ -split '\s+' | Select-Object -First 1
        $dcvDetails = dcv describe-session $sessionId | ConvertFrom-Json
        [PSCustomObject]@{
            Username = $dcvDetails.owner
            SessionId = $dcvDetails.'user-session-id'
        }
    }
    $activeSessions += $dcvSessions | Where-Object { $_.SessionId -notin $activeSessions.SessionId }
}

# 3. 在每个活跃会话中执行空闲时间检测
$latestActivity = $null
foreach ($session in $activeSessions) {
    $idleOutput = & C:\Tools\PsExec.exe -i $session.SessionId -s powershell -Command @"
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class IdleTime {
    [StructLayout(LayoutKind.Sequential)]
    struct LASTINPUTINFO {
        public uint cbSize;
        public uint dwTime;
    }
    [DllImport("user32.dll")]
    static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
    public static uint GetIdleTime() {
        LASTINPUTINFO lii = new LASTINPUTINFO();
        lii.cbSize = (uint)Marshal.SizeOf(lii);
        GetLastInputInfo(ref lii);
        return ((uint)Environment.TickCount - lii.dwTime) / 1000;
    }
}
"@
$idle = [IdleTime]::GetIdleTime()
(Get-Date).ToUniversalTime().AddSeconds(-$idle).ToString("o")
"@
    $sessionActivity = [DateTime]::Parse($idleOutput)
    if (-not $latestActivity -or $sessionActivity -gt $latestActivity) {
        $latestActivity = $sessionActivity
    }
    Write-Output "会话 $($session.SessionId)(用户 $($session.Username))最后活动时间:$idleOutput"
}

# 4. 输出所有会话中最新的活动时间
if ($latestActivity) {
    Write-Output "全局最新用户活动时间:$($latestActivity.ToString('o'))"
} else {
    Write-Output "无活跃用户会话"
}

缺点:

  • 每次调用SSM都有延迟,实时性差
  • 需要维护PsExec.exe的权限和安全例外(可能被Windows Defender拦截)
  • 会话枚举逻辑需要适配不同的远程桌面工具(比如Moonlight可能需要额外处理)

方案二:自定义后台代理服务(推荐,长期可靠)

如果需要跨所有会话类型的实时、可靠数据,部署一个轻量级的代理服务是最优解。它能持续监控所有会话的活动状态,主动上报到存储服务,Lambda直接查询即可。

具体实现思路:

  1. 代理脚本:编写PowerShell脚本,定期(比如每10秒)检查所有交互式会话的空闲时间,取所有会话中最新的活动时间戳,上报到DynamoDB/CloudWatch Metrics。
  2. 注册为Windows服务:用NSSM(Non-Sucking Service Manager)把脚本包装成Windows服务,随机器自动启动,后台运行。
  3. Lambda查询:Lambda直接调用DynamoDB API,根据EC2实例ID获取最新的活动时间戳。

代理脚本示例:

# 配置参数
$awsRegion = "us-east-1"
$dynamoTable = "EC2UserActivity"
$checkInterval = 10 # 秒

# 获取当前EC2实例ID
$instanceId = (Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/instance-id -ErrorAction Stop)

# 初始化AWS工具
Set-AWSCredential -ProfileName "EC2AgentRole" -Region $awsRegion

while ($true) {
    $allIdleTimes = @()

    # 1. 从WMI获取所有会话的空闲时间(覆盖RDP/控制台/大部分远程工具)
    $desktops = Get-WmiObject -Class Win32_Desktop -ErrorAction SilentlyContinue
    if ($desktops) {
        $allIdleTimes += $desktops | ForEach-Object { $_.IdleTime }
    }

    # 2. 补充DCV会话的空闲时间
    if (Get-Command "dcv" -ErrorAction SilentlyContinue) {
        $dcvSessions = dcv list-sessions | Where-Object { $_ -match '^[a-f0-9-]+' }
        foreach ($session in $dcvSessions) {
            $sessionId = $_ -split '\s+' | Select-Object -First 1
            $dcvDetails = dcv describe-session $sessionId | ConvertFrom-Json
            $desktop = Get-WmiObject -Class Win32_Desktop -Filter "SessionId = $($dcvDetails.'user-session-id')" -ErrorAction SilentlyContinue
            if ($desktop) {
                $allIdleTimes += $desktop.IdleTime
            }
        }
    }

    # 3. 计算并上报最新活动时间
    if ($allIdleTimes.Count -gt 0) {
        $latestIdle = ($allIdleTimes | Measure-Object -Minimum).Minimum
        $lastActivity = (Get-Date).ToUniversalTime().AddMilliseconds(-$latestIdle)
        
        # 写入DynamoDB
        $item = @{
            InstanceId = $instanceId
            LastActivity = $lastActivity.ToString('o')
            Timestamp = $lastActivity
        }
        Write-DDBItem -TableName $dynamoTable -Item $item -ErrorAction SilentlyContinue

        Write-Host "$(Get-Date -Format 'HH:mm:ss') 上报最新活动时间:$($lastActivity.ToString('o'))"
    }

    Start-Sleep -Seconds $checkInterval
}

部署注意事项:

  • 给EC2实例的IAM角色添加dynamodb:PutItem权限
  • 用NSSM把脚本注册为服务:nssm install EC2UserActivityMonitor "powershell.exe" "-File C:\Scripts\ActivityMonitor.ps1"
  • 确保代理脚本的执行权限,避免被Windows Defender拦截

方案三:使用Win32_Desktop WMI类(快速验证)

如果不想用额外工具,可以直接通过WMI的Win32_Desktop类获取所有会话的空闲时间,它能返回每个交互式会话的IdleTime(毫秒级),取最小的IdleTime对应的就是最新的活动时间。

SSM命令示例:

$desktops = Get-WmiObject -Class Win32_Desktop
if ($desktops) {
    $latestIdle = $desktops | Sort-Object IdleTime | Select-Object -First 1 -ExpandProperty IdleTime
    $lastActivity = (Get-Date).ToUniversalTime().AddMilliseconds(-$latestIdle)
    Write-Output $lastActivity.ToString('o')
} else {
    Write-Output "无活跃用户会话"
}

缺点:

  • 部分远程工具(比如Moonlight)的会话可能没有对应的Win32_Desktop实例
  • WMI的IdleTime更新频率可能不如直接调用GetLastInputInfo

最终建议

如果你的场景需要实时性高、跨所有会话类型的可靠数据,方案二的自定义代理服务是最优选择,它能持续监控、主动上报,Lambda查询也非常高效。如果只是临时排查问题,方案一或方案三可以快速验证。

另外,关于SSM Run Command的Session 0问题,目前AWS没有提供直接的配置项让它在用户会话中执行,所以必须通过跨会话执行工具(如PsExec)或代理服务来绕过这个限制。

火山引擎 最新活动