LOL 更新后,偶尔在进入游戏时会自动跳回桌面。搜索后发现有人提到可以用监控软件查看是哪个应用进程抢占了窗口焦点。受此启发,我编写了一个 PowerShell 脚本,通过实时监控前台窗口切换来揪出元凶。
使用方法
1. 下载与准备
将 WindowMonitor.ps1 和 启动监视器.bat 两个文件放在同一目录下(例如桌面或任意文件夹)。
2. 启动监视器
方式一(推荐): 双击 启动监视器.bat 文件,脚本会自动启动并打开 PowerShell 窗口。
方式二: 右键 WindowMonitor.ps1 → 选择「使用 PowerShell 运行」。
3. 观察与记录
启动后,控制台窗口会实时显示每次窗口焦点切换的信息,格式如下:
[21:30:15.123] FOCUS >> [LOL客户端] (LeagueClientUx / PID:12345)
[21:30:18.456] FOCUS >> [Google Chrome] (chrome / PID:67890)
[21:30:18.789] FOCUS >> BACK TO DESKTOP (explorer.exe)
- 正常游戏时,焦点应保持在游戏窗口。
- 如果突然跳回桌面,控制台会以红色 显示
BACK TO DESKTOP记录,同时日志中会记录跳转前最后一个获得焦点的进程,帮助定位元凶。
4. 查看日志
脚本会在同目录下自动生成日志文件,命名格式为 WindowMonitor_Log_20260613_213000.txt。日志记录了每次焦点切换的详细时间、窗口标题、进程名和 PID,方便事后分析。
5. 停止监视
在 PowerShell 窗口中按 Ctrl + C 即可停止监视,控制台会显示本次共记录了多少次焦点切换事件。
脚本内容
WindowMonitor.ps1 文件内容如下
ps1
<#
窗口活动监视器 v2.0 (便携版)
功能:实时监控前台窗口切换,记录每次焦点变化的时间、窗口标题和进程
用法:双击 启动监视器.bat 或右键此文件用 PowerShell 运行
日志:同目录下自动生成日志文件
#>
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class WinAPI {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
}
"@
$PollIntervalMs = 200
$LogDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$LogFile = Join-Path $LogDir "WindowMonitor_Log_$ts.txt"
$ShowDesktopAsFocus = $true
function Get-ForegroundInfo {
$hwnd = [WinAPI]::GetForegroundWindow()
if ($hwnd -eq [IntPtr]::Zero) { return $null }
$len = [WinAPI]::GetWindowTextLength($hwnd)
$sb = New-Object System.Text.StringBuilder ($len + 1)
[WinAPI]::GetWindowText($hwnd, $sb, $sb.Capacity) | Out-Null
$title = $sb.ToString()
$pidOut = [uint32]0
[WinAPI]::GetWindowThreadProcessId($hwnd, [ref]$pidOut) | Out-Null
$procName = ""
try {
$proc = Get-Process -Id $pidOut -ErrorAction SilentlyContinue
if ($proc) { $procName = $proc.ProcessName }
} catch {}
return @{
Hwnd = $hwnd
Title = $title
ProcName = $procName
Pid = $pidOut
}
}
$Host.UI.RawUI.WindowTitle = "Window Monitor v2.0"
Write-Host ""
Write-Host " ================================================" -ForegroundColor Cyan
Write-Host " Window Monitor v2.0 (Portable)" -ForegroundColor Cyan
Write-Host " ================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Log: $LogFile" -ForegroundColor Yellow
Write-Host " Poll: ${PollIntervalMs}ms | Ctrl+C to stop" -ForegroundColor Yellow
Write-Host ""
Write-Host " ------------------------------------------------" -ForegroundColor DarkGray
Write-Host ""
$header = "Window Monitor v2.0 Log`r`nStart: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`nPoll: ${PollIntervalMs}ms`r`n================================`r`n"
$header | Out-File -FilePath $LogFile -Encoding UTF8
$lastHwnd = [IntPtr]::Zero
$eventCount = 0
try {
while ($true) {
$info = Get-ForegroundInfo
if ($null -ne $info -and $info.Hwnd -ne $lastHwnd) {
$now = Get-Date -Format "HH:mm:ss.fff"
$t = $info.Title
$p = $info.ProcName
$pidVal = $info.Pid
if ([string]::IsNullOrEmpty($t)) { $t = "(no title)" }
if ([string]::IsNullOrEmpty($p)) { $p = "unknown" }
$isDesktop = ($p -eq "explorer" -and ($info.Title -eq "" -or $info.Title -eq "Program Manager"))
if ($isDesktop -and -not $ShowDesktopAsFocus) {
Start-Sleep -Milliseconds $PollIntervalMs
continue
}
$eventCount++
if ($isDesktop) {
$line = " [$now] >> BACK TO DESKTOP (explorer.exe)"
$color = "Red"
} else {
$line = " [$now] FOCUS >> [$t] ($p / PID:$pidVal)"
$color = "White"
}
Write-Host $line -ForegroundColor $color
$logLine = "[$now] FOCUS >> Title: $t | Process: $p | PID: $pidVal"
$logLine | Out-File -FilePath $LogFile -Encoding UTF8 -Append
$lastHwnd = $info.Hwnd
}
Start-Sleep -Milliseconds $PollIntervalMs
}
}
finally {
Write-Host ""
Write-Host " ------------------------------------------------" -ForegroundColor DarkGray
Write-Host " Stopped. $eventCount events recorded." -ForegroundColor Yellow
Write-Host " Log: $LogFile" -ForegroundColor Yellow
Write-Host ""
}
WindowMonitor.ps1 文件v2.1版本内容如下
bash
<#
Window Monitor v2.1 (Portable - Hybrid Edition)
- 50ms fast polling to catch quick popups
- Auto snapshot of all visible windows when BACK TO DESKTOP detected
- Monitor window minimized to avoid stealing game focus
Usage: double-click the .bat launcher or run this .ps1 in PowerShell
#>
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
public class WinAPI2 {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left, Top, Right, Bottom;
}
public const int SW_MINIMIZE = 6;
public const int SW_SHOWMINNOACTIVE = 7;
public static void MinimizeConsole() {
IntPtr console = GetConsoleWindow();
if (console != IntPtr.Zero) {
ShowWindow(console, SW_MINIMIZE);
}
}
public static List<string> GetVisibleWindowSnapshot() {
var result = new List<string>();
EnumWindows(delegate(IntPtr hWnd, IntPtr param) {
if (IsWindowVisible(hWnd)) {
int len = GetWindowTextLength(hWnd);
if (len > 0) {
StringBuilder sb = new StringBuilder(len + 1);
GetWindowText(hWnd, sb, sb.Capacity);
string title = sb.ToString();
uint pid = 0;
GetWindowThreadProcessId(hWnd, out pid);
string procName = "";
try {
var p = System.Diagnostics.Process.GetProcessById((int)pid);
if (p != null) procName = p.ProcessName;
} catch {}
RECT rect;
GetWindowRect(hWnd, out rect);
int w = rect.Right - rect.Left;
int h = rect.Bottom - rect.Top;
if (w > 5 && h > 5 && procName != "") {
result.Add(string.Format("{0} | {1} | PID:{2} | {3}x{4}",
title, procName, pid, w, h));
}
}
}
return true;
}, IntPtr.Zero);
return result;
}
}
"@
$PollIntervalMs = 50
$LogDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$LogFile = Join-Path $LogDir "WindowMonitor_Log_$ts.txt"
$Host.UI.RawUI.WindowTitle = "Window Monitor v2.1"
Write-Host ""
Write-Host " ================================================" -ForegroundColor Cyan
Write-Host " Window Monitor v2.1 (Hybrid)" -ForegroundColor Cyan
Write-Host " ================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Log: $LogFile" -ForegroundColor Yellow
Write-Host " Poll: ${PollIntervalMs}ms + desktop snapshot" -ForegroundColor Yellow
Write-Host " Ctrl+C to stop" -ForegroundColor Yellow
Write-Host ""
Write-Host " Monitor will minimize in 3 seconds..." -ForegroundColor DarkGray
Start-Sleep -Seconds 3
[WinAPI2]::MinimizeConsole()
Write-Host ""
Write-Host " ------------------------------------------------" -ForegroundColor DarkGray
Write-Host ""
$header = "Window Monitor v2.1 Log`r`nStart: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`nPoll: ${PollIntervalMs}ms + snapshot on desktop`r`n================================`r`n"
$header | Out-File -FilePath $LogFile -Encoding UTF8
$lastHwnd = [IntPtr]::Zero
$eventCount = 0
$myPid = $PID
try {
while ($true) {
$hwnd = [WinAPI2]::GetForegroundWindow()
if ($hwnd -ne [IntPtr]::Zero -and $hwnd -ne $lastHwnd) {
$now = Get-Date -Format "HH:mm:ss.fff"
$len = [WinAPI2]::GetWindowTextLength($hwnd)
$sb = New-Object System.Text.StringBuilder ($len + 1)
[WinAPI2]::GetWindowText($hwnd, $sb, $sb.Capacity) | Out-Null
$rawTitle = $sb.ToString()
$pidOut = [UInt32]0
[WinAPI2]::GetWindowThreadProcessId($hwnd, [ref]$pidOut) | Out-Null
$procName = ""
try {
$proc = Get-Process -Id $pidOut -ErrorAction SilentlyContinue
if ($proc) { $procName = $proc.ProcessName }
} catch {}
# Skip our own window to avoid stealing game focus
if ($pidOut -eq $myPid) {
$lastHwnd = $hwnd
Start-Sleep -Milliseconds $PollIntervalMs
continue
}
$title = $rawTitle
if ([string]::IsNullOrEmpty($title)) { $title = "(no title)" }
if ([string]::IsNullOrEmpty($procName)) { $procName = "unknown" }
$isDesktop = ($procName -eq "explorer" -and ($rawTitle -eq "" -or $rawTitle -eq "Program Manager"))
$eventCount++
if ($isDesktop) {
# BACK TO DESKTOP - take snapshot
$line = " [$now] >> BACK TO DESKTOP (explorer.exe)"
Write-Host $line -ForegroundColor Red
"[$now] >> BACK TO DESKTOP" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
Write-Host " [$now] >> Snapshot: all visible windows ---" -ForegroundColor Magenta
"[$now] >> SNAPSHOT:" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
$snap = [WinAPI2]::GetVisibleWindowSnapshot()
foreach ($entry in $snap) {
# Skip explorer desktop and our own process
if ($entry -match "Program Manager \| explorer") { continue }
Write-Host " $entry" -ForegroundColor DarkYellow
" $entry" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
}
Write-Host " [$now] >> End snapshot" -ForegroundColor Magenta
"---" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
Write-Host ""
} else {
$line = " [$now] FOCUS >> [$title] ($procName / PID:$pidOut)"
Write-Host $line -ForegroundColor White
"[$now] FOCUS >> Title: $title | Process: $procName | PID: $pidOut" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
}
$lastHwnd = $hwnd
}
Start-Sleep -Milliseconds $PollIntervalMs
}
}
finally {
Write-Host ""
Write-Host " ------------------------------------------------" -ForegroundColor DarkGray
Write-Host " Stopped. $eventCount events recorded." -ForegroundColor Yellow
Write-Host " Log: $LogFile" -ForegroundColor Yellow
Write-Host ""
}
启动监视器.bat内容如下
bash
@echo off
chcp 65001 >nul 2>&1
title Window Monitor v2.0
echo.
echo Starting Window Monitor...
echo.
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0WindowMonitor.ps1"
pause