1

As the title says, how can I bring a Powershell GUI window in front of another window after an event has happened, if it is at all possible? As in, I have, for example, Firefox opened and the Powershell GUI is running behind it, after certain event happens inside of the Powershell it pops in front of the Firefox?

raz0shi
  • 11
  • 1

1 Answers1

2

On Windows, you can use [Microsoft.VisualBasic.Interaction]::AppActivate() to reactivate your own process' main window via the process ID, as reflected in the automatic $PID variable:

# Enable cross-process window activation (see below).
(Add-Type -ErrorAction Stop -PassThru -Namespace "Random.Ns$PID.AllowWindowActivation" -Name WinApiHelper -MemberDefinition @'
  [DllImport("user32.dll", EntryPoint="SystemParametersInfo")]
  static extern bool SystemParametersInfo_Set_UInt32(uint uiAction, uint uiParam, UInt32 pvParam, uint fWinIni);
  public static void AllowWindowActivation()
  {
    if (! SystemParametersInfo_Set_UInt32(0x2001 /* SPI_SETFOREGROUNDLOCKTIMEOUT */, 0, 0 /* timeout in secs */, 0 /* non-persistent change */)) {
      throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error(), "Unexpected failure calling SystemParametersInfo() with SPI_SETFOREGROUNDLOCKTIMEOUT");
    }
  }
'@)::AllowWindowActivation()

# Load the required assembly.
Add-Type -AssemblyName Microsoft.VisualBasic

# Launch a sample GUI application that will steal the focus
# (will become the foreground application).
Start-Process notepad.exe

# Wait a little.
Start-Sleep 3 

# Now reactivate the main window of the current process.
[Microsoft.VisualBasic.Interaction]::AppActivate($PID)

Note:

  • General programmatic activation of arbitrary windows across process boundaries is prevented by default:

    • Instead of the targeted window getting activated, its taskbar button flashes, so as to signal to the user the intent to make the window active.

    • However, programmatic activation from code running in the current foreground window seems to always be permitted.

  • The Add-Type -MemberDefinition call above overrides this for the current session using a P/Invoke call to the SystemParametersInfo WinAPI function, via setting its SPI_SETFOREGROUNDLOCKTIMEOUT parameter to 0.

    • This will incur a one-time compilation performance penalty per session.

    • Cross-process window activation will be enabled for the remainder of the session, for all processes.

    • [Doesn't work anymore in W10+] The alternative is to persistently configure your user account to allow activation, via the registry:

      • Set the ForegroundLockTimeout per-user registry value in HKEY_CURRENT_USER\Control Panel\Desktop to 0 (the default is 200000 msecs., i.e. 3 minutes and 20 secs); requires logging off or rebooting for the change to take effect:

        Set-ItemProperty 'registry::HKEY_CURRENT_USER\Control Panel\Desktop' ForegroundLockTimeout 0
        
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Good alternative to native `SetForegroundWindow()`, so P/Invoke can be avoided. Under the hood it propably calls `SetForegroundWindow` after determining the "main window" of the process, so it actually just flashes the taskbar button instead of actively setting the window to the foreground. – zett42 Sep 11 '22 at 09:22
  • 1
    Thanks man, I found exactly that code prior to posting this question, but couldn't find the way to determine the PID of my program/script. So this does it, thanks again. – raz0shi Sep 11 '22 at 12:54
  • Thanks, @zett42. On my machine I never see the flashing behavior and I don't know why: the `ForegroundLockTimeout` registry value is at its default, but the `SystemParametersInfo()` WinAPI function indeed indicates that activation is _allowed_, but I don't know where that is set (it is _not_ related to Developer Mode, which is off on my machine). Do you have an explanation? – mklement0 Sep 11 '22 at 15:07
  • @zett42, as for `[Microsoft.VisualBasic.Interaction]::AppActivate()`, specifically: [This answer](https://stackoverflow.com/a/19789921/45375) claims that the method uses a hack to unconditionally support activation. If you have a machine where `SetForegroundWindow` fails to activate, can you confirm that `[Microsoft.VisualBasic.Interaction]::AppActivate()` still succeeds? – mklement0 Sep 11 '22 at 15:09
  • @mklement0 No, `AppActivate()` exhibits the same behaviour as `SetForegroundWindow()` on my machine (both flash the taskbar button only), that's why I thought the former is implemented by calling the latter. That link is to a very old answer, so the hack has propably been prevented from working in newer Windows versions. – zett42 Sep 11 '22 at 15:36
  • Thanks, @zett42. Does `$outVal = 0; (Add-Type -PassThru -Name WinApi -Namespace pg -MemberDefinition @' [DllImport("user32.dll")] public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref UInt32 pvParam, uint fWinIni); '@)::SystemParametersInfo(0x2000 <# SPI_GETFOREGROUNDLOCKTIMEOUT #>, 0, [ref] $outVal, 0); $outVal` return (`$true` and) a nonzero value for you? – mklement0 Sep 11 '22 at 15:40
  • Yes, `$true` and `20000`. It matches the value from the registry. – zett42 Sep 11 '22 at 15:42
  • Thanks, the mystery is why on my machine it reports `0`, even though the registry also shows `20000` – mklement0 Sep 11 '22 at 15:44
  • 1
    I don't have an explanation for that. You may use [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) to determine which registry key(s) are actually queried by `SystemParametersInfo()`. – zett42 Sep 11 '22 at 15:46
  • 1
    Thanks, @zett42: I tried (not too hard) and came up short, and ended up creating a question, https://stackoverflow.com/q/73735129/45375, which contains additional background info, including with respect to Windows 11. – mklement0 Sep 15 '22 at 17:30