22

If I'm understanding this correctly this code should capture the active window and keep it in focus. concentr.exe is the process name.

How do I bring a window in focus based on process name?

Add-Type @"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"@            
try {            
    $ActiveHandle = [UserWindows]::GetForegroundWindow()
    $Process = Get-Process | ? {$_.MainWindowHandle -eq $activeHandle}            
    $Process | Select ProcessName, @{Name="concentr.exe";Expression=    {($_.MainWindowTitle)}}            
} catch {            
    Write-Error "Failed to get active Window details. More Info: $_"            
}

I've also tried

param([string] $proc="Citrix Connection Manager", [string]$adm)
cls

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 {$_.mainWindowTItle }|where {$_.Name -like   "$proc"}

 if (($p -eq $null) -and ($adm -ne ""))
 {
     Start-Process "$proc" -Verb runAs
 }
 elseif (($p -eq $null) -and ($adm -eq ""))
 {
     Start-Process "$proc" #-Verb runAs
 }
 else
 {
     $h = $p.MainWindowHandle

     [void] [WinAp]::SetForegroundWindow($h)
     [void] [WinAp]::ShowWindow($h,3);
 }
Sled
  • 18,541
  • 27
  • 119
  • 168
user770022
  • 2,899
  • 19
  • 52
  • 79

4 Answers4

15

I found it:

Param(
    [string] $proc="C:\Program Files (x86)\Citrix\ICA Client\concentr.exe",
    [string] $adm
)
Clear-Host

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 {$_.mainWindowTitle} |
    Where {$_.Name -like "$proc"}
if (($p -eq $null) -and ($adm -ne "")) {
    Start-Process "$proc" -Verb runAs
} elseif (($p -eq $null) -and ($adm -eq "")) {
    Start-Process "$proc"
} else {
    $h = $p.MainWindowHandle
    [void] [WinAp]::SetForegroundWindow($h)
    [void] [WinAp]::ShowWindow($h, 3)
}
jdgregson
  • 1,457
  • 17
  • 39
user770022
  • 2,899
  • 19
  • 52
  • 79
12

Have you thought of using the Window Name? I've found this bit of code to work great and not take up a lot of space:

$wshell = New-Object -ComObject wscript.shell
$wshell.AppActivate('New Tab - Google Chrome')

Also, if you need to just Alt-TAB back to the last thing that ran (ie: you need focus to come back to the script window after firing something off) try this:

$wshell = New-Object -ComObject wscript.shell
$wshell.SendKeys('%{TAB}')
Inventologist
  • 169
  • 1
  • 4
  • Alt-Tab was what I needed but I had to do it this way `Add-Type -AssemblyName System.Windows.Forms` and `[System.Windows.Forms.SendKeys]::SendWait('%{TAB}')` – Donn Lee Apr 09 '19 at 02:48
  • This proves far less reliable for me, often returning `False`. @user770022 's answer is more reliable. Neither however address input focus – Honest Objections Apr 18 '20 at 04:04
11

Note:
• This answer in part uses the same technique as the existing answers, but also introduces a new technique, and aims to contrast the approaches in a focused manner.
• Only the last solution below - which requires on-demand compilation of C# code via Add-Member - properly activates a window if it happens to be minimized.
• All solutions use PSv4+ syntax.


Important:

  • Unless your code runs in the current foreground window, Windows prevents the programmatic activation of other process' windows by default: Instead of activating the target window, its taskbar icon flashes.

  • Enabling unconditional programmatic activation requires additional work, via a per-session P/Invoke call - see this answer.


A simpler solution that doesn't require Add-Type with WinAPI P/Invoke signatures is possible, based on the WScript.Shell COM object's .AppActivate() method (which Inventologist's answer hints at):

Note:

  • If the target window happens to be minimized, this solution does put the focus on it, but doesn't restore it.
function Show-Window {
  param(
    [Parameter(Mandatory)]
    [string] $ProcessName
  )

  # As a courtesy, strip '.exe' from the name, if present.
  $ProcessName = $ProcessName -replace '\.exe$'

  # Get the ID of the first instance of a process with the given name
  # that has a non-empty window title.
  # NOTE: If multiple instances have visible windows, it is undefined
  #       which one is returned.
  $procId = (Get-Process -ErrorAction Ignore $ProcessName).Where({ $_.MainWindowTitle }, 'First').Id

  if (-not $procId) { Throw "No $ProcessName process with a non-empty window title found." }

  # Note: 
  #  * This can still fail, because the window could have been closed since
  #    the title was obtained.
  #  * If the target window is currently minimized, it gets the *focus*, but is
  #    *not restored*.
  #  * The return value is $true only if the window still existed and was *not
  #    minimized*; this means that returning $false can mean EITHER that the
  #    window doesn't exist OR that it just happened to be minimized.
  $null = (New-Object -ComObject WScript.Shell).AppActivate($procId)

}

# Sample invocation
Show-Window notepad

A solution that also restores the target window if it happens to be minimized requires Add-Type with WinAPI P/Invoke declarations:

Note:

  • On first invocation of the function in a PowerShell session, there is a noticeable delay due to having to compile the helper type that provides WinAPI access.

  • Unlike the solution above, this solution restores a currently minimized window to ensure that its content is visible, while properly activating a currently-maximized window without restoring it.

function Show-Window {
  param(
    [Parameter(Mandatory)]
    [string] $ProcessName
  )

  # As a courtesy, strip '.exe' from the name, if present.
  $ProcessName = $ProcessName -replace '\.exe$'

  # Get the PID of the first instance of a process with the given name
  # that has a non-empty window title.
  # NOTE: If multiple instances have visible windows, it is undefined
  #       which one is returned.
  $hWnd = (Get-Process -ErrorAction Ignore $ProcessName).Where({ $_.MainWindowTitle }, 'First').MainWindowHandle

  if (-not $hWnd) { Throw "No $ProcessName process with a non-empty window title found." }

  $type = Add-Type -PassThru -NameSpace Util -Name SetFgWin -MemberDefinition @'
    [DllImport("user32.dll", SetLastError=true)]
    public static extern bool SetForegroundWindow(IntPtr hWnd);
    [DllImport("user32.dll", SetLastError=true)]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);    
    [DllImport("user32.dll", SetLastError=true)]
    public static extern bool IsIconic(IntPtr hWnd);    // Is the window minimized?
'@ 

  # Note: 
  #  * This can still fail, because the window could have been closed since
  #    the title was obtained.
  #  * If the target window is currently minimized, it gets the *focus*, but its
  #    *not restored*.
  $null = $type::SetForegroundWindow($hWnd)
  # If the window is minimized, restore it.
  # Note: We don't call ShowWindow() *unconditionally*, because doing so would
  #       restore a currently *maximized* window instead of activating it in its current state.
  if ($type::IsIconic($hwnd)) {
    $type::ShowWindow($hwnd, 9) # SW_RESTORE
  }

}

