有没有一种简单的方法来定位从PowerShell的Invoke-Item打开的窗口?

xt0899hw  于 2023-06-23  发布在  Shell
关注(0)|答案(2)|浏览(112)

我发现solutions可以编程地定位窗口。甚至还有my question (similar, not exact match)docs for Invoke-Item中没有任何关于它的说明。我不喜欢高级的解决方案,因为它的复杂性(希望是由于通用性和更高的代码标准)。我正在寻找一个懒人的解决方案(可能有相同的警告和限制)。
我有一个脚本,它循环遍历字符串数组,并尝试为找到的每个路径打开一个窗口。我知道屏幕的大小。我不需要确保可扩展性,也不需要插入到其他系统。“在我的机器上工作”在这种情况下是完全可持续的。

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

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

我希望能够为Invoke-Item语句打开的每个窗口指定一个固定位置。在这个阶段,我很高兴,如果每个这样的是在同一位置打开,说在150px从顶部和230px从左边。
这可行吗

wvt8vs2t

wvt8vs2t1#

是否有一个“简单”的方法,答案是不,没有。
有非常复杂的方法来实现它,你可以让他们在大多数时间工作。JosefZ的答案是复杂方法之一的一个例子。
请参阅,在窗口控件所在位置创建窗口控件的应用程序。这些应用程序允许用户移动窗口并调整它们的大小,它们还可以根据活动屏幕,屏幕大小,其他窗口的位置以及过去使用窗口的历史来自动定位。
因此,除非应用程序提供了一种机制,通过命令行或配置文件给予窗口位置,否则您将面临一些复杂的解决方案。一般而言,这些措施包括:

  • 以提升的权限(作为管理员)运行应用程序。这是必要的,因为你实际上是在干扰其他可能被提升的过程。
  • 查找要重新定位的窗。有多种方法可以搜索窗口。你可以通过标题来做,或者比较在启动之前和之后存在的窗口列表。你需要过滤掉隐藏窗口和非顶层窗口。通过一些努力,您可以找到启动每个窗口的应用程序的文件名,并在过滤器中使用它。
  • 使用Windows用户API重新定位窗口。

毕竟,它可能会工作。或者,应用程序将只是将窗口移回它之后的位置。
对于explorer.exe,不会启动新应用程序。行为可能会有所不同,并且可能会重用现有窗口。
底线是,这很少是值得付出的努力,并且推回这个需求将是您应该尝试的第一件事。

unguejic

unguejic2#

脚本(需要大大改进的函数Set-Window,* 附在下面 *)在一个新的浏览器窗口中打开每个 * 子目录 *,并将每个下一个窗口向右和向下移动40像素(调整大小只是为了使屏幕截图清晰):

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"
        # Invoke-Item -Path $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++
        }
    } <# elseif (Test-Path $TargetDir -PathType Leaf) {
        $aux = Start-Process -FilePath $TargetDir -PassThru
        # Invoke-Item -Path $TargetDir
    } <##>
}

$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
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:\>$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.

#>
[cmdletbinding(DefaultParameterSetName='Name')]
Param (
    # Name of the process to determine the window characteristics. 
    # (All processes if omitted).
    [parameter(Mandatory=$False,ValueFromPipeline=$True,
                                ValueFromPipelineByPropertyName=$False,
                                ParameterSetName='Name')]
    [Alias("ProcessName")][string[]]$Name='*',

    # Id of the process to determine the window characteristics.
    [parameter(Mandatory=$True, ValueFromPipeline=$False,
                                ValueFromPipelineByPropertyName=$True,
                                ParameterSetName='Id')]
    [int[]]$Id,
    [parameter(Mandatory=$True, ValueFromPipeline=$True,
                                ValueFromPipelineByPropertyName=$False,
                                ParameterSetName='Process')]

    # 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 @"
        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")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public extern static bool MoveWindow( 
            IntPtr handle, int x, int y, int width, int height, bool redraw);
              
        [DllImport("user32.dll")] 
        [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
        }
"@
    }
}
Process {
    $Rectangle = New-Object RECT
    if ($PSBoundParameters['Debug']) {
        $DebugPreference = [System.Management.Automation.ActionPreference]::Continue
    }
    If ( $PSBoundParameters.ContainsKey('Id') ) {
        $Processes = Get-Process -Id $Id -ErrorAction SilentlyContinue
        Write-Debug "Id"
    } elseIf ( $PSBoundParameters.ContainsKey('Name') ) {
        $Processes = Get-Process -Name $Name -ErrorAction SilentlyContinue
        Write-Debug "Name"
    } else {
        Write-Debug "Process"
    }
    if ( $null -eq $Processes ) {
        If ( $PSBoundParameters['Passthru'] ) {
            Write-Warning 'No process match criteria specified'
        }
    } else {
        $Processes | ForEach-Object {
            $Handle = $_.MainWindowHandle
            Write-Verbose "$($_.ProcessName) `(Id=$($_.Id), Handle=$Handle`)"
            if ( $Handle -eq [System.IntPtr]::Zero ) { return }
            $Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)
            If (-NOT $PSBoundParameters.ContainsKey('X')) {
                $X = $Rectangle.Left            
            }
            If (-NOT $PSBoundParameters.ContainsKey('Y')) {
                $Y = $Rectangle.Top
            }
            If (-NOT $PSBoundParameters.ContainsKey('Width')) {
                $Width = $Rectangle.Right - $Rectangle.Left
            }
            If (-NOT $PSBoundParameters.ContainsKey('Height')) {
                $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 "$($_.ProcessName) `($($_.Id)`) is minimized! Coordinates will not be accurate."
                    }
                    $Object = [PSCustomObject]@{
                        Id          = $_.Id
                        ProcessName = $_.ProcessName
                        Size        = $Size
                        TopLeft     = $TopLeft
                        BottomRight = $BottomRight
                        WindowTitle = $_.MainWindowTitle
                    }
                    $Object
                }
            }
        }
    }
}
}

说明

假设文件系统文件夹的默认应用程序是Windows default explorer.exeInvoke-Item是不够的:如果我不加修改地运行你的脚本,那么所有新打开的窗口都会被 * 程序管理器进程 * 阻塞(另见第一个快照):

Get-Process -Name explorer
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName          
-------  ------    -----      -----     ------     --  -- -----------          
   3275     168   175140     160808      20.14   3904   1 explorer

运行提供的脚本SO\76193105.ps1(使用Start-Process而不是Invoke-Item)后:

我们可以看到每个窗口都是在一个独立的进程中打开的:

Set-Window -Name explorer -Passthru | Format-Table -AutoSize
Id ProcessName Size     TopLeft BottomRight WindowTitle
  -- ----------- ----     ------- ----------- -----------
3904 explorer    1280,50  0,974   1280,1024              
4180 explorer    1130,440 32,32   1162,472    donkey     
5152 explorer    1130,440 0,0     1130,440    monkey     
7016 explorer    1130,440 64,64   1194,504    wonkey

注意"^$Dir$|^$([regex]::Escape($TargetDir))$"正则表达式包含两种可能的标题栏值:leaf onlyfull path

相关问题