public/Position-ExplorerWindow.ps1
function Position-ExplorerWindow { <# .SYNOPSIS Opens, resizes, and arranges multiple Explorer Windows at specified paths in a grid fashion to fit a screen, or multiple screens. .DESCRIPTION Opens, resizes, and arranges multiple Explorer Windows at specified paths in a grid fashion to fit a screen, or multiple screens. .PARAMETER Paths # Defines the paths (folders) the Explorer windows should show. .PARAMETER ModeEasy # Simple Mode. In this mode, most defaults are used. # If you hate configurations, simply use this with -Paths .PARAMETER DestinationScreenWidth # Resolution of the Destination Screen (think of this as block of pixels, and not necesarily a Monitor's resolution) where the Explorer windows will reside. # For Single-Monitor setups, in most cases should match your Monitor's resolution. # For Multi-Monitor setups, you may also think of this a pooling of a block of pixels spanning your screen(s). You may use 3840 x 1080 to pool multiple monitor pixels together, or use 640 x 480 to select a smaller pool .PARAMETER DestinationScreenHeight # Resolution of the Destination Screen (think of this as block of pixels, and not necesarily a Monitor's resolution) where the Explorer windows will reside. # For Single-Monitor setups, in most cases should match your Monitor's resolution. # For Multi-Monitor setups, you may also think of this a pooling of a block of pixels spanning your screen(s). You may use 3840 x 1080 to pool multiple monitor pixels together, or use 640 x 480 to select a smaller pool .PARAMETER DestinationMonitor # Physical position of the Destination Monitor where the Explorer windows will open # NOTE: # This is ignored if you have only 1 monitor. # Possible values: 'M', L', 'R', 'T', 'B' # E.g. 'M' - Destination Monitor is the Main Monitor # E.g. 'L' - Destination Monitor is to the left of the Main Monitor # E.g. 'R' - Destination Monitor is to the right of the Main Monitor # E.g. 'T' - Destination Monitor is to the top of the Main Monitor # E.g. 'B' - Destination Monitor is to the bottom of the Main Monitor # Default: 'M' .PARAMETER Rows # Define the number of rows of Explorer instances # E.g. 4 - a maximum of four explorer instances will be stacked vertically in a column. The 5th-8th windows will be stacked on the next column to the right of the previous column. And so on. # Default: 4 .PARAMETER Cols # Define the number of columns of Explorer instances # If a value greater than 1 is specified, columns of x windows will stack horizontally (where x is a defined in $Rows) # E.g. A value of 2 means that 2 columns of x explorer instances will be stacked horizontally # Default: 2 .PARAMETER OffsetLeft # How many pixels left/right the Explorer instances should be shifted from the Top-Left Corner(0,0) of the Destination Monitor. Useful in the case of multiple-monitor setups. # Best to be left as default (left-most of Destination screen) # E.g. Single Monitor setups: # 0 positions the windows on the Main monitor, starting from its leftmost edge # E.g. Multi-Monitor setups: # 0 positions the windows on the Destination monitor, starting from its leftmost edge. # x the windows the windows on the Destination Monitor, x pixels right of its leftmost edge. # -x positions the windows on the Destination Monitor, x pixels left of its leftmost edge. # Default: 0 .PARAMETER OffsetTop # How many pixels up/down the Explorer instances should be shifted from the Top-Left Corner(0,0) of the Destination Monitor. Useful in the case of multiple-monitor setups. # Best to be left as default (The very top of destination screen) # E.g. Single Monitor setups: # 0 positions the windows on the Main monitor, starting from its topmost edge # E.g. Multi-Monitor setups: # 0 positions the windows on the Destination monitor, starting from its topmost edge. # y the windows the windows on the Destination Monitor, x pixels down of its topmost edge. # -y positions the windows on the Destination Monitor, x pixels up of its topmost edge. # Default: 0 .PARAMETER Flow # Arrangement of Explorer Windows # Whether windows should flow left-to-right, or top-down fashion # 'X' - Left-to-Right fashion. # --------- # | 1 | 2 | # --------- # | 3 | 4 | # --------- # 'Y' - Top-Down fashion # --------- # | 1 | 3 | # --------- # | 2 | 4 | # --------- # Default: 'Y' .EXAMPLE Example 1a: This opens 4 windows: all 4 windows stacked vertically, occupying a total of half your Main full-HD Screen. Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 1920 -DestinationScreenHeight 1080 -DestinationMonitor 'M' -Rows 4 -Cols 2 -Flow 'Y' Example 1b: This is the same as Example 1, except instead of stacking vertically, windows flow in a zig-zag fashion: the first 2 windows are stacked horizontally in one row, then the next 2 are stacked horintally on the next row below. Each window's width is 1/2 the screen's width, and height 1/4 the screen's height. Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 1920 -DestinationScreenHeight 1080 -DestinationMonitor 'M' -Rows 4 -Cols 2 -Flow 'X' Example 2: This opens 4 windows: 3 windows stacked vertically on the left half of your Main full-HD Screen, and 1 window on the top occupying 1/3 of the right half of your Main full-HD Screen. Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 1920 -DestinationScreenHeight 1080 -DestinationMonitor 'M' -Rows 3 -Cols 2 -OffsetLeft 0 -OffsetTop 0 -Flow 'X' Example 3: This is the same as Example 1a, except the windows are on your Left Monitor. Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 1920 -DestinationScreenHeight 1080 -DestinationMonitor 'L' -Rows 4 -Cols 2 -Flow 'Y' Example 4: This is the same as Example 2, except the windows are on your Right Monitor. Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 1920 -DestinationScreenHeight 1080 -DestinationMonitor 'R' -Rows 3 -Cols 2 -OffsetLeft 0 -OffsetTop 0 -Flow 'X' Example 5: This is a nice hack if you have 2 screens. You want the windows to span two screens, rather than being confined to a single screen. Assumes your second screen is to the left of your Main Monitor. This will open 4 windows: 2 your Left Monitor, 2 on your Main monitor, arranged horizontally, each taking up 1/2 the width and 1/2 the height of each screen Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 3840 -DestinationScreenHeight 1080 -DestinationMonitor 'L' -Rows 2 -Cols 4 -OffsetLeft 1920 -OffsetTop 0 -Flow 'X' Example 6: This is a nice hack if you have 3 screens. You want the windows to span three screens, rather than being confined to a single screen. Assumes your second screen is to the left of your Main Monitor, and the third is to the right of your Main Monitor. This will open 6 windows: There will be on the first row, 2 windows your Left Monitor, 2 on your Main monitor, 2 on your Right Monitor, arranged horizontally, each taking up 1/3 the width and 1/3 the height of each screen Position-ExplorerWindow -paths @('D:\My Data Folder\Data1', 'D:\My Data Folder\Data2', 'D:\My Data Folder\Data3', 'D:\My Data Folder\Data\', '\\MYSERVER\public', '\\192.168.0.1\share') -DestinationScreenWidth 5760 -DestinationScreenHeight 1080 -DestinationMonitor 'L' -Rows 3 -Cols 3 -OffsetLeft 3840 -OffsetTop 0 -Flow 'X' .NOTES ################################################################################################################################ # Dependencies: # # - UIAutomation PS module: https://uiautomation.codeplex.com/wikipage?title=Getting%20a%20window&referringTitle=Documentation # ################################################################################################################################ #> [CmdletBinding()] param( [Parameter(Mandatory=$False,Position=0)] [switch]$ModeEasy , [Parameter(Mandatory=$True,Position=1)] [String[]]$Paths , [Parameter(Mandatory=$False,Position=2)] #[ValidateRange(640, [int]::MaxValue)] [Int]$DestinationScreenWidth , [Parameter(Mandatory=$False,Position=3)] #[ValidateRange(360, [int]::MaxValue)] [Int]$DestinationScreenHeight , [Parameter(Mandatory=$False,Position=4)] [ValidateSet('M', 'L', 'R', 'T', 'B')] [String]$DestinationMonitor = 'M' , [Parameter(Mandatory=$False,Position=5)] #[ValidateRange(1, [int]::MaxValue)] [Int]$Rows = 4 , [Parameter(Mandatory=$False,Position=6)] #[ValidateRange(1, [int]::MaxValue)] [Int]$Cols = 2 , [Parameter(Mandatory=$False,Position=7)] #[ValidateRange(0, [int]::MaxValue)] [Int]$OffsetLeft = 0 , [Parameter(Mandatory=$False,Position=8)] #[ValidateRange(0, [int]::MaxValue)] [Int]$OffsetTop = 0 , [Parameter(Mandatory=$False,Position=9)] [ValidateSet('X', 'Y')] [String]$Flow = 'Y' ) begin { # NOTE: No longer using UIAutomation Module. # Import Dependency - UIAutomation Module # try { # Import-Module UIAutomation -ErrorAction Stop # [UIAutomation.Preferences]::HighlightParent = $False # [UIAutomation.Preferences]::Highlight = $False # }catch { # throw $_.Exception.Message # } } process { try { # Validate powershell version if ((Get-PowershellVersion -Major) -gt 5) { throw "Module is only supported on Powershell v5 or lower." } # Validate paths foreach ($path in $paths) { if (! (Test-Path $path) ) { Write-Warning "Path $path does not exist. Ignoring path." -ErrorAction Continue } } if ($Paths.Count -gt ($Rows * $Cols)) { throw "Number of paths is greater than rows*cols ($Rows*$Cols). Increase the number of rows and columns." } # Get all main monitors resolution # Doesn't work for Windows 7 PSv2 #$mainMonitor = Get-Wmiobject Win32_Videocontroller #$mainMonitorWidth = $mainMonitor.CurrentHorizontalResolution #$mainMonitorHeight = $mainMonitor.CurrentVerticalResolution # Get all main monitors resolution # From: https://stackoverflow.com/questions/7967699/get-screen-resolution-using-wmi-powershell-in-windows-7/7968063#7968063 # The returned screen objects appears to be in order of the physical position of the monitors, from left to right, # regardless of what the monitor's ID in Control Panel's / Settings 'Identify' feature shows. if (Add-Type -AssemblyName System.Windows.Forms -PassThru) { $screens = @( Get-AllScreens ) }else { throw "Failed to load assembly: System.Windows.Forms" } # Detect get the Main Monitor's resolution "`n[Detecting Main Monitor Resolution]" | Write-Verbose $mainMonitor = $screens | Where-Object { $_.Primary } | Select-Object -First 1 if (!$mainMonitor) { $DestinationScreenWidth = $mainMonitorWidth = 1920 $DestinationScreenHeight = $mainMonitorHeight = 1080 Write-Warning "Unable to auto-detect main monitor's resolution. Assuming 1920 x 1080." }else { # Use working area instead of bounds #$mainMonitorWidth = $mainMonitor.Bounds.Width #$mainMonitorHeight = $mainMonitor.Bounds.Height $mainMonitorWidth = $mainMonitor.WorkingArea.Width $mainMonitorHeight = $mainMonitor.WorkingArea.Height "Main Monitor Resolution: $mainMonitorWidth x $mainMonitorHeight" | Write-Verbose # In simple mode, consider the destination screen (i.e. pixel pool) to be the same as the main monitor's resolution if ($ModeEasy) { $DestinationScreenWidth = $mainMonitorWidth $DestinationScreenHeight = $mainMonitorHeight } } # Determine number of monitors $numMonitors = if ($screens) { $screens.Count } else { 1 } # Doesn't work on Windows 7 PSv2 #$numMonitors = (Get-WmiObject WmiMonitorID -Namespace root\wmi).Count "[Position-ExplorerWindow options]" | Write-Verbose "Paths: " | Write-Verbose $Paths | ForEach-Object { " $($_.Trim())" } | Write-Verbose "DestinationScreenWidth: $DestinationScreenWidth" | Write-Verbose "DestinationScreenHeight: $DestinationScreenHeight" | Write-Verbose "DestinationMonitor: $DestinationMonitor" | Write-Verbose "Rows: $Rows" | Write-Verbose "Cols: $Cols" | Write-Verbose "ForegroundColor: $OffsetLeft" | Write-Verbose "OffsetTop: $OffsetTop" | Write-Verbose "Flow: $Flow" | Write-Verbose # Determine the Window Group Starting Position, each window's dimension "`n[Calculating Window Group Starting Position, each window's dimension]" | Write-Verbose $params = @{ Paths = $Paths NumMonitors = $NumMonitors DestinationMonitor = $DestinationMonitor MainMonitorWidth = $mainMonitorWidth MainMonitorHeight = $mainMonitorHeight Rows = $Rows Cols = $Cols DestinationScreenWidth = $DestinationScreenWidth DestinationScreenHeight = $DestinationScreenHeight OffsetLeft = $OffsetLeft OffsetTop = $OffsetTop Flow = $Flow } $windowPositions = Get-WindowPositions @params foreach ($windowPosition in $windowPositions) { # Path count "[Opening Windows]" | Write-Verbose # Debug "My Coordinates (left, top): ($( $windowPosition['left'] ), $( $windowPosition['top'] )))" | Write-Verbose "My Dimensions (width, height): $( $windowPosition['width'] ) x $( $windowPosition['height'] )" | Write-Verbose # We are going to use difference objects of explorer.exe ################# # Start-Process # ################# # Start-Process: https://ss64.com/ps/start-process.html # explorer.exe: https://ss64.com/nt/explorer.html # Note: A newly started explorer.exe subsequently spawns a child explorer.exe before killing itself. # Start a new explorer.exe process and get its pid "Starting Explorer process..." | Write-Verbose $parent = Start-Process -FilePath explorer -ArgumentList "/separate,`"$( $windowPosition['path'] )`"" -PassThru $parentPid = $parent.Id # Skip over this path if we didn't get a newly started explorer.exe if (!$parentPid) { Write-Warning "Could not find parent explorer.exe. Skipping." continue } # Get the explorer processes before launching "Getting Explorer processes..." | Write-Verbose # Skip over this path if we didn't get any explorer instances. $processesPrev = Get-Process -Name explorer -ErrorAction SilentlyContinue if (!$processesPrev) { Write-Warning "Could not find parent explorer.exe. Skipping."; continue } $processesPrev | Format-Table | Out-String | % { $_.Trim() } | Write-Verbose # Get the pid of the spawned child explorer.exe. This is achieved by getting a diff-object of explorer.exe processes until we find the spawned child's pid "Getting spawned child process..." | Write-Verbose # Loop count $childPid = $null $loopCount = 0 $SleepMilliseconds = 10 while ($null -eq $childPid) { $loopCount++ # Get explorer processes after starting the new explorer process "`t`tGetting Explorer processes..." | Write-Verbose $processesAfter = Get-Process -Name explorer -ErrorAction SilentlyContinue $processesAfter | Format-Table | Out-String | Write-Verbose # Get the child process id from the difference object between two collections of explorer.exe # E.g. # Loop 0: $NULL # Loop 1: 7972 $diff = Compare-Object $processesPrev $processesAfter -Property Id | Where-Object { $_.SideIndicator -eq '=>'} | Select-Object -First 1 if ($diff) { $childPid = $diff.Id } "`t`t >Diff: $childPid" | Write-Verbose # Successfully found a child process id. Print a message if ($childPid) { "`tWe took $loopCount loops to get the child process id: $childPid" | Write-Verbose "`tWe found the child process" | Write-Verbose }else { Start-Sleep -Milliseconds $SleepMilliseconds # Stop looping if we can't find it if ($loopCount -eq 100) { "We took too many loops($loopCount) and $( $loopCount * $SleepMilliseconds )ms and to find the child explorer process." | Write-Verbose break } } } if ($childPid) { # Give some time before positioning and resizing window Start-Sleep -Milliseconds 100 # Try and reposition and resize Window "`tRepositioning and Resizing window..." | Write-Verbose $success = Position-ResizeWindow -ProcessId $childPid -Left $windowPosition['left'] -Top $windowPosition['top'] -Width $windowPosition['width'] -Height $windowPosition['height'] if ($success) { "`tSuccessfully repositioned and resized window." | Write-Verbose }else { Write-Warning "Failed to reposition and resize window. The window is not movable or not resizable." } }else { Write-Warning "Could not find a child explorer.exe instance. Unable to position and resize a new Explorer window for path: $Path" } } # End paths loop }catch { if ($ErrorActionPreference -eq 'Stop') { throw }else { Write-Error -ErrorRecord $_ } } } # End process # } |