我有一个 Pwsh 脚本,其中的菜单可以执行几种不同的操作来帮助我完成日常工作。
对于其中一项操作,我使用 Start-Process 启动一个进程,只不过它聚焦于启动的窗口,但为了方便起见,我希望在启动后再次聚焦脚本而不是启动的进程。
我不想使用“PassThru”或“NoNewWindow”参数,因为我不想启动的进程在脚本中自行启动,也不想使用最小化的“WindowStyle”,因为无论如何这都会聚焦于启动的进程,也不想使用隐藏,因为我仍然需要在某处看到进程窗口。
我尝试过这种方法,似乎效果最好:
Start-Process -FilePath "MyProcessPath"
Start-Sleep -Seconds 1
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class WinAp {
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
"@
$p = Get-Process | Where-Object { $_.MainWindowTitle -like "*ScriptTitle*" -and $_.ProcessName -eq "powershell" }
$h = $p.MainWindowHandle
[void] [WinAp]::SetForegroundWindow($h)
[void] [WinAp]::ShowWindow($h, 1)
但不幸的是,它不起作用。它似乎对窗口做了一些操作,因为它在任务栏中闪烁,但并没有获得焦点。似乎虽然脚本是以管理员身份运行的,但它无法像管理员窗口那样获得焦点?
但是,对于脚本的另一部分,我这样做了:
$tempPath = [System.IO.Path]::GetTempFileName().Replace(".tmp", ".vbs")
$code = @'
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.AppActivate "App.exe"
WScript.Sleep 250
WshShell.SendKeys "{ENTER}"
'@
$code | Out-File $tempPath -Encoding ASCII
Start-Process "wscript.exe" $tempPath -Wait
Remove-Item $tempPath
它运行正常……它聚焦窗口,然后按下所需的键(尽管这部分在我目前的问题中不是必需的)。我试过我的脚本,但是……什么也没做。尤其是应用程序是用脚本启动的,所以我想管理员身份也能启动吧?所以我不明白为什么它有效,但脚本本身却不行。
谢谢你!
默认情况下,Windows 会阻止以编程方式激活任意窗口,除非调用进程在调用时拥有活动(聚焦)前台窗口,大致来说;确切的标准列在帮助主题的“备注”部分
SetForegroundWindow()
。如果不满足条件,则进程尝试以编程方式设置前台窗口会导致目标进程的任务栏图标闪烁几次,而活动窗口没有任何变化,这就是您所看到的。
(您的脚本是否以管理员身份运行无关紧要。)
在您的情况下会发生这种情况,因为如果
Start-Process
启动一个创建(非瞬时)窗口的进程,后者将获得焦点(被激活),[1]因此会阻止您的 PowerShell 脚本稍后重新激活其自己的控制台窗口。您可以通过对 WinAPI 函数进行P/Invoke调用并将其参数设置为 来(暂时)解除此限制
SystemParametersInfo
SPI_SETFOREGROUNDLOCKTIMEOUT
0
。通过调用,这将导致每个 PowerShell 会话产生一次性编译性能损失
Add-Type
。对于用户 Windows 会话的剩余时间,所有进程都将启用跨进程窗口激活(因此,请考虑在退出脚本之前重新启用限制,如下所示)。
下面是一个演示解决方案的独立示例
WScript.Shell
;为简单起见,它使用COM 对象的.AppActivate()
方法重新激活 PowerShell 会话控制台窗口,并使用会话的进程 ID(反映在自动$PID
变量中) 。[2]关于潜在替代方案的说明:
可以想象,您可以尝试以不从 PowerShell 脚本(的控制台窗口)窃取焦点的方式启动应用程序,从而无需重新激活后者。
例如,
(New-Object -ComObject Shell.Application).ShellExecute('yourApp.exe', '', '', '', 4)
可以在不改变活动窗口的情况下启动yourapp.exe
;是否这样做取决于它是否设计为遵循所请求的启动窗口样式 - 而且似乎很少有应用程序这样做。此外,使用窗口样式时
4
,虽然应用程序的窗口不会变为活动状态,但它仍然可以遮挡PowerShell 脚本的控制台。如果可接受,样式会在启动时最小化
7
应用程序窗口,而不会窃取焦点,这可以避免遮挡问题,但需要用户通过 Alt-Tab 或点击其任务栏图标来发现已启动的应用程序。 不过,这也仅适用于支持启动窗口样式的应用程序。.ShellExecute()
COM 对象的方法和Shell.Application
可用的窗口样式都记录在这里。[Microsoft.VisualBasic.Interaction]::Shell()
提供非常相似的功能。[1] 如果您直接启动 GUI 应用程序,而不使用 ,则此方法同样适用
Start-Process
。如果您使用Start-Process
启动 GUI 应用程序,则即使使用也无法-WindowStyle Hidden
防止焦点丢失。GitHub问题 #24387是一项功能请求,要求将启动应用程序而不使其获得焦点的功能添加到,但该请求被拒绝,理由是,由于 PowerShell (Core) 7 的跨平台特性,不应使用仅限 Windows 的功能进行增强。Start-Process
Start-Process
[2] 请注意,此方法
.MainWindowHandle
与问题中的方法类似,仅当 PowerShell 进程拥有其运行所在的控制台窗口时才有效。虽然这对于直接启动的 PowerShell 会话(例如,从“开始”菜单或任务栏启动)适用,但对于从其他控制台应用程序(例如从会话启动)启动的 PowerShell 进程则不适用cmd.exe
。您可以使用以下替代方法重新激活,这也需要 P/Invoke 方法(可以与前一种方法合并为一个Add-Type
调用):(Add-Type -PassThru -Name ConsoleActivator -Namespace WinApiHelper -MemberDefinition ' [DllImport("kernel32.dll")] static extern IntPtr GetConsoleWindow(); [DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd); public static void ActivateOwnConsoleWindow() { SetForegroundWindow(GetConsoleWindow()); } ')::ActivateOwnConsoleWindow()
启动进程后,使用临时 VBS 脚本重新聚焦 PowerShell 窗口
如果有用的话请告诉我
据我了解,您有一个在控制台中运行并触发应用程序的脚本。此时,控制台失去了焦点。过一会儿,您希望焦点返回到控制台。
我通常使用自己创建的函数来完成这个任务:
使用方法是: