Home > Mobile >  Bring a program to foreground or start it
Bring a program to foreground or start it

Time:12-21

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?

CodePudding user response:

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).

  • Related