Is there a simple way to position a window opened from PowerShell’s Invoke-Item?

huangapple go评论60阅读模式
英文:

Is there a simple way to position a window opened from PowerShell's Invoke-Item?

问题

I've found solutions to programmatically position windows. There's even my question (similar, not exact match). Nothing in the docs for Invoke-Item states anything about it. I don't like the advanced solutions due to the complexity (hopefully due to a general purpose and a, indeed, higher code standard). I'm looking for a lazy man's solution (possibly having same caveats and limitations).

我找到了解决方案以程序化地定位窗口。甚至有我的问题(相似但不完全相同)。在Invoke-Item的文档中没有提到这方面的内容。我不喜欢复杂的解决方案,因为它们可能是因为通用目的和更高的代码标准而复杂。我正在寻找一个懒人的解决方案(可能具有相同的注意事项和限制)。

I have a script that loops through an array of strings and attempts to open a window for each path found. I know the size of my screens. I don't need to ensure scalability nor interpolate to other systems. "Works on my machine" is perfectly sustainable in this case.

我有一个脚本,循环遍历字符串数组,并尝试为找到的每个路径打开一个窗口。我知道我的屏幕尺寸。我不需要确保可伸缩性,也不需要插值到其他系统。在这种情况下,“在我的机器上运行”是完全可行的。

$BaseDir = "C:\Blaha"
$SubDirs = @("monkey", "donkey", "wonkey")

foreach($Dir in $SubDirs){
$TargetDir = Join-Path $BaseDir $Dir
if(Test-Path $TargetDir) { Invoke-Item($TargetDir) }
}

I would like to be able to specify a fixed position for each window opened by the Invoke-Item statement. At this stage, I'm happy if each such is opened at the same location, say at 150px from the top and 230px from the left.

我希望能够为Invoke-Item语句打开的每个窗口指定固定位置。在这个阶段,如果每个窗口都在相同的位置打开,比如距离顶部150px,左侧230px,我会很满意。

英文:

I've found solutions to programmatically position windows. There's even my question (similar, not exact match). Nothing in the docs for Invoke-Item states anything about it. I don't like the advanced solutions due to the complexity (hopefully due to a general purpose and a, indeed, higher code standard). I'm looking for a lazy man's solution (possibly having same caveats and limitations).

I have a script that loops through an array of strings and attempts to open a window for each path found. I know the size of my screens. I don't need to ensure scalability nor interpolate to other systems. "Works on my machine" is perfectly sustainable in this case.

$BaseDir = "C:\Blaha"
$SubDirs = @("monkey", "donkey", "wonkey")

foreach($Dir in $SubDirs){
  $TargetDir = Join-Path $BaseDir $Dir
  if(Test-Path $TargetDir) { Invoke-Item($TargetDir) }
}

I would like to be able to specify a fixed position for each window opened by the Invoke-Item statement. At this stage, I'm happy if each such is opened at the same location, say at 150px from the top and 230px from the left.

Is that doable?

答案1

得分: 1

以下是翻译好的内容:

是否存在一种“简单”的方法的答案是:不,没有。

有非常复杂的方法可以实现,通常可以使它们大部分时间正常工作。JosefZ的回答就是其中一个复杂方法的示例。

请注意,创建窗口控件的应用程序决定了它们的位置。这些应用程序允许用户移动窗口并调整其大小,它们还根据活动屏幕、屏幕大小、其他窗口的位置以及窗口以前的使用历史自动定位。

因此,除非应用程序提供通过命令行或配置文件来提供窗口位置的机制,否则你只能采用一些复杂的解决方案。通常,这些解决方案包括:

  • 以提升的权限运行应用程序(作为管理员)。这是必要的,因为你实际上是干扰其他可能已提升的进程。

  • 查找要重新定位的窗口。有各种各样的搜索窗口的方法。你可以按标题查找,或者将启动前存在的窗口列表与启动后存在的窗口列表进行比较。你需要过滤掉隐藏的窗口和非顶层窗口。通过一些努力,你可以找到启动每个窗口的应用程序的文件名,并在筛选器中使用它。

  • 使用Windows用户API重新定位窗口。

经过所有这些步骤,它可能会起作用。或者,应用程序之后可能会将窗口移回原来的位置。

对于explorer.exe,不会启动新的应用程序。行为可能会有所不同,它可能会重用现有的窗口。

总之,这很少值得付出的努力,最好首先尝试反对这一要求。

英文:

The answer to whether there is a "simple" way, is No, there isn't.

There are very complex ways to make it happen, and you can get them to work MOST of the time. The answer from JosefZ is an example of one of the complicated methods.

See, the application that creates the windows controls where they are positioned. These applications allow users to move windows around and resize them, and they also do a lot to automatically position based on the active screen, the screen size, where other windows are positioned and the history of where windows were used in the past.

So, unless the application provides a mechanism to give window position through a command line or config file, you are left with some complicated solutions. Generally, these involve:

  • running an application with elevated privileges (as Administrator). This is necessary because you are, in effect, interfering with other processes, that may be elevated.

  • Find the window to reposition. There are various ways to search for the window. You can do it by title, or compare a list of windows that existed before launching to those that exist after. You need to filter out hidden windows and non-top level windows. Through some effort, you can find the filename of the application that launched each window, and use that in your filter.

  • use a windows user API to reposition the window.

After all that, it MIGHT work. Or, the application will just move the window right back to where it was afterwards.

For explorer.exe, no new application is launched. Behaviors can vary and it might reuse an existing window.

Bottom line is that this is rarely worth the effort involved, and pushing back on this requirement would be the first thing you should try.

答案2

得分: 0

Here is the translated content you requested:

**脚本**(需要改进的函数`Set-Window`,*在下面附上*)在新的资源管理器窗口中打开每个*子目录*,并将每个窗口向右和向下移动40个像素(仅调整大小以使屏幕截图清晰):

```powershell
if (-not (Get-Command -Name Set-Window -ErrorAction SilentlyContinue)) {
    . D:\PShell\Downloaded\WindowManipulation\Set-Window.ps1
}
$BaseDir = "C:\Blaha"
$BaseDir = "D:\PShell\DataFiles\Blaha"

$SubDirs = @("monkey", "donkey", "wonkey")
$XYcnt = 0
$XYinc = 32
$auxArray = [System.Collections.ArrayList]::new()
foreach ($Dir in $SubDirs) {
    $TargetDir = Join-Path -Path $BaseDir -ChildPath $Dir
    if (Test-Path $TargetDir -PathType Container) {
        Start-Process -FilePath "c:\Windows\explorer.exe" -ArgumentList "/root,$TargetDir"
        Start-Sleep -Seconds 3
        $rex = "^$Dir$|^$([regex]::Escape($TargetDir))$"
        $aux = Get-Process -Name explorer |
            Where-Object MainWindowTitle -match $rex
        if ($null -ne $aux) {
            $XYpos = $XYcnt * $XYinc
            [void]$auxArray.Add(
                $(Set-Window -Id $($aux.Id) -X $XYpos -Y $XYpos -Height 440 -Passthru)
            )
            $XYcnt++
        }
    }
}

$auxArray | Format-Table -AutoSize

结果D:\PShell\SO\76193105.ps1

Id ProcessName Size     TopLeft BottomRight WindowTitle
-- ----------- ----     ------- ----------- -----------
5152 explorer    1130,440 0,0     1130,440    monkey     
4180 explorer    1130,440 32,32   1162,472    donkey     
7016 explorer    1130,440 64,64   1194,504    wonkey     

重要信息:改进后的脚本Set-Window.ps1

Function Set-Window {
<#
.SYNOPSIS
检索/设置进程窗口的大小和坐标。

.DESCRIPTION
检索/设置进程窗口的大小(高度、宽度)和坐标(x、y)。

.NOTES
名称:Set-Window
作者:Boe Prox
版本历史:
1.0//Boe Prox 11/24/2015 初始构建
1.1//JosefZ   19.05.2018 正确处理提供的进程名称的更多进程实例
1.2//JosefZ   21.02.2019 添加参数`Id`
1.3//JosefZ   07.05.2023 更改输入参数的类型:
                    [int]$Id to [int[]]$Id
                    [string]$ProcessName to [string[]]$ProcessName
                  添加参数`Processes`
                  添加`MainWindowTitle`(注释属性)以输出

最新版本:
D:\PShell\Downloaded\WindowManipulation\Set-Window.ps1

.OUTPUTS
System.Management.Automation.PSCustomObject
System.Object

.EXAMPLE
Get-Process powershell | Set-Window -X 20 -Y 40 -Passthru -Verbose
VERBOSE: powershell(Id=11140,句柄=132410)

Id          : 11140
ProcessName : powershell
Size        : 1134,781
TopLeft     : 20,40
BottomRight : 1154,821

描述:设置进程PowerShell.exe的窗口坐标。

.EXAMPLE
$windowArray = Set-Window -Passthru
WARNING: cmd(1096)被最小化!坐标将不准确。

PS C:\&gt;$windowArray | Format-Table -AutoSize

  Id ProcessName    Size     TopLeft       BottomRight  
  -- -----------    ----     -------       -----------  
1096 cmd            199,34   -32000,-32000 -31801,-31966
4088 explorer       1280,50  0,974         1280,1024    
6880 powershell     1280,974 0,0           1280,974     

描述:获取所有可见窗口的坐标并将其保存到$windowArray变量中。然后,以表格视图显示它们。

.EXAMPLE
Set-Window -Id $PID -Passthru | Format-Table

Id ProcessName Size     TopLeft BottomRight
-- ----------- ----     ------- -----------
7840 pwsh        1024,638 0,0     1024,638

描述:以表格视图显示当前PowerShell会话窗口的坐标。

#>
[cmdletbinding(DefaultParameterSetName='Name')]
Param (
    # 要确定窗口特性的进程的名称。(如果未指定,则为所有进程)。
    [parameter(Mandatory=$False, ValueFromPipeline=$True,
                                ValueFromPipelineByPropertyName=$False,
                                ParameterSetName='Name')]
    [Alias("ProcessName")][string[]]$Name='*',

    # 要确定窗口特性的进程的Id。
    [parameter(Mandatory=$True, ValueFromPipeline=$False,
                                ValueFromPipelineByPropertyName=$True,
                                ParameterSetName='Id')]
    [int[]]$Id,
    [parameter(Mandatory=$True, ValueFromPipeline=$True,
                                ValueFromPipelineByPropertyName=$False,
                                ParameterSetName='Process')]

    # 要检索/确定窗口特性的进程。
    [System.Diagnostics.Process[]]$Processes,

    # 设置窗口从左边的像素位置。
    [int]$X,

    # 设置窗口从顶部的像素位置。
    [int]$Y,

    # 设置窗口的宽度。
    [int]$Width,

    # 设置窗口的高度。
    [int]$Height,

    # 返回窗口的输出对象。
    [switch]$Passthru
)
Begin {
    Try { 
        [void][Window]
    } Catch {
    Add-Type @"
        using System;
        using System.Runtime.InteropServices;
        public class Window {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetWindowRect(
            IntPtr hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        [

<details>
<summary>英文:</summary>

**The script** (requires considerably improved function `Set-Window`, *enclosed below*) opens each *subdir* in a new explorer window, and moves every next window 40 pixels right and down (resized merely to make screenshot clear):

    if ( -not (Get-Command -Name Set-Window -ErrorAction SilentlyContinue) ) {
        . D:\PShell\Downloaded\WindowManipulation\Set-Window.ps1
    }
    $BaseDir = &quot;C:\Blaha&quot;
    $BaseDir = &quot;D:\PShell\DataFiles\Blaha&quot;
    
    $SubDirs = @(&quot;monkey&quot;, &quot;donkey&quot;, &quot;wonkey&quot;)
    $XYcnt = 0
    $XYinc = 32
    $auxArray = [System.Collections.ArrayList]::new()
    foreach($Dir in $SubDirs) {
        $TargetDir = Join-Path -Path $BaseDir -ChildPath $Dir
        if (Test-Path $TargetDir -PathType Container) {
            Start-Process -FilePath &quot;c:\Windows\explorer.exe&quot; -ArgumentList &quot;/root,$TargetDir&quot;
            # Invoke-Item -Path $TargetDir
            Start-Sleep -Seconds 3
            $rex = &quot;^$Dir$|^$([regex]::Escape($TargetDir))$&quot;
            $aux = Get-Process -Name explorer |
                Where-Object MainWindowTitle -match $rex
            if ( $null -ne $aux ) {
                $XYpos = $XYcnt*$XYinc
                [void]$auxArray.Add( 
                    $(Set-Window -Id $($aux.Id) -X $XYpos -Y $XYpos -Height 440 -Passthru)
                )
                $XYcnt++
            }
        } &lt;# elseif (Test-Path $TargetDir -PathType Leaf) {
            $aux = Start-Process -FilePath $TargetDir -PassThru
            # Invoke-Item -Path $TargetDir
        } &lt;##&gt;
    }
    
    $auxArray| Format-Table -AutoSize

**Result**: `D:\PShell\SO\76193105.ps1`

      Id ProcessName Size     TopLeft BottomRight WindowTitle
      -- ----------- ----     ------- ----------- -----------
    5152 explorer    1130,440 0,0     1130,440    monkey     
    4180 explorer    1130,440 32,32   1162,472    donkey     
    7016 explorer    1130,440 64,64   1194,504    wonkey     

**Important**: improved script `Set-Window.ps1`:

    Function Set-Window {
    &lt;#
    .SYNOPSIS
    Retrieve/Set the window size and coordinates of a process window.
    
    .DESCRIPTION
    Retrieve/Set the size (height,width) and coordinates (x,y) 
    of a process window.
    
    .NOTES
    Name:   Set-Window
    Author: Boe Prox
    Version History:
    1.0//Boe Prox 11/24/2015 Initial build
    1.1//JosefZ   19.05.2018 Treats more process instances 
                                of supplied process name properly
    1.2//JosefZ   21.02.2019 Added parameter `Id`
    1.3//JosefZ   07.05.2023 Type of input parameters changed:
                                [int]$Id to [int[]]$Id
                                [string]$ProcessName to [string[]]$ProcessName
                              Added parameter `Processes`
                              Added `MainWindowTitle` (noteproperty) to output 
    
    The most recent version:
    D:\PShell\Downloaded\WindowManipulation\Set-Window.ps1
    
    .OUTPUTS
    None
    System.Management.Automation.PSCustomObject
    System.Object
    
    .EXAMPLE
    Get-Process powershell | Set-Window -X 20 -Y 40 -Passthru -Verbose
    VERBOSE: powershell (Id=11140, Handle=132410)
    
    Id          : 11140
    ProcessName : powershell
    Size        : 1134,781
    TopLeft     : 20,40
    BottomRight : 1154,821
    
    Description: Set the coordinates on the window for the process PowerShell.exe
    
    .EXAMPLE
    $windowArray = Set-Window -Passthru
    WARNING: cmd (1096) is minimized! Coordinates will not be accurate.
    
        PS C:\&gt;$windowArray | Format-Table -AutoSize
    
      Id ProcessName    Size     TopLeft       BottomRight  
      -- -----------    ----     -------       -----------  
    1096 cmd            199,34   -32000,-32000 -31801,-31966
    4088 explorer       1280,50  0,974         1280,1024    
    6880 powershell     1280,974 0,0           1280,974     
    
    Description: Get the coordinates of all visible windows and save them into the
                 $windowArray variable. Then, display them in a table view.
    
    .EXAMPLE
    Set-Window -Id $PID -Passthru | Format-Table
    ​‌‍
      Id ProcessName Size     TopLeft BottomRight
      -- ----------- ----     ------- -----------
    7840 pwsh        1024,638 0,0     1024,638
    
    Description: Display the coordinates of the window for the current 
                 PowerShell session in a table view.
    
    #&gt;
    [cmdletbinding(DefaultParameterSetName=&#39;Name&#39;)]
    Param (
        # Name of the process to determine the window characteristics. 
        # (All processes if omitted).
        [parameter(Mandatory=$False,ValueFromPipeline=$True,
                                    ValueFromPipelineByPropertyName=$False,
                                    ParameterSetName=&#39;Name&#39;)]
        [Alias(&quot;ProcessName&quot;)][string[]]$Name=&#39;*&#39;,
    
        # Id of the process to determine the window characteristics.
        [parameter(Mandatory=$True, ValueFromPipeline=$False,
                                    ValueFromPipelineByPropertyName=$True,
                                    ParameterSetName=&#39;Id&#39;)]
        [int[]]$Id,
        [parameter(Mandatory=$True, ValueFromPipeline=$True,
                                    ValueFromPipelineByPropertyName=$False,
                                    ParameterSetName=&#39;Process&#39;)]
    
        # Process to retrieve/determine the window characteristics.
        [System.Diagnostics.Process[]]$Processes,
    
        # Set the position of the window in pixels from the left.
        [int]$X,
    
        # Set the position of the window in pixels from the top.
        [int]$Y,
    
        # Set the width of the window.
        [int]$Width,
    
        # Set the height of the window.
        [int]$Height,
    
        # Returns the output object of the window.
        [switch]$Passthru
    )
    Begin {
        Try { 
            [void][Window]
        } Catch {
        Add-Type @&quot;
            using System;
            using System.Runtime.InteropServices;
            public class Window {
            [DllImport(&quot;user32.dll&quot;)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetWindowRect(
                IntPtr hWnd, out RECT lpRect);
    
            [DllImport(&quot;user32.dll&quot;)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool MoveWindow( 
                IntPtr handle, int x, int y, int width, int height, bool redraw);
                  
            [DllImport(&quot;user32.dll&quot;)] 
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool ShowWindow(
                IntPtr handle, int state);
            }
            public struct RECT
            {
            public int Left;        // x position of upper-left corner
            public int Top;         // y position of upper-left corner
            public int Right;       // x position of lower-right corner
            public int Bottom;      // y position of lower-right corner
            }
    &quot;@
        }
    }
    Process {
        $Rectangle = New-Object RECT
        if ($PSBoundParameters[&#39;Debug&#39;]) {
            $DebugPreference = [System.Management.Automation.ActionPreference]::Continue
        }
        If ( $PSBoundParameters.ContainsKey(&#39;Id&#39;) ) {
            $Processes = Get-Process -Id $Id -ErrorAction SilentlyContinue
            Write-Debug &quot;Id&quot;
        } elseIf ( $PSBoundParameters.ContainsKey(&#39;Name&#39;) ) {
            $Processes = Get-Process -Name $Name -ErrorAction SilentlyContinue
            Write-Debug &quot;Name&quot;
        } else {
            Write-Debug &quot;Process&quot;
        }
        if ( $null -eq $Processes ) {
            If ( $PSBoundParameters[&#39;Passthru&#39;] ) {
                Write-Warning &#39;No process match criteria specified&#39;
            }
        } else {
            $Processes | ForEach-Object {
                $Handle = $_.MainWindowHandle
                Write-Verbose &quot;$($_.ProcessName) `(Id=$($_.Id), Handle=$Handle`)&quot;
                if ( $Handle -eq [System.IntPtr]::Zero ) { return }
                $Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)
                If (-NOT $PSBoundParameters.ContainsKey(&#39;X&#39;)) {
                    $X = $Rectangle.Left            
                }
                If (-NOT $PSBoundParameters.ContainsKey(&#39;Y&#39;)) {
                    $Y = $Rectangle.Top
                }
                If (-NOT $PSBoundParameters.ContainsKey(&#39;Width&#39;)) {
                    $Width = $Rectangle.Right - $Rectangle.Left
                }
                If (-NOT $PSBoundParameters.ContainsKey(&#39;Height&#39;)) {
                    $Height = $Rectangle.Bottom - $Rectangle.Top
                }
                If ( $Return ) {
                    $Return = [Window]::MoveWindow($Handle, $x, $y, $Width, $Height,$True)
                }
                If ( $Passthru.IsPresent ) {
                    $Rectangle = New-Object RECT
                    $Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)
                    If ( $Return ) {
                        $Height      = $Rectangle.Bottom - $Rectangle.Top
                        $Width       = $Rectangle.Right  - $Rectangle.Left
                        $Size        = New-Object System.Management.Automation.Host.Size        -ArgumentList $Width, $Height
                        $TopLeft     = New-Object System.Management.Automation.Host.Coordinates -ArgumentList $Rectangle.Left , $Rectangle.Top
                        $BottomRight = New-Object System.Management.Automation.Host.Coordinates -ArgumentList $Rectangle.Right, $Rectangle.Bottom
                        If ($Rectangle.Top    -lt 0 -AND 
                            $Rectangle.Bottom -lt 0 -AND
                            $Rectangle.Left   -lt 0 -AND
                            $Rectangle.Right  -lt 0) {
                            Write-Warning &quot;$($_.ProcessName) `($($_.Id)`) is minimized! Coordinates will not be accurate.&quot;
                        }
                        $Object = [PSCustomObject]@{
                            Id          = $_.Id
                            ProcessName = $_.ProcessName
                            Size        = $Size
                            TopLeft     = $TopLeft
                            BottomRight = $BottomRight
                            WindowTitle = $_.MainWindowTitle
                        }
                        $Object
                    }
                }
            }
        }
    }
    }

Explanation
-----------

On the assumption that default application for a filesystem folder is Windows default `explorer.exe`, `Invoke-Item` does not suffice: if I run your script unchanged then all new open windows are choked down by *program manager process* (see also the first snapshot): 

    Get-Process -Name explorer
    

&gt;     Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName          
&gt;     -------  ------    -----      -----     ------     --  -- -----------          
&gt;        3275     168   175140     160808      20.14   3904   1 explorer


[![explorer with `Invoke-Item`][1]][1]


After running the provided script `SO\76193105.ps1` (with `Start-Process` instead of `Invoke-Item`):

[![windows moved and resized][2]][2]


and we can see that each window is open in a stand-alone process:

    Set-Window -Name explorer -Passthru | Format-Table -AutoSize
    

&gt;       Id ProcessName Size     TopLeft BottomRight WindowTitle
&gt;       -- ----------- ----     ------- ----------- -----------
&gt;     3904 explorer    1280,50  0,974   1280,1024              
&gt;     4180 explorer    1130,440 32,32   1162,472    donkey     
&gt;     5152 explorer    1130,440 0,0     1130,440    monkey     
&gt;     7016 explorer    1130,440 64,64   1194,504    wonkey

[![explorer with `Start-Process`][3]][3]


**Note**: The `&quot;^$Dir$|^$([regex]::Escape($TargetDir))$&quot;` regex covers both possible title bar values: *leaf only* or *full path*:
[![enter image description here][4]][4]


  [1]: https://i.stack.imgur.com/LMO6s.png
  [2]: https://i.stack.imgur.com/kqWrQ.png
  [3]: https://i.stack.imgur.com/nGv2q.png
  [4]: https://i.stack.imgur.com/Sj0DM.png

</details>



huangapple
  • 本文由 发表于 2023年5月7日 17:31:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76193105.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定