Skip to content

Instantly share code, notes, and snippets.

@lalibi
Forked from Nora-Ballard/Set-WindowState.ps1
Last active June 13, 2024 09:51
Show Gist options
  • Save lalibi/3762289efc5805f8cfcf to your computer and use it in GitHub Desktop.
Save lalibi/3762289efc5805f8cfcf to your computer and use it in GitHub Desktop.
Hide, Show, Minimize, Maximize, etc window from Powershell.
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
}
}
@Inventologist
Copy link

@ed1chandler

Modify the code so that BEFORE you minimize it, you store info about the process (PID or ProcessName). That way you can reference it.

@dearZander
Copy link

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.

@chaoscreater
Copy link

chaoscreater commented Aug 30, 2019

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

?

@lalibi
Copy link
Author

lalibi commented Nov 17, 2019

@chaoscreater RESTORE should work after you minimize a window. It doesn't work when you use HIDE, where MainWindowHandle becomes 0.

@lalibi
Copy link
Author

lalibi commented Nov 18, 2019

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)

@chaoscreater
Copy link

chaoscreater commented Dec 3, 2019

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)

@lalibi

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

@lalibi
Copy link
Author

lalibi commented Dec 21, 2019

Or like this:

Get-Process notepad | Set-WindowState -State Hide
Get-Process notepad | Set-WindowState -State Show

@Duoquadragesimal
Copy link

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

@fleetingbytes
Copy link

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

@lalibi
Copy link
Author

lalibi commented Dec 27, 2021

@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

@lalibi
Copy link
Author

lalibi commented Dec 27, 2021

@Nagidal added a flag for that too -SetForegroundWindow

Get-Process someprocess| Set-WindowState -State Show -SetForegroundWindow 

@DasGandlaf
Copy link

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?

@lalibi
Copy link
Author

lalibi commented Jun 14, 2022

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?

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.

@lalibi
Copy link
Author

lalibi commented May 24, 2024

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.

@lalibi
Copy link
Author

lalibi commented May 24, 2024

.json now includes a timestamp for each entry and invalidates them, if a threshold is reached (default is 24 hours).

@DasGandlaf
Copy link

Wow, two years later, you still added a feature. That's awesome!

And I just realized I didn't respond, sorry about that lol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment