英文:
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:\>$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 = "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
**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 {
<#
.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
}
}
}
}
}
}
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
> Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
> ------- ------ ----- ----- ------ -- -- -----------
> 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
> 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
[![explorer with `Start-Process`][3]][3]
**Note**: The `"^$Dir$|^$([regex]::Escape($TargetDir))$"` 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>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论