# Sample invocation
Show-Window notepad
mklement0
  • 382,024
  • 64
  • 607
  • 775
3

I use this script to do this. Modify as you need...

For example, the default variables $ProcessNameRegEx and $WindowTitleRegEx will move new Notepad windows (just start a couple of them with no file specifed).

You can pass different regex expressions to the script. Edit as makes sense to your needs.

Show-WindowByName

#Requires -RunAsAdministrator

[CmdletBinding()]
param (
    [string]
    $ProcessNameRegEx = 'notepad',

    [string]
    $WindowTitleRegEx = 'unt'
)

$cs = @" 
using System; 
using System.Runtime.InteropServices;

namespace User32
{
    public static class WindowManagement
    {
        [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
        public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);

        public const int SWP_NOSIZE = 0x01, SWP_NOMOVE = 0x02, SWP_SHOWWINDOW = 0x40, SWP_HIDEWINDOW = 0x80;

        public static void SetWindowPosWrappoer(IntPtr handle, int x, int y, int width, int height)
        {
            if (handle != null)
            { 
                SetWindowPos(handle, 0, x, y, 0, 0, SWP_NOSIZE | SWP_HIDEWINDOW);

                if (width > -1 && height > -1)
                    SetWindowPos(handle, 0, 0, 0, width, height, SWP_NOMOVE);

                SetWindowPos(handle, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW);
            }
        }

        [DllImport("user32.dll", EntryPoint = "ShowWindow")]
        public static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);

        public static void ShowWindowWrapper(IntPtr handle, int nCmdShow)
        {
            if (handle != null)
            { 
                ShowWindow(handle, nCmdShow);
            }
        }

        [DllImport("user32.dll", EntryPoint = "SetForegroundWindow")]
        public static extern IntPtr SetForegroundWindow(IntPtr hWnd);

        public static void SetForegroundWindowWrapper(IntPtr handle)
        {
            if (handle != null)
            { 
                SetForegroundWindow(handle);
            }
        }
    }
}
"@ 

Add-Type -TypeDefinition $cs -Language CSharp -ErrorAction SilentlyContinue


function Move-Window
{
    param (
        [int]$MainWindowHandle,
        [int]$PosX,
        [int]$PosY,
        [int]$Height,
        [int]$Width
    )

    if($MainWindowHandle -ne [System.IntPtr]::Zero)
    {
        [User32.WindowManagement]::SetWindowPosWrappoer($MainWindowHandle, $PosX, $PosY, $Width, $Height);
    }
    else
    {
      throw "Couldn't find the MainWindowHandle, aborting (your process should be still alive)"
    }
}


function Show-Window
{
    param (
        [int]$MainWindowHandle,
        [int]$CmdShow
    )

    if($MainWindowHandle -ne [System.IntPtr]::Zero)
    {
        [User32.WindowManagement]::ShowWindowWrapper($MainWindowHandle, $CmdShow);
        [User32.WindowManagement]::SetForegroundWindowWrapper($MainWindowHandle);
    }
    else
    {
      throw "Couldn't find the MainWindowHandle, aborting (your process should be still alive)"
    }
}


$windows = Get-Process | ? {$_.ProcessName -match $ProcessNameRegEx -and $_.MainWindowTitle -match $WindowTitleRegEx} | Select -Last 100 | Select Id, MainWindowTitle, MainWindowHandle | Sort MainWindowTitle

$h = 180
$w = 1500
$x = 400
$y = 800
$deltax = 80
$deltay = 180

foreach ($window in $windows)
{
    Move-Window $window.MainWindowHandle $x $y $h $w
    Show-Window $window.MainWindowHandle 5
    #$x -= $deltax
    $y -= $deltay
}
Kory Gill
  • 6,993
  • 1
  • 25
  • 33
  • Hmm I havent been able to get this to work. I assume the only change I need to make is param ( [string] $ProcessNameRegEx = 'concentr.exe', [string] $WindowTitleRegEx = 'Citrix Connection Manager' – user770022 Mar 02 '17 at 22:27
  • I am going to guess the '.' in your input was interpreted not as you expected since this is a regex. – Kory Gill Mar 02 '17 at 22:59
  • @KoryGill This won't work as written. The variables called are not used in the code. – Bewc Feb 27 '19 at 16:42
  • Can you elaborate on this @Bewc? Also, I updated the sample code to work on 2 new notepad windows (with Untitled as the window title). Is this what you were talking about? – Kory Gill Feb 27 '19 at 18:52
  • @KoryGill that was fast! It works for me now. Appreciate the responsiveness! – Bewc Feb 27 '19 at 20:10