AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79582909
Accepted
Lusiiky
Lusiiky
Asked: 2025-04-20 07:36:16 +0800 CST2025-04-20 07:36:16 +0800 CST 2025-04-20 07:36:16 +0800 CST

如何在启动过程之后重新聚焦 Powershell 脚本?

  • 772

我有一个 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

它运行正常……它聚焦窗口,然后按下所需的键(尽管这部分在我目前的问题中不是必需的)。我试过我的脚本,但是……什么也没做。尤其是应用程序是用脚本启动的,所以我想管理员身份也能启动吧?所以我不明白为什么它有效,但脚本本身却不行。

谢谢你!

powershell
  • 3 3 个回答
  • 113 Views

3 个回答

  • Voted
  1. Best Answer
    mklement0
    2025-04-20T22:33:13+08:002025-04-20T22:33:13+08:00

    默认情况下,Windows 会阻止以编程方式激活任意窗口,除非调用进程在调用时拥有活动(聚焦)前台窗口,大致来说;确切的标准列在帮助主题的“备注”部分SetForegroundWindow()。

    如果不满足条件,则进程尝试以编程方式设置前台窗口会导致目标进程的任务栏图标闪烁几次,而活动窗口没有任何变化,这就是您所看到的。
    (您的脚本是否以管理员身份运行无关紧要。)

    在您的情况下会发生这种情况,因为如果Start-Process启动一个创建(非瞬时)窗口的进程,后者将获得焦点(被激活),[1]因此会阻止您的 PowerShell 脚本稍后重新激活其自己的控制台窗口。

    您可以通过对 WinAPI 函数进行P/Invoke调用并将其参数设置为 来(暂时)解除此限制SystemParametersInfoSPI_SETFOREGROUNDLOCKTIMEOUT0。

    • 通过调用,这将导致每个 PowerShell 会话产生一次性编译性能损失Add-Type。

    • 对于用户 Windows 会话的剩余时间,所有进程都将启用跨进程窗口激活(因此,请考虑在退出脚本之前重新启用限制,如下所示)。


    下面是一个演示解决方案的独立示例WScript.Shell;为简单起见,它使用COM 对象的.AppActivate()方法重新激活 PowerShell 会话控制台窗口,并使用会话的进程 ID(反映在自动$PID变量中) 。[2]

    # Disable the restriction on programmatically activating arbitrary windows
    # from processes not owning the current foreground window, 
    # for the remainder session of the user's Windows session.  
    # using a P/Invoke call:
    
    # Compile a helper type...
    $helperType = 
      Add-Type -PassThru -ErrorAction Stop -Namespace ('NS_{0}_{1}' -f ((Split-Path -Leaf $PSCommandPath) -replace '[^a-z0-9]'), $PID) -Name CGetSetForegroundLockTimeout -MemberDefinition @'
        [DllImport("user32.dll", EntryPoint="SystemParametersInfo", SetLastError=true)]
        static extern bool SystemParametersInfo_Set_UInt32(uint uiAction, uint uiParam, UInt32 pvParam, uint fWinIni);
        public static void EnableCrossProcessWindowActivation(bool enable = true) 
        {
          uint timeout = enable ? (uint)0 : (uint)2147483647;
          if (!SystemParametersInfo_Set_UInt32(0x2001 /* SPI_SETFOREGROUNDLOCKTIMEOUT */, 0, timeout /* timeout in msecs. */, 0 /* non-persistent change */)) {
            throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error(), "Unexpected failure calling SystemParametersInfo() with SPI_SETFOREGROUNDLOCKTIMEOUT. Make sure that the call is made from the process owning the foreground window.");      
          }
        }
    '@
    
    #'# ... and call the method that calls the SystemParametersInfo() WinAPI function
    #       to enable cross-process window activation.
    # IMPORTANT: In order for this call to succeed, it must be made while
    #            this PowerShell script is running in the process that owns
    #            the current foreground window, 
    #            i.e. BEFORE launching your application.
    $helperType::EnableCrossProcessWindowActivation()
    
    # Start your application (Notepad in this example), asynchronously.
    # (Since Notepad is a *GUI* application, you don't strictly need Start-Process here.)
    # This will invariably give the window that Notepad creates the focus. 
    Start-Process notepad.exe
    
    # Sleep a little, to make sure Notepad has started up and received the focus.
    Start-Sleep 2
    
    # Now reactivating this PowerShell session's console window
    # works as intended.
    $null = (New-Object -ComObject WScript.Shell).AppActivate($PID)
    
    # Disable cross-process window activation again.
    # IMPORTANT: Here too the process running this script must be the one that
    #            owns the current foreground window for the call to succeed;
    #            this is what the .AppActivate() call ensured.
    $helperType::EnableCrossProcessWindowActivation($false)
    

    关于潜在替代方案的说明:

    可以想象,您可以尝试以不从 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-ProcessStart-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()

    • 2
  2. masoud
    2025-04-20T15:53:24+08:002025-04-20T15:53:24+08:00

    启动进程后,使用临时 VBS 脚本重新聚焦 PowerShell 窗口

    Start-Process -FilePath "MyProcessPath"
    
    Start-Sleep -Seconds 1
    
    $myWindowTitle = (Get-Process -Id $PID).MainWindowTitle
    
    $tempPath = [System.IO.Path]::GetTempFileName().Replace(".tmp", ".vbs")
    
    $code = @"
    
    Set WshShell = WScript.CreateObject("WScript.Shell")
    
    WScript.Sleep 500
    
    WshShell.AppActivate "$myWindowTitle"
    
    "@
    
    $code | Out-File $tempPath -Encoding ASCII
    
    Start-Process "wscript.exe" $tempPath -Wait
    
    Remove-Item $tempPath  
    
    

    如果有用的话请告诉我

    • 0
  3. João Mac
    2025-04-20T17:11:26+08:002025-04-20T17:11:26+08:00

    据我了解,您有一个在控制台中运行并触发应用程序的脚本。此时,控制台失去了焦点。过一会儿,您希望焦点返回到控制台。

    我通常使用自己创建的函数来完成这个任务:

        ## Define constants for the window display mode
        . {
            $Global:WINDOW_DISPLAY_MODE_HIDDEN = 0
            $Global:WINDOW_DISPLAY_MODE_SHOW_AND_ACTIVATE = 1
            $Global:WINDOW_DISPLAY_MODE_MINIMIZE_AND_ACTIVATE = 2
            $Global:WINDOW_DISPLAY_MODE_MAXIMIZE_AND_ACTIVATE = 3
            $Global:WINDOW_DISPLAY_MODE_SHOW_WITHOUT_ACTIVATING = 4
            $Global:WINDOW_DISPLAY_MODE_SHOW_AT_ORIGINAL_POSITION_AND_ACTIVATE = 5
            $Global:WINDOW_DISPLAY_MODE_MINIMIZE_AND_ACTIVATE_OTHER_WINDOW = 6
            $Global:WINDOW_DISPLAY_MODE_MINIMIZE_WITHOUT_ACTIVATING = 7
            $Global:WINDOW_DISPLAY_MODE_SHOW_AT_ORIGINAL_POSITION_WITHOUT_ACTIVATING = 8
            $Global:WINDOW_DISPLAY_MODE_RESTORE_AND_ACTIVATE = 9
            $Global:WINDOW_DISPLAY_MODE_DEFAULT = 10
            $Global:WINDOW_DISPLAY_MODE_FORCE_MINIMIZE = 11
        }
        Function Get-Console ( $_Format = $Global:WINDOW_DISPLAY_MODE_HIDDEN ) {
            If ( $psISE ) {
                $__Window = Get-Process -Id $PID |
                    Select-Object -ExpandProperty 'MainWindowTitle'
                Return-Window -Title $__Window | Out-Null
            } Else {
                $__Resource = '
                    [DllImport ( "Kernel32.dll" )]
                    public static extern IntPtr GetConsoleWindow();
                    [DllImport ( "user32.dll" )]
                    public static extern bool ShowWindow ( IntPtr hWnd, Int32 nCmdShow );'
                Add-Type -Namespace 'Console' -Name 'Window' -MemberDefinition $__Resource
                $__ConsoleHandle = [Console.Window]::GetConsoleWindow()
                [Console.Window]::ShowWindow( $__ConsoleHandle, $_Format ) | Out-Null
            }
        }
    

    使用方法是:

        Get-Console $Global:WINDOW_DISPLAY_MODE_MINIMIZE_AND_ACTIVATE_OTHER_WINDOW
        Get-Console $Global:WINDOW_DISPLAY_MODE_RESTORE_AND_ACTIVATE
    
    • 0

相关问题

  • 如何获取作业的进程句柄?

  • PowerShell 一行代码 - 如何

  • Powershell 5.1:我的模块中缺少别名描述

  • Import-CSV:将行(成员)添加到 foreach 循环中的结果对象

  • 在所有startthreadjob powershell中使用和修改我的变量

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve