PowerShell计算机空闲时长检测异常:30分钟误判为超1小时
问题:PowerShell空闲时长检测异常导致提前休眠
我参考了一篇关于用PowerShell获取计算机空闲时长的文章,写了个脚本想实现「空闲满1小时自动休眠」的功能,但运行出问题了——明明只空闲了30分钟,脚本却检测到空闲时长超过1小时,直接触发了休眠。附上脚本和日志片段,求排查思路。
脚本代码
Add-Type @' using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace PInvoke.Win32 { public static class UserInput { [DllImport("user32.dll", SetLastError=false)] private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); [StructLayout(LayoutKind.Sequential)] private struct LASTINPUTINFO { public uint cbSize; public int dwTime; } public static DateTime LastInput { get { DateTime bootTime = DateTime.UtcNow.AddMilliseconds(-Environment.TickCount); DateTime lastInput = bootTime.AddMilliseconds(LastInputTicks); return lastInput; } } public static TimeSpan IdleTime { get { return DateTime.UtcNow.Subtract(LastInput); } } public static int LastInputTicks { get { LASTINPUTINFO lii = new LASTINPUTINFO(); lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO)); GetLastInputInfo(ref lii); return lii.dwTime; } } } } '@ do{ Log-IdleState c:\sleeplog.txt if(([PInvoke.Win32.UserInput]::IdleTime) -ge (new-timespan -hours 1)){ Add-Type -Assembly System.Windows.Forms [System.Windows.Forms.Application]::SetSuspendState("Suspend", $false, $true) } start-sleep -seconds 10 } while(1 -eq 1)
日志片段
No Action performed at 05/10/2024 11:55:58 | Detected idle for : 00:25:52.4530000 No Action performed at 05/10/2024 11:56:08 | Detected idle for : 00:26:02.4840000 No Action performed at 05/10/2024 11:56:18 | Detected idle for : 00:26:12.5160000 No Action performed at 05/10/2024 11:56:28 | Detected idle for : 00:26:22.5470000 No Action performed at 05/10/2024 11:56:38 | Detected idle for : 00:26:32.5780000 No Action performed at 05/10/2024 11:56:48 | Detected idle for : 00:26:42.6090000 No Action performed at 05/10/2024 11:56:58 | Detected idle for : 00:26:52.6410000 No Action performed at 05/10/2024 11:57:08 | Detected idle for : 00:27:02.6870000 No Action performed at 05/10/2024 11:57:18 | Detected idle for : 00:27:12.7190000 No Action performed at 05/10/2024 11:57:28 | Detected idle for : 00:27:22.7340000 No Action performed at 05/10/2024 11:57:38 | Detected idle for : 00:27:32.7500000 No Action performed at 05/10/2024 11:57:48 | Detected idle for : 00:27:42.7810000 No Action performed at 05/10/2024 11:57:58 | Detected idle for : 00:27:52.8280000 No Action performed at 05/10/2024 11:58:08 | Detected idle for : 00:28:02.8440000 No Action performed at 05/10/2024 11:58:18 | Detected idle for : 00:28:12.8590000 No Action performed at 05/10/2024 11:58:28 | Detected idle for : 00:28:22.8910000 No Action performed at 05/10/2024 11:58:38 | Detected idle for : 00:28:32.9220000 No Action performed at 05/10/2024 11:58:48 | Detected idle for : 00:28:42.9370000 No Action performed at 05/10/2024 11:58:58 | Detected idle for : 00:28:52.9840000 No Action performed at 05/10/2024 11:59:08 | Detected idle for : 00:29:03.0310000 No Action performed at 05/10/2024 11:59:18 | Detected idle for : 00:29:13.0620000 No Action performed at 05/10/2024 11:59:28 | Detected idle for : 00:29:23.0940000 No Action performed at 05/10/2024 11:59:38 | Detected idle for : 00:29:33.1250000 No Action performed at 05/10/2024 11:59:48 | Detected idle for : 00:29:43.1560000 No Action performed at 05/10/2024 11:59:58 | Detected idle for : 00:29:53.1870000 Slept at 05/10/2024 12:00:02 | Detected idle for : 1.03:56:12.4680000
解决思路
问题根源
脚本里用的Environment.TickCount是32位有符号整数,系统运行超过24.9天(2^31-1毫秒≈24.9天)后,这个值会溢出变成负数。计算启动时间bootTime = DateTime.UtcNow.AddMilliseconds(-Environment.TickCount)时,负数会变成超大的正数,导致LastInput计算完全错误,最终IdleTime出现像日志里1.03:56:12这种离谱的结果。
修复方案
替换Environment.TickCount为不会溢出的64位计时方法,推荐用GetTickCount64()(Windows API提供的64位无符号计时函数),修改后的脚本如下:
Add-Type @' using System; using System.Runtime.InteropServices; namespace PInvoke.Win32 { public static class UserInput { [DllImport("user32.dll", SetLastError=false)] private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); [DllImport("kernel32.dll")] private static extern ulong GetTickCount64(); [StructLayout(LayoutKind.Sequential)] private struct LASTINPUTINFO { public uint cbSize; public uint dwTime; // 改成uint,匹配GetLastInputInfo的返回类型 } public static DateTime LastInput { get { // 用GetTickCount64计算启动时间,无溢出问题 DateTime bootTime = DateTime.UtcNow.AddMilliseconds(-(double)GetTickCount64()); DateTime lastInput = bootTime.AddMilliseconds(LastInputTicks); return lastInput; } } public static TimeSpan IdleTime { get { return DateTime.UtcNow.Subtract(LastInput); } } public static uint LastInputTicks { get { LASTINPUTINFO lii = new LASTINPUTINFO(); lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO)); GetLastInputInfo(ref lii); return lii.dwTime; } } } } '@ # 补充原脚本缺失的Log-IdleState函数 function Log-IdleState($logPath) { $idleTime = [PInvoke.Win32.UserInput]::IdleTime $currentTime = Get-Date -Format "MM/dd/yyyy HH:mm:ss" if ($idleTime -ge (New-TimeSpan -Hours 1)) { $logEntry = "Slept at $currentTime | Detected idle for : $idleTime" } else { $logEntry = "No Action performed at $currentTime | Detected idle for : $idleTime" } Add-Content -Path $logPath -Value $logEntry } do{ Log-IdleState c:\sleeplog.txt if(([PInvoke.Win32.UserInput]::IdleTime) -ge (New-TimeSpan -Hours 1)){ Add-Type -Assembly System.Windows.Forms [System.Windows.Forms.Application]::SetSuspendState("Suspend", $false, $true) } Start-Sleep -Seconds 10 } while($true)
额外说明
- 原脚本里的
LASTINPUTINFO结构中dwTime是int类型,实际应该用uint,因为GetLastInputInfo返回的是无符号毫秒数,避免类型转换导致的潜在问题。 - 补充了原脚本缺失的
Log-IdleState函数,否则脚本运行会报错。
内容的提问来源于stack exchange,提问作者IDontKnowHowToCode
相关产品推荐
相关产品推荐

