Functions/GenXdev.Windows/Get-WindowPosition.ps1

<##############################################################################
Part of PowerShell module : GenXdev.Windows
Original cmdlet filename : Get-WindowPosition.ps1
Original author : René Vaessen / GenXdev
Version : 1.302.2025
################################################################################
Copyright (c) René Vaessen / GenXdev
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
    http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
################################################################################>

################################################################################
# Part of PowerShell module : GenXdev.Windows
# Original cmdlet filename : Get-WindowPosition.ps1
# Original author : Ren� Vaessen / GenXdev
# Version : 1.302.2025
################################################################################
# Copyright (c) Ren� Vaessen / GenXdev
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################
<#
.SYNOPSIS
Gets the position and state information of windows.
 
.DESCRIPTION
Retrieves detailed information about window positioning, size, monitor placement,
docking state, and focus status for specified processes or window handles.
 
.PARAMETER ProcessName
The process name of the window to get position information for
 
.PARAMETER Process
Process or processes whose windows need position information
 
.PARAMETER WindowHelper
Window helper object for direct window manipulation
 
.EXAMPLE
Get-WindowPosition -ProcessName notepad
Gets position information for all notepad windows
 
.EXAMPLE
Get-Process notepad | Get-WindowPosition
Gets position information for notepad processes via pipeline
 
.EXAMPLE
Get-Window -ProcessName notepad | Get-WindowPosition
Gets position information using window helper objects
#>

