Reputation: 2959
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);
}
Upvotes: 23
Views: 55835
Reputation: 437608
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.
The gist of the solution is the following, using Notepad
as an example process name; the statement activates the (first) process by that name that has a non-empty window title (if you can assume that the target process always has a window title and can therefore be presumed to be visible, you can simplify to (Get-Process Notepad)[0].Id
):
$null = (New-Object -ComObject WScript.Shell).AppActivate(
(Get-Process Notepad).Where({ $_.MainWindowTitle }, 'First').Id
)
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
Upvotes: 16
Reputation: 7163
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
}
Upvotes: 3
Reputation: 307
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}')
Upvotes: 14
Reputation: 2959
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)
}
Upvotes: 16