views:

205

answers:

2

I have a FileSystemWatcher instance running in the background of my PoSh session watching for changes to text files. A PoSh event subscriber is attached to this event and, when fired, it launches a console program by calling Start-Process. This program steals de focus from the current foreground window (my PoSh console). Calling SetForegroundWindow from the PoSh event subscriber to return the focus to my PoSh console doesn't work. SwitchToThisWindow does work most of the time, but according to the MSDN docs, it shoulnd't be used.

Can I prevent Start-Process from stealing the focus in this situation or set it back from the event subscriber to the window that had it before this event is fired?

A: 

For me SetForegroundWindow works well. Check this code:

Add-Type @"
  using System;
  using System.Runtime.InteropServices;
  public class Tricks {
     [DllImport("user32.dll")]
     [return: MarshalAs(UnmanagedType.Bool)]
     public static extern bool SetForegroundWindow(IntPtr hWnd);
  }
"@
sleep -sec 2
$h = (Get-Process firefox).MainWindowHandle
[void] [Tricks]::SetForegroundWindow($h)
sleep -sec 2
$h = (Get-Process -id $pid).MainWindowHandle
[void] [Tricks]::SetForegroundWindow($h)

But note that if you host PowerShell or use e.g. Console (http://sourceforge.net/projects/console/) then MainWindowHandle is the handle of your host program. So instead of (Get-Process -id $pid).MainWindowHandle you would need [tricks]::SetForegroundWindow((Get-Process console).MainWindowHandle).

Example with timer event:

$timer = New-Object Timers.Timer
$timer.Interval = 5000
$h = (Get-Process -id $pid).MainWindowHandle
$action = { 
    notepad; 
    sleep -sec 1;  # wait until the program starts (very simple approach)
    [Tricks]::SetForegroundWindow($h) }
Register-ObjectEvent $timer Elapsed -Action $action
$timer.Start()

Otherwise if you run process that has its window hidden, it could solve your problem.

$ps = new-object system.diagnostics.processstartinfo 'notepad'
$ps.windowStyle = 'hidden'
[system.diagnostics.process]::Start($ps)

Example taken and altered from documentation on msdn about Process class

stej
I'd need to return focus to the previous process as soon as the new ps console process steals it, but not by waiting an arbitrary predefined number of seconds for the new window to be drawn.
guillermooo
The I guess it is not possible without waiting. Consider that you run the process but the window appears after some time and *then* it will catch the focus. You need to determine when to get the focus back. What process do you execute?
stej
A: 

It sounds like it didnt work because you set focus, before you lost focus.

Have you tried setting focus through a job? It runs in the background while you use the console.

Something like this might work, it keeps your focus for 10 seconds after the event

Start-Job -ScriptBlock { 1..100 | %{ sleep -Milliseconds 100 #Set focus back } }

If you mix in GetForegroundWindow, you can wait till you lose focus, then grab it back

http://www.leeholmes.com/blog/MorePInvokeInPowerShell.aspx

mrwaim
I think the answer is using `CreateProcess`, `STARTUP_INFO`, `dwFlags` and `wShowWindow` as they are meant to. This will involve embedding a C# class in the powershell script to do the API call, since .NET's `Process` class doesn't seem to expose `dwFlags` and `wShowWindow`, or at least not all of their valid values.
guillermooo