Bring Powershell-Console to front from WinForms Bring Powershell-Console to front from WinForms powershell powershell

Bring Powershell-Console to front from WinForms


Thanks to @iRon's answer, i was able to figure it out, how i want it.For anyone curious, the problem is, you only can get the consoles MainwindowHandle as long as ShowDialog wasn't called.So i save the console Handle in a variable and i use the Form_Shown event to get the Form WindowHandle, since Form_Load still returns the Console Handle.

$sig = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);[DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);'$type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru[IntPtr]$handleConsole = (Get-Process -Id $pid).MainWindowHandle[void]$type::ShowWindowAsync($handleConsole, 4);[void]$type::SetForegroundWindow($handleConsole)Add-Type -AssemblyName System.Windows.Forms[System.Windows.Forms.Application]::EnableVisualStyles()$Form                            = New-Object system.Windows.Forms.Form$Form.ClientSize                 = '446,266'$Form.text                       = "Form"$Form.TopMost                    = $false$Form.Add_Shown({ $global:handleForm = (Get-Process -Id $pid).MainWindowHandle})$Button1                         = New-Object system.Windows.Forms.Button$Button1.text                    = "Clone ad-USer"$Button1.width                   = 60$Button1.height                  = 30$Button1.location                = New-Object System.Drawing.Point(75,29)$Button1.Font                    = 'Microsoft Sans Serif,10'$Button1.Add_Click({    [void]$type::ShowWindowAsync($handleConsole, 4);[void]$type::SetForegroundWindow($handleConsole)    Read-Host -Prompt "Please Enter a Value"    [void]$type::ShowWindowAsync($global:handleForm, 4);[void]$type::SetForegroundWindow($global:handleForm)})$Form.controls.AddRange(@($Button1))[void]$Form.ShowDialog()

Now, if i press the Button, to console pops up in front.After the User enter something into the Console, the Form comes to front again.


Unfortunately, I can't completely fix it, but maybe others might help you further based on my findings:

First of all, the process within the button click event is a different process space as where the parent PowerShell host runs in. This can be easily proven but revealing the $hwhd with Write-Host $hwnd in the Show-Process function and also calling the Show-Process function prior calling the ShowDialog:

Show-Process -Process (Get-Process -Id $pid) [void]$Form.ShowDialog()

In other words: to fix this part, you will need to the catch the parent $Pid from the PowerShell window first:

$Button1.Add_Click({    Show-Process -Process $MyProcess })$Form.controls.AddRange(@($Button1))$MyProcess = Get-Process -Id $pidShow-Process -Process $MyProcess[void]$Form.ShowDialog()

The above snippet works, but as soon I remove (or comment out) the line Show-Process -Process $MyProcess (at the host level), it breaks again...


As you've discovered, .MainWindowHandle is not a static property (from the linked docs; emphasis added):

The main window is the window opened by the process that currently has the focus [...]

Therefore, what the value of the current process' .MainWindowHandle property changes from the console-window handle to the WinForms window while the form is being displayed.[1]

Caching the console-window handle before you display the form is definitely an option, but there's an easier way, given that you're already using Add-Member with WinAPI P/Invoke declarations: The GetConsoleWindow() WinAPI function always returns the current process' console-window handle.

Additionally, your $Forms form instance has a .Handle property, which directly returns the form's window handle - no (Get-Process -Id $pid).MainWindowHandle call needed.

The following solution therefore needs no global or script-level variables and confines querying the window handles to the button-click event handler:

# P/Invoke signatures - note the addition of GetConsoleWindow():$sig = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);[DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();'$type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThruAdd-Type -AssemblyName System.Windows.Forms$Form = New-Object system.Windows.Forms.Form -Property @{  ClientSize = '446,266'  text = "Form"}$Button1 = New-Object system.Windows.Forms.Button -Property @{  text = "Test"  location = New-Object System.Drawing.Point(75, 29)}$Button1.Add_Click({    # Get this form's window handle.    $handleForm = $Form.Handle # More generically: $this.FindForm().Handle    # Get the console window's handle.    $handleConsole = $type::GetConsoleWindow()    # Activate the console window and prompt the user.    $null = $type::ShowWindowAsync($handleConsole, 4); $null = $type::SetForegroundWindow($handleConsole)    Read-Host -Prompt "Please Enter a Value"    # Reactivate this form.    $null = $type::ShowWindowAsync($handleForm, 4); $null = $type::SetForegroundWindow($handleForm)  })$Form.controls.AddRange(@($Button1))$null = $Form.ShowDialog()

[1] Note that a cached process object doesn't dynamically update its .MainWindowHandle value; you have to call .Refresh() manually. Because iRon's solution caches the current-process object before displaying the form, it still happens to reflect the console-window handle inside the button-click handler.