2

I am trying to get/set the current window position, relative to my screens (x,y) coordinates, within a powershell script (running in that window). In other words, I would like my powershell script ran in one (window) to move itself to a fixed position I decide. (Unfortunately windows insists on changing the location of where the shell window is opened, every time it is run.)

In powershell, setting the size is trivial via the following:

[console]::BufferHeight=9001
[console]::BufferWidth=140
[console]::WindowHeight=50
[console]::WindowWidth=140

So I was expecting to be able to use something like this:

[console]::SetWindowPosition(100,100)

# <error output>
# MethodInvocationException: Exception calling "SetWindowPosition" with "2" argument(s): 
#  "The window position must be set such that the current window size fits within the
#  console's buffer, and the numbers must not be negative. (Parameter 'left')
#  Actual value was 100."

Also, tried checking with: (Get-Host).UI.RawUI | Select-Object *, but found nothing useful there.

Q: What is the most simple in-code powershell commands to set/move the current window?


Unhelpful & outdated Answers & References:

These look more promising:

mklement0
  • 382,024
  • 64
  • 607
  • 775
not2qubit
  • 14,531
  • 8
  • 95
  • 135
  • 1
    There is no generic way, it depends on the console host. If you only need to support the classic console host, p/invoking `GetConsoleWindow()` and `SetWindowPos()` should do the trick. – zett42 Jun 03 '23 at 11:01
  • 2
    Note that [`[Console]::SetWindowPosition()`](https://learn.microsoft.com/en-US/dotnet/api/System.Console.SetWindowPosition), despite its name, does _not_ control the position of the console window on the desktop (emphasis added): "The `SetWindowPosition` method _affects the position of the console window relative to the screen buffer_, but does not affect the position of the operating system window relative to the desktop." – mklement0 Jun 04 '23 at 13:55

1 Answers1

2

After having looked around some more, I managed to reduce one of the Set-Window.ps1 cmdlet's, to something that worked. However, it is surprising that powershell doesn't provide something this basic already. I am not happy to have to include in-line C# code in my PoSh code.

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 static extern 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
    }
"@
}

$XP  = [System.Diagnostics.Process]::GetCurrentProcess().Id     # Get PID (of self)
$WH  = (Get-Process -Id $XP).MainWindowHandle                   # Get (Current) Window Handle (from Get process info from PID)
$R   = New-Object RECT                                          # Define A Rectangle Object
[Void][Window]::GetWindowRect($WH,[ref]$R)                      # Get the Rectangle Object in $R (and Result flag)

# Define the Window (Position, Size)
$X = 20                             # $R.Left
$Y = 40                             # $R.Top
$Width  = $R.Right  - $R.Left
$Height = $R.Bottom - $R.Top

# Move the window to that position...
[Void][Window]::MoveWindow($WH, $X, $Y, $Width, $Height, $True)


not2qubit
  • 14,531
  • 8
  • 95
  • 135
  • 2
    Not questioning your expectations, but when you say "it is surprising that powershell doesn't provide something this basic already.", bear in mind a windowed desktop is only one context that PowerShell can run in. For example, it wouldn't have a "position" if it were running in, say, a headless CI pipeline (say, GitHub Actions or Azure DevOps) or a scheduled task, etc.... – mclayton Jun 03 '23 at 14:17
  • @mclayton I think what I meant to say is, that since it is so easy set the window and buffer size, one would expect it to be equally easy to move the window. – not2qubit Jun 03 '23 at 15:23
  • 1
    Nice. Note that the [automatic `$PID` variable](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Automatic_Variables#pid) contains the current session's process ID, and because that ID is guaranteed to exist, you don't need `-ErrorAction SilentlyContinue`. Thus, `(Get-Process -Id $PID).MainWindowHandle` should be fine, though there's an edge case where it won't work: if the PowerShell session isn't the owner of the console window, in which case you'd need the `GetConsoleWindow()` WinAPI function. Fundamentally, this only works with `conhost.exe` windows – mklement0 Jun 04 '23 at 13:07
  • Thanks. Further reduced the code for readability using line-continuation, and removed result code from invoke. – not2qubit Jun 05 '23 at 09:24
  • A weird side-effect of using line-continuation, is that the above code works as-is in a script, but not when called as a function within a script... – not2qubit Jun 05 '23 at 09:46
  • [Here](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.3) and [here](https://stackoverflow.com/a/30937024/1147688) is some info on using single-quoted and double-quoted `Here-strings`, (`@"..."@` and `@'...'@`). And about [`Add_type`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-7.3&viewFallbackFrom=powershell). – not2qubit Jun 05 '23 at 12:04
  • It looks to me your new C# code is broken: there are no line continuations in C# (statements implicitly continue across lines until terminated with `;`) and having multiple methods share the specifies before the method name isn't supported. – mklement0 Jun 05 '23 at 12:04
  • What's tricky about here-strings is that a newline must immediately follow the opening delimiter, and another one must immediately precede the closing one. This means that closing delimiter must be _at the very start of a line_. – mklement0 Jun 05 '23 at 12:05
  • Yea, I know about the here-string requirements. Also I see no reason why you should not be able to add all items after the line `public static extern bool`? Sorry, my bad, the way I invoked it, was actually running a previous copy (in *name-space*) as the `Add-Type` is not named, and thus maybe the code I was actually running was something from before. IDK how to properly give the correct `Add-Type` switches in this case. – not2qubit Jun 05 '23 at 12:38
  • From [here](https://www.tutlane.com/tutorial/csharp/csharp-variables-with-examples), they say: "*In c#, we can declare and initialize multiple variables of the same data type in a single line by separating with a comma.*" But from [here](https://stackoverflow.com/questions/13374454/declare-and-assign-multiple-string-variables-at-the-same-time), we are told something else. – not2qubit Jun 05 '23 at 12:52
  • 1
    Your links refer to _variable_, not _method_ declarations. – mklement0 Jun 05 '23 at 13:58