-
-
Save lalibi/3762289efc5805f8cfcf to your computer and use it in GitHub Desktop.
| function Set-WindowState { | |
| <# | |
| .SYNOPSIS | |
| Set the state of a window. | |
| .DESCRIPTION | |
| Set the state of a window using the `ShowWindowAsync` function from `user32.dll`. | |
| .PARAMETER InputObject | |
| The process object(s) to set the state of. Can be piped from `Get-Process`. | |
| .PARAMETER State | |
| The state to set the window to. Default is 'SHOW'. | |
| .PARAMETER SuppressErrors | |
| Suppress errors when the main window handle is '0'. | |
| .PARAMETER SetForegroundWindow | |
| Set the window to the foreground | |
| .PARAMETER ThresholdHours | |
| The number of hours to keep the window handle in memory. Default is 24. | |
| .EXAMPLE | |
| Get-Process notepad | Set-WindowState -State HIDE -SuppressErrors | |
| .EXAMPLE | |
| Get-Process notepad | Set-WindowState -State SHOW -SuppressErrors | |
| .LINK | |
| https://gist.github.com/lalibi/3762289efc5805f8cfcf | |
| .NOTES | |
| Original idea from https://gist.github.com/Nora-Ballard/11240204 | |
| #> | |
| [CmdletBinding(DefaultParameterSetName = 'InputObject')] | |
| param( | |
| [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] | |
| [Object[]] $InputObject, | |
| [Parameter(Position = 1)] | |
| [ValidateSet( | |
| 'FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE', | |
| 'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED', | |
| 'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL' | |
| )] | |
| [string] $State = 'SHOW', | |
| [switch] $SuppressErrors = $false, | |
| [switch] $SetForegroundWindow = $false, | |
| [int] $ThresholdHours = 24 | |
| ) | |
| Begin { | |
| $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 | |
| } | |
| $Win32ShowWindowAsync = Add-Type -MemberDefinition @' | |
| [DllImport("user32.dll")] | |
| public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); | |
| [DllImport("user32.dll", SetLastError = true)] | |
| public static extern bool SetForegroundWindow(IntPtr hWnd); | |
| '@ -Name "Win32ShowWindowAsync" -Namespace Win32Functions -PassThru | |
| $handlesFilePath = "$env:APPDATA\WindowHandles.json" | |
| $global:MainWindowHandles = @{} | |
| if (Test-Path $handlesFilePath) { | |
| $json = Get-Content $handlesFilePath -Raw | |
| $data = $json | ConvertFrom-Json | |
| $currentTime = Get-Date | |
| foreach ($key in $data.PSObject.Properties.Name) { | |
| $handleData = $data.$key | |
| if ($handleData -and $handleData.Timestamp) { | |
| try { | |
| $timestamp = [datetime] $handleData.Timestamp | |
| if ($currentTime - $timestamp -lt (New-TimeSpan -Hours $ThresholdHours)) { | |
| $global:MainWindowHandles[[int] $key] = $handleData | |
| } | |
| } catch { | |
| Write-Verbose "Skipping invalid timestamp for handle $key" | |
| } | |
| } else { | |
| Write-Verbose "Skipping entry for handle $key due to missing data" | |
| } | |
| } | |
| } | |
| } | |
| Process { | |
| foreach ($process in $InputObject) { | |
| $handle = $process.MainWindowHandle | |
| if ($handle -eq 0 -and $global:MainWindowHandles.ContainsKey($process.Id)) { | |
| $handle = [int] $global:MainWindowHandles[$process.Id].Handle | |
| } | |
| if ($handle -eq 0) { | |
| if (-not $SuppressErrors) { | |
| Write-Error "Main Window handle is '0'" | |
| } else { | |
| Write-Verbose ("Skipping '{0}' with id '{1}', because Main Window handle is '0'" -f $process.ProcessName, $process.Id) | |
| } | |
| continue | |
| } | |
| Write-Verbose ("Processing '{0}' with id '{1}' and handle '{2}'" -f $process.ProcessName, $process.Id, $handle) | |
| $global:MainWindowHandles[$process.Id] = @{ | |
| Handle = $handle.ToString() | |
| Timestamp = (Get-Date).ToString("o") | |
| } | |
| $Win32ShowWindowAsync::ShowWindowAsync($handle, $WindowStates[$State]) | Out-Null | |
| if ($SetForegroundWindow) { | |
| $Win32ShowWindowAsync::SetForegroundWindow($handle) | Out-Null | |
| } | |
| Write-Verbose ("» Set Window State '{1}' on '{0}'" -f $handle, $State) | |
| } | |
| } | |
| End { | |
| $data = [ordered] @{} | |
| foreach ($key in $global:MainWindowHandles.Keys) { | |
| if ($global:MainWindowHandles[$key].Handle -ne 0) { | |
| $data["$key"] = $global:MainWindowHandles[$key] | |
| } | |
| } | |
| $json = $data | ConvertTo-Json | |
| Set-Content -Path $handlesFilePath -Value $json | |
| } | |
| } |
I'm having an issue with using your inputobject. It's telling me that its null when I run this from a piped "Get-Process"
Question: Once you "HIDE" a window, its MainWindowHandle becomes 0, ... so how do you make it visible again?
Start-Process 'notepad.exe' # Start notepad.
$hwndBefore = (Get-Process -Name 'notepad').MainWindowHandle # Get main window handle.
Set-WindowStyle -MainWindowHandle $hwndBefore -Style HIDE # Hide it.
$hwndAfter = (Get-Process -Name 'notepad').MainWindowHandle # Handle now = 0
Set-WindowStyle -MainWindowHandle $hwndAfter -Style SHOW # No effect
Modify the code so that BEFORE you minimize it, you store info about the process (PID or ProcessName). That way you can reference it.
Thanks, Dude, working like a charm on Windows 8, I need the alt - tab simulation but I was able to solve my issue minimizing the unwanted app and bring to the front the desired app.
I can't seem to restore a minimized window. Can someone post an example code on how to achieve this?
After the window is minimized, if I do this:
$hwndAfter = (Get-Process -Name 'notepad').MainWindowHandle
I can get the mainwindowhandle just fine. So why Can't I just do this?
Set-WindowStyle -MainWindowHandle $hwndAfter -Style RESTORE
OR this:
Set-WindowStyle -MainWindowHandle $hwndAfter -Style SHOW
?
@chaoscreater RESTORE should work after you minimize a window. It doesn't work when you use HIDE, where MainWindowHandle becomes 0.
Updated the code so that it keeps the MainWindowHandle of the processes (for the current session) it handles. So now (in the same session) you can HIDE and then SHOW a window.
The implementation is not that elegant at the moment, it pollutes the global space with a variable (MainWindowHandle)
Updated the code so that it keeps the
MainWindowHandleof the processes (for the current session) it handles. So now (in the same session) you canHIDEand thenSHOWa window.The implementation is not that elegant at the moment, it pollutes the global space with a variable (
MainWindowHandle)
Hi, thanks for updating the script. I can't figure out how to use it, I'm getting the following error:
Cannot convert argument "hWnd", with value: "", for "ShowWindowAsync" to type "System.IntPtr": "Cannot convert null to type "System.IntPtr"."
Could you provide an example of how to restore a minimized window please?
Update:
Nevermind, I got it.
$inputobject = get-process -name 'notepad'
Set-WindowState -inputObject $inputobject -State RESTORE
Or like this:
Get-Process notepad | Set-WindowState -State Hide
Get-Process notepad | Set-WindowState -State Show
Keep getting error "Main Window handle is '0' " when trying to run this on spotify, works Fine on notepad though. Maybe something to do with how spotify seems to be made up of 3 processes in task manager.
Image attached to show what I mean.
https://i.ibb.co/71nqXhx/Capture69.jpg
It's good, but although it sets the correct window state on the correct handle, what is still missing in certain $States is a command to bring the window to the foreground, e.g. $Win32ShowWindowAsync::SetForegroundWindow($handle) | Out-Null
@Duoquadragesimal, yes it's because of the multiple processes, it should work though. You get an error for each process that doesn't have a main window. I added a -SuppressErrors flag, so you can call it like this:
Get-Process spotify | Set-WindowState -State Show -SuppressErrors
Get-Process spotify | Set-WindowState -State Hide -SuppressErrors
@Nagidal added a flag for that too -SetForegroundWindow
Get-Process someprocess| Set-WindowState -State Show -SetForegroundWindow
Hmm. When I do this, the program gets hidden, but it does not show again
Get-Process program | Set-WindowState -State Hide -SuppressErrors
then
Get-Process program | Set-WindowState -State Show -SuppressErrors
Any idea?
Hmm. When I do this, the program gets hidden, but it does not show again
Get-Process program | Set-WindowState -State Hide -SuppressErrorsthenGet-Process program | Set-WindowState -State Show -SuppressErrorsAny idea?
You need to execute those two commands in the same session. Otherwise it won't work, since a "hidden" application doesn't have a MainWindowHandle.
MainWindowHandles are now stored in a .json file, in "$env:APPDATA\WindowHandles.json", and restored them every time the script is run. At this point there is no automatic way to clear this file, only manually.
.json now includes a timestamp for each entry and invalidates them, if a threshold is reached (default is 24 hours).
Wow, two years later, you still added a feature. That's awesome!
And I just realized I didn't respond, sorry about that lol.
Corrected issues with illegal characters and added pipeline support.