PowerShell Get process by its handle - powershell

$ActiveHandle = [UserWindows]::GetForegroundWindow()
$Process = Get-Process | ? {$_.MainWindowHandle -eq $ActiveHandle}
This code retrieves a title of the current active window. Problem is that it only filters processes by MainWindowHandle. For example, if my active handle is a popup from the same process, it doesn't return anything as the handle is not its main handle. How can I modify the code to check for ALL handles instead of just the main one? Or rather, how can I retrieve all process handles?
I do not want to use external tools like WASP.

You can use the GetWindowThreadProcessId Win32 API function for this:
# Define a type that allows us to call the relevant win32 api
$user32 = Add-Type -MemberDefinition #'
[DllImport("user32.dll", SetLastError=true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
'# -PassThru -Name user32Func
# prepare a variable to receive the target process id
$procId = 0
# call the function with, pass the handle as the first argument
$threadId = $user32::GetWindowThreadProcessId($ActiveHandle, [ref]$procId)
# If the owning thread and process was identified, the return-value will have been non-0
if($threadId) {
Write-Host "Found process $(Get-Process -Id $procId |% Name) with PID $procId"
}
else {
Write-Host "No owning process found"
}

Related

What is the Windows terminal command to check if a program is running?

I would like to create a Windows script that checks if a program "anyprogram.exe" is running. If the condition is true, it ends the process of that program.
How can I do this?
Use the Get-Process cmdlet in combination with Stop-Process:
# Note: Do NOT include ".exe" in the process name.
Get-Process -Name anyprogram -ErrorAction Ignore | Stop-Process
Note that Stop-Process forcefully stops (kills) a process, without giving it a chance to shut down gracefully.
GitHub issue #13664 discusses how graceful termination could be implemented as well.
If you need graceful termination - which may or may not be honored by the target process, however - use the standard Windows taskkill.exe utility (whose /f switch can be used to request forceful termination):
Get-Process -Name anyprogram -ErrorAction Ignore |
ForEach-Object { taskkill /pid $_.Id }
If - potentially forceful - termination must be guaranteed, try graceful termination first, then - after a timeout - fall back to forceful termination - see this answer.
(Get-Process | Select-Object ProcessName | Where { $_.ProcessName -eq "anyprogram" }).Count -gt 0
Gets a list of processes running, filters on the name, counts the number of programs running with that name. If that number is greater than zero then the return value is true. If that number is zero then the return value is false.
A word of caution: omit the .exe extension in the comparison $_.ProcessName -eq "anyprogram" because Get-Process omits the ".exe" extension from results.
I've done things within this realm years ago but I've been so heavily into my Mac world now that I'm hoping this is up to date enough or helpful.
Closing an app "gracefully" with WM_CLOSE:
$src = #'
using System;
using System.Runtime.InteropServices;
public static class Win32 {
public static uint WM_CLOSE = 0x10;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
'#
Add-Type -TypeDefinition $src
$zero = [IntPtr]::Zero
$p = #(Get-Process Notepad)[0]
[Win32]::SendMessage($p.MainWindowHandle, [Win32]::WM_CLOSE, $zero, $zero) > $null
Also, I found this post I had bookmarked, which just might be helpful in the sense of the "checking to see" if the app is running.
Stack Overflow: Continuously check if a process is running

Bring a program to foreground or start it

For a handful of programs I use frequently, I am trying to write me some functions or aliases which would check if this program is already running and bring its window to foreground, else start this program.
Usage example with np, a handle for notepad.exe:
PS> np
checks if notepad.exe is running (Get-Process -Name "notepad.exe") if not, it would start it. When Notepad is already running but my maximized console is in the foreground, I'd like to execute the same command again, but this time I want it to bring the already running notepad process to foreground, rather than start a new one.
In order to implement this, I created this class called Program which I would instantiate for every program I want to handle like this. Then I have a HashTable $knownprograms of instances of this class, and in the end I try to define functions for every program, so that I could just type two or three letters to the console to start a program or bring its running process back to foreground.
class Program {
[string]$Name
[string]$Path
[string]$Executable
[string[]]$Arguments
Program(
[string]$n,
[string]$p,
[string]$e,
[string[]]$a
){
$this.Name = $n
$this.Path = $p
$this.Executable = $e
$this.Arguments = $a
}
[string]FullPath(){
return ("{0}\{1}" -f $this.Path, $this.Executable)
}
[void]ShowOrStart(){
try {
# Adapted from https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/bringing-window-in-the-foreground
$Process = Get-Process -Name $this.Name -ErrorAction Stop
Write-Host "Found at least one process called $this.Name"
$sig = '
[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
'
$Mode = 4 # Will restore the window, not maximize it
$type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru
$hwnd = $process.MainWindowHandle
$null = $type::ShowWindowAsync($hwnd, $Mode)
$null = $type::SetForegroundWindow($hwnd)
} catch [Microsoft.PowerShell.Commands.ProcessCommandException] {
Write-Host "Did not find any process called $this.Name"
Invoke-Command -ScriptBlock { & $this.FullPath() $this.Arguments }
}
}
}
$knownprograms = #{}
$knownprograms.Add("np", [Program]::new(
"np",
"$Env:SystemRoot\System32",
"notepad.exe",
#())
)
$knownprograms.Add("pt", [Program]::new(
"pt",
"$Env:SystemRoot\System32",
"mspaint.exe",
#())
)
Function np {
[cmdletbinding()]
Param()
$knownprograms.np.ShowOrStart()
}
Function pt {
[cmdletbinding()]
Param()
$knownprograms.pt.ShowOrStart()
}
The idea would be that I would source this script in my profile.ps1 and then just use the pre-factored functions. However, it seems that this code always opens a new instance of the program, rather than using its running process. Maybe I need some sort of delayed evaluation, so that the ShowOrStart() method checks at the time of invocation of np or pt whether the associated process exists. Any ideas how to accomplish this?
The process name for notepad.exe is notepad.
Update
$knownprograms.Add("np", [Program]::new(
"notepad",
"$Env:SystemRoot\System32",
"notepad.exe",
#())
)
And this works as expected.
This would be probably interesting to register $sig once for all and not on every call (which will probably raise an error).

Mapping of process name and application name using powershell

I need to track foreground application with application name in window machine. I am using given code but it provide ProcessName not application name example ProcessName is "chrome" Apliication name is "Google Chrome". Either I get application name direclty or i able to mapped Application name with process name.Please help me into this
[CmdletBinding()]
Param(
)
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="AppTitle";Expression= {($_.MainWindowTitle)}}
}catch{
Write-Error "Failed to get active Window details. More info:$_"
}
Rather than give you the answer (since you may have more questions after) I will teach to to fish for yourself.
Lets say you have a variable and you want to see all of its properties you can get from it; run $variable |get-member
Now you see there are a lot of properties attached to your variable and you don't see any called "application name". So lets list all of the properties of this variable and see what gives us the value we are looking for.
For my example I will grab chrome to put into my variable so we are on the same page.
Here is the code I used to grab the variable to match what you would be working with (ignore this if you already have your variable you're working with).
$variable= Get-Process|? name -ilike chrome|select -first 1
Lets list all the properties
$variable|format-list *
Now we see there are 2 properties that list the name were looking for, Description and Product (for chrome either works, but i don't know which will work for your other use cases, possibly none). Lets grab Product and use that for your code, substituting processname in the select statement (the statement that selects what properties to keep/show in that variable) with the Product Property... Now that you know how, you can change if need be =)
[CmdletBinding()]
Param(
)
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 Product, #{Name="AppTitle";Expression= {($_.MainWindowTitle)}}
}catch{
Write-Error "Failed to get active Window details. More info:$_"
}

Sending keys to an open windows in PowerShell

I made a program that opens a certain program, then Ctrl+C it after x amount of time.
I am now using this [System.Windows.Forms.SendKeys]::SendWait("^{c}").
Will this target that certain window or just randomly send it to the current window?
How can I change it to a certain window?
This is my code:
Write-Host "Safe Botting V0.1"
Write-Host "Initializing..."
Start-Sleep -s 3
Write-Host "Program started successfully with no errors."
While($true)
{
Write-Host "Starting bot..."
Start-Sleep -s 3
Start-Process -FilePath E:\Documents\bot.exe
Write-Host "Bot started successfully"
$rnd = Get-Random -Minimum 1800 -Maximum 10800
Write-Host "The bot will run for:"
Write-Host $rnd
Start-Sleep -s $rnd
Write-Host "Bot will now stop!"
[System.Windows.Forms.SendKeys]::SendWait("^{c}")
Write-Host "Bot terminated"
Write-Host "Starting cooldown time"
$rnb = Get-Random -Minimum 14400 -Maximum 28800
Write-Host "The bot will cooldown for"
Write-host $rnb
Start-Sleep -s $rnb
Write-Host "Cooldown Finished, Restarting"
Start-Sleep -s 5
}
You could send the CTRL_C_EVENT signal to the process if you have the process id. In your case you can get that from Start-Process (read the docs if you don't know how to get the process id). It's also possible to get the process id from a Window Handle:
Find process id by window's handle
Sending the signal is non trivial, but thanks to #Nemo1024, #KindDragon, and Stack Overflow it's been worked out:
Can I send a ctrl-C (SIGINT) to an application on Windows?
Unfortunately, using the best approach I could find also terminated the calling PowerShell process and the only workaround I could come up with was to send the signal from a fresh PowerShell instance that I launch.
In PowerShell it looks something like this:
# be sure to set $ProcessID properly. Sending CTRL_C_EVENT signal can disrupt or terminate a process
$ProcessID = 1234
$encodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("Add-Type -Names 'w' -Name 'k' -M '[DllImport(""kernel32.dll"")]public static extern bool FreeConsole();[DllImport(""kernel32.dll"")]public static extern bool AttachConsole(uint p);[DllImport(""kernel32.dll"")]public static extern bool SetConsoleCtrlHandler(uint h, bool a);[DllImport(""kernel32.dll"")]public static extern bool GenerateConsoleCtrlEvent(uint e, uint p);public static void SendCtrlC(uint p){FreeConsole();AttachConsole(p);GenerateConsoleCtrlEvent(0, 0);}';[w.k]::SendCtrlC($ProcessID)"))
start-process powershell.exe -argument "-nologo -noprofile -executionpolicy bypass -EncodedCommand $encodedCommand"
Yes, I know this is VERY ugly.
Thanks to jimhark, I found a way to make it work without spawning a separate PowerShell process to send the Ctrl-C. It works if the PowerShell process sending the Ctrl-C also spawned, using for example Start-Process, the process it is sending the Ctrl-C to:
$ProcessID = 1234
$MemberDefinition = '
[DllImport("kernel32.dll")]public static extern bool FreeConsole();
[DllImport("kernel32.dll")]public static extern bool AttachConsole(uint p);
[DllImport("kernel32.dll")]public static extern bool GenerateConsoleCtrlEvent(uint e, uint p);
public static void SendCtrlC(uint p) {
FreeConsole();
AttachConsole(p);
GenerateConsoleCtrlEvent(0, p);
FreeConsole();
AttachConsole(uint.MaxValue);
}'
Add-Type -Name 'dummyName' -Namespace 'dummyNamespace' -MemberDefinition $MemberDefinition
[dummyNamespace.dummyName]::SendCtrlC($ProcessID) }
What made things work was sending the GenerateConsoleCtrlEvent to the desired process group instead of all processes that share the console of the calling process and AttachConsole back to the console of the parent of the current process.

How to execute a powershell script without stealing focus?

I made a powershell script that uses GetForegroundWindow() to identify which Window is the currently focused Window. Unfortunately, when the powershell script is executed (via Windows Task Scheduler or a hotkey), the script steals focus of the current foreground app; incorrectly using the Powershell window itself as the Foreground app, instead of the intended foreground app.
I even tried creating an EXE using PS2EXE "-noconsole" setting; however, the script still doesn't doesn't work.
Could someone please suggest a way to execute this script without changing focus of the current foreground Window?
My script code works during tests (ONLY if I add "Start-Sleep -s 5"; and, manually steal back the focus with alt-tab for the script to identify the correct foreground Window.
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class Tricks {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
function Set-WindowStyle {
param(
[Parameter()]
[ValidateSet('FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE',
'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED',
'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL')]
$Style = 'SHOW',
[Parameter()]
$MainWindowHandle = (Get-Process -Id $pid).MainWindowHandle
)
$WindowStates = #{
FORCEMINIMIZE = 11; HIDE = 0
MAXIMIZE = 3; MINIMIZE = 6
RESTORE = 9; SHOW = 5
SHOWDEFAULT = 10; SHOWMAXIMIZED = 3
SHOWMINIMIZED = 2; SHOWMINNOACTIVE = 7
SHOWNA = 8; SHOWNOACTIVATE = 4
SHOWNORMAL = 1
}
Write-Verbose ("Set Window Style {1} on handle {0}" -f $MainWindowHandle, $($WindowStates[$style]))
$Win32ShowWindowAsync = Add-Type -memberDefinition #"
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
"# -name "Win32ShowWindowAsync" -namespace Win32Functions -passThru
$Win32ShowWindowAsync::ShowWindowAsync($MainWindowHandle, $WindowStates[$Style]) | Out-Null
}
$a = [tricks]::GetForegroundWindow()
$title = get-process | ? { $_.mainwindowhandle -eq $a }
$title2 = $title | select -ExpandProperty ProcessName
if ($title2 -eq 'Kodi'){
Set-WindowStyle MINIMIZE $a;
if (Get-Process -Name Yatse2) {(Get-Process -Name Yatse2).MainWindowHandle | foreach { Set-WindowStyle MINIMIZE $_ }}
} ELSE {
$title.CloseMainWindow()
}
I know this question is old, but I've spent quite some time trying to figure out how to not lose focus from the current window for myself and found some information, so hopefully this will help future readers.
The easiest solution is just to literally simulate an Alt+Tab keypress from within your Powershell script instead of having to do it yourself. The following code comes from this StackOverflow answer:
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
[System.Windows.Forms.SendKeys]::SendWait("%{TAB}")
# Retrieve the window handles...
...where % represents the Alt modifier and {TAB} is interpreted as the tab key.
User homersimpson's answer works, but using reflection is a bit slow. You can speed things up by adding the Windows.Forms assembly directly...
Add-Type -AssemblyName System.Windows.Forms
...other imports
#Return focus to the original window.
[System.Windows.Forms.SendKeys]::SendWait("%{TAB}")
...Your Code Here
Again, the % represents Alt and you know what TAB is. You are effectively Alt-Tabbing the new window away, returning focus to your desired one.
Create task that can be run on demand in Task Scheduler and executes your powershell script
Make sure it is set to be run whether user is logged on or not
Create a shortcut that starts the task
That allows your script to be run without visible window