################################################################################
function Get-WindowPosition {

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [Alias('gwp')]
    param(
        ########################################################################
        [parameter(
            ParameterSetName = 'ProcessName',
            Mandatory = $false,
            Position = 0,
            HelpMessage = 'The process name of the window to get position for',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ValueFromRemainingArguments = $false
        )]
        [SupportsWildcards()]
        [Alias('Name')]
        [string] $ProcessName,
        ########################################################################
        [parameter(
            ParameterSetName = 'Process',
            Mandatory = $false,
            HelpMessage = 'The process of the window to get position for',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ValueFromRemainingArguments = $false
        )]
        [ValidateNotNull()]
        [System.Diagnostics.Process] $Process,
        ########################################################################
        [parameter(
            ParameterSetName = 'WindowHelper',
            Mandatory = $false,
            HelpMessage = 'Get-Window helper object for direct window manipulation',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ValueFromRemainingArguments = $false
        )]
        [ValidateNotNull()]
        [GenXdev.Helpers.WindowObj[]] $WindowHelper
        ########################################################################
    )

    begin {

        # retrieve all available monitors from the system
        $allScreens = @([WpfScreenHelper.Screen]::AllScreens |
                Microsoft.PowerShell.Core\ForEach-Object { $PSItem })

        # log the total number of detected monitors for debugging
        Microsoft.PowerShell.Utility\Write-Verbose ("Found $($allScreens.Count) " +
            "monitors available for window positioning")

        # enumerate and log details of each available monitor
        for ($i = 0; $i -lt $allScreens.Count; $i++) {

            # get current monitor information for logging
            $screenInfo = $allScreens[$i]

            # log monitor specifications including size, position and device name
            Microsoft.PowerShell.Utility\Write-Verbose ("Monitor ${i}: " +
                "$($screenInfo.WorkingArea.Width)x$($screenInfo.WorkingArea.Height) " +
                "at ($($screenInfo.WorkingArea.X),$($screenInfo.WorkingArea.Y)) " +
                "Device: $($screenInfo.DeviceName)")
        }

        # resolve target process based on the parameter set specified by the user
        switch ($PSCmdlet.ParameterSetName) {
            'ProcessName' {
                Microsoft.PowerShell.Utility\Write-Verbose ('ParameterSetName: ProcessName')

                # search for processes matching the specified name with active windows
                $foundProcess = Microsoft.PowerShell.Management\Get-Process `
                    -Name $ProcessName `
                    -ErrorAction SilentlyContinue |
                    Microsoft.PowerShell.Core\Where-Object `
                        -Property 'MainWindowHandle' `
                        -NE 0 |
                    Microsoft.PowerShell.Utility\Sort-Object `
                        -Property 'StartTime' `
                        -Descending |
                    Microsoft.PowerShell.Utility\Select-Object `
                        -First 1

                # validate that a matching process with a window was found
                if ($null -eq $foundProcess) {
                    Microsoft.PowerShell.Utility\Write-Verbose ("No process found with name '$ProcessName'")
                    Microsoft.PowerShell.Utility\Write-Error ('No process found with ' + "name '$ProcessName'")
                }
                else {
                    Microsoft.PowerShell.Utility\Write-Verbose ("Process found: $($foundProcess.ProcessName) with ID $($foundProcess.Id)")
                    $Process = $foundProcess
                    Microsoft.PowerShell.Utility\Write-Verbose ('Found process: ' + "$($Process.ProcessName) with ID $($Process.Id)")
                }
                break;
            }
            'Process' {
                Microsoft.PowerShell.Utility\Write-Verbose ('ParameterSetName: Process')
                break;
            }
            'WindowHelper' {
                Microsoft.PowerShell.Utility\Write-Verbose ('ParameterSetName: WindowHelper')
                break;
            }
            default {
                Microsoft.PowerShell.Utility\Write-Verbose ('ParameterSetName: default (using PowerShell main window process)')
                # default to powershell main window process
                $Process = (GenXdev.Windows\Get-PowershellMainWindowProcess)
                break;
            }
        }
    }

    process {

        # begin processing windows that require position information
        if ($null -ne $Process) {

            # log which type of processing is being performed
            Microsoft.PowerShell.Utility\Write-Verbose ("Processing window for process: $($Process.ProcessName) (Id: $($Process.Id))")

            # get window object using either provided helper or process main window
            $window = $WindowHelper ? $WindowHelper : (GenXdev.Windows\Get-Window -ProcessId ($Process.Id))

            # validate window object detection and log detailed window information
            if ($null -ne $window) {

                # log comprehensive window details for debugging and troubleshooting
                Microsoft.PowerShell.Utility\Write-Verbose ("`r`n-----------`r`nWindow object found:`r`n" +
                    "Title: $($window.Title)`r`n" +
                    "Handle: $($window.Handle)`r`n" +
                    "Position: $($window.Position().X),$($window.Position().Y)`r`n" +
                    "Size: $($window.Size().Width)x$($window.Size().Height)`r`n-----------")

                # get window position and size
                $position = $window.Position()
                $size = $window.Size()

                # get monitor information
                $monitorIndex = $window.GetCurrentMonitor()
                $monitor = $monitorIndex + 1  # 1-based

                # get current screen
                $currentScreen = $allScreens[$monitorIndex]
                if ($null -eq $currentScreen) {
                    $currentScreen = $allScreens[0]  # fallback to primary
                }

                # detect docking state
                $dockedLeft = $false
                $dockedTop = $false
                $dockedRight = $false
                $dockedBottom = $false
                $dockedLeftTop = $false
                $dockedTopRight = $false
                $dockedBottomLeft = $false
                $dockedBottomRight = $false

                # calculate relative position within monitor's work area
                $workArea = $currentScreen.WorkingArea
                $relativeX = ($position.X - $workArea.X) / $workArea.Width
                $relativeY = ($position.Y - $workArea.Y) / $workArea.Height
                $relativeWidth = $size.Width / $workArea.Width
                $relativeHeight = $size.Height / $workArea.Height

                $tolerance = 0.1  # 10% tolerance for position detection
                $sizeTolerance = 0.4  # 40% minimum size to consider positioned

                # detect docking based on relative position and size
                if ($relativeWidth -ge $sizeTolerance) {
                    if ($relativeX -le $tolerance) {
                        $dockedLeft = $true
                    } elseif (($relativeX + $relativeWidth) -ge (1.0 - $tolerance)) {
                        $dockedRight = $true
                    }
                }
                if ($relativeHeight -ge $sizeTolerance) {
                    if ($relativeY -le $tolerance) {
                        $dockedTop = $true
                    } elseif (($relativeY + $relativeHeight) -ge (1.0 - $tolerance)) {
                        $dockedBottom = $true
                    }
                }

                # detect corner docking
                if ($dockedLeft -and $dockedTop) {
                    $dockedLeftTop = $true
                }
                if ($dockedTop -and $dockedRight) {
                    $dockedTopRight = $true
                }
                if ($dockedBottom -and $dockedLeft) {
                    $dockedBottomLeft = $true
                }
                if ($dockedBottom -and $dockedRight) {
                    $dockedBottomRight = $true
                }

                # get window state
                $isMinimized = $window.IsMinimized()
                $isMaximized = $window.IsMaximized()
                $isHidden = -not $window.IsVisible()
                $isRestored = -not $isMinimized -and -not $isMaximized

                # get focus information
                $foregroundWindow = [GenXdev.Helpers.WindowObj]::GetFocusedWindow()
                $hasFocus = $false
                $isInForeground = $false
                if ($null -ne $foregroundWindow) {
                    $hasFocus = ($window.Handle -eq $foregroundWindow.Handle)
                    $isInForeground = $hasFocus
                }

                # create result hashtable
                $result = @{
                    Left             = $position.X
                    Top              = $position.Y
                    Width            = $size.Width
                    Height           = $size.Height
                    MonitorIndex     = $monitorIndex
                    Monitor          = $monitor
                    DockedLeft       = $dockedLeft
                    DockedTop        = $dockedTop
                    DockedBottom     = $dockedBottom
                    DockedRight      = $dockedRight
                    DockedLeftTop    = $dockedLeftTop
                    DockedTopRight   = $dockedTopRight
                    DockedBottomLeft = $dockedBottomLeft
                    DockedBottomRight= $dockedBottomRight
                    IsMinimized      = $isMinimized
                    IsMaximized      = $isMaximized
                    IsHidden         = $isHidden
                    IsRestored       = $isRestored
                    WindowHandle     = $window.Handle
                    Process          = $Process
                    HasFocus         = $hasFocus
                    IsInForeground   = $isInForeground
                }

                # return the result
                $result

            } else {
                Microsoft.PowerShell.Utility\Write-Verbose ("No window object available for process $($Process.ProcessName)")
            }
        } else {
            Microsoft.PowerShell.Utility\Write-Verbose ('No process object available')
        }
    }

    end {
    }
}
################################################################################