Reputation: 9721
I have a batch file sps.bat
that simply contains one line: start powershell
.
In a PowerShell console window, when I type sps
a new PowerShell console window is launched and is pre-navigated to the same path as the launching instance.
What I would like to have are separate scripts which not only launch a new PowerShell console window but also, once launched, subsequently locate the new PowerShell window in a specified quadrant (filling it entirely except for the TaskBar) of either of my monitors on a dual screen system.
I.e., I would like to create these commands:
Command New Window Location
——————————————————————————————————————————————————
l1 Left-monitor, quadrant 1 (Upper left)
l2 Left-monitor, quadrant 2 (Upper Right)
l3 Left-monitor, quadrant 3 (Lower Right)
l4 Left-monitor, quadrant 4 (Lower Left)
r1 Right-monitor, quadrant 1 (Upper left)
r2 Right-monitor, quadrant 2 (Upper Right)
r3 Right-monitor, quadrant 3 (Lower Right)
r4 Right-monitor, quadrant 4 (Lower Left)
It's easy to place and size a window using C#. However, detecting when the window is launched and ready to be positioned might perhaps be trickier.
Hopefully someone already has working code for this task available.
Upvotes: 2
Views: 341
Reputation: 9721
Here's some code that will do this:
// Interop.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WindowQuadrantPositioner {
public static class Interop {
public static class Flags {
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
static readonly IntPtr HWND_TOP = new IntPtr(0);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
/// <summary>
/// Window handles (HWND) used for hWndInsertAfter
/// </summary>
public static class HWND {
public static IntPtr
NoTopMost = new IntPtr(-2),
TopMost = new IntPtr(-1),
Top = new IntPtr(0),
Bottom = new IntPtr(1);
}
/// <summary>
/// SetWindowPos Flags
/// </summary>
public static class SWP {
public static readonly int
NOSIZE = 0x0001,
NOMOVE = 0x0002,
NOZORDER = 0x0004,
NOREDRAW = 0x0008,
NOACTIVATE = 0x0010,
DRAWFRAME = 0x0020,
FRAMECHANGED = 0x0020,
SHOWWINDOW = 0x0040,
HIDEWINDOW = 0x0080,
NOCOPYBITS = 0x0100,
NOOWNERZORDER = 0x0200,
NOREPOSITION = 0x0200,
NOSENDCHANGING = 0x0400,
DEFERERASE = 0x2000,
ASYNCWINDOWPOS = 0x4000;
}
}
[Flags()]
public enum SetWindowPosFlags : uint {
/// <summary>If the calling thread and the thread that owns the window are attached to different input queues,
/// the system posts the request to the thread that owns the window. This prevents the calling thread from
/// blocking its execution while other threads process the request.</summary>
/// <remarks>SWP_ASYNCWINDOWPOS</remarks>
AsynchronousWindowPosition = 0x4000,
/// <summary>Prevents generation of the WM_SYNCPAINT message.</summary>
/// <remarks>SWP_DEFERERASE</remarks>
DeferErase = 0x2000,
/// <summary>Draws a frame (defined in the window's class description) around the window.</summary>
/// <remarks>SWP_DRAWFRAME</remarks>
DrawFrame = 0x0020,
/// <summary>Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to
/// the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE
/// is sent only when the window's size is being changed.</summary>
/// <remarks>SWP_FRAMECHANGED</remarks>
FrameChanged = 0x0020,
/// <summary>Hides the window.</summary>
/// <remarks>SWP_HIDEWINDOW</remarks>
HideWindow = 0x0080,
/// <summary>Does not activate the window. If this flag is not set, the window is activated and moved to the
/// top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter
/// parameter).</summary>
/// <remarks>SWP_NOACTIVATE</remarks>
DoNotActivate = 0x0010,
/// <summary>Discards the entire contents of the client area. If this flag is not specified, the valid
/// contents of the client area are saved and copied back into the client area after the window is sized or
/// repositioned.</summary>
/// <remarks>SWP_NOCOPYBITS</remarks>
DoNotCopyBits = 0x0100,
/// <summary>Retains the current position (ignores X and Y parameters).</summary>
/// <remarks>SWP_NOMOVE</remarks>
IgnoreMove = 0x0002,
/// <summary>Does not change the owner window's position in the Z order.</summary>
/// <remarks>SWP_NOOWNERZORDER</remarks>
DoNotChangeOwnerZOrder = 0x0200,
/// <summary>Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to
/// the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent
/// window uncovered as a result of the window being moved. When this flag is set, the application must
/// explicitly invalidate or redraw any parts of the window and parent window that need redrawing.</summary>
/// <remarks>SWP_NOREDRAW</remarks>
DoNotRedraw = 0x0008,
/// <summary>Same as the SWP_NOOWNERZORDER flag.</summary>
/// <remarks>SWP_NOREPOSITION</remarks>
DoNotReposition = 0x0200,
/// <summary>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</summary>
/// <remarks>SWP_NOSENDCHANGING</remarks>
DoNotSendChangingEvent = 0x0400,
/// <summary>Retains the current size (ignores the cx and cy parameters).</summary>
/// <remarks>SWP_NOSIZE</remarks>
IgnoreResize = 0x0001,
/// <summary>Retains the current Z order (ignores the hWndInsertAfter parameter).</summary>
/// <remarks>SWP_NOZORDER</remarks>
IgnoreZOrder = 0x0004,
/// <summary>Displays the window.</summary>
/// <remarks>SWP_SHOWWINDOW</remarks>
ShowWindow = 0x0040,
}
/// <summary>
/// Changes the size, position, and Z order of a child, pop-up, or top-level window. These windows are ordered
/// according to their appearance on the screen. The topmost window receives the highest rank and is the first window
/// in the Z order.
/// <para>See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545%28v=vs.85%29.aspx for more information.</para>
/// </summary>
/// <param name="hWnd">C++ ( hWnd [in]. Type: HWND )<br />A handle to the window.</param>
/// <param name="hWndInsertAfter">
/// C++ ( hWndInsertAfter [in, optional]. Type: HWND )<br />A handle to the window to precede the positioned window in
/// the Z order. This parameter must be a window handle or one of the following values.
/// <list type="table">
/// <itemheader>
/// <term>HWND placement</term><description>Window to precede placement</description>
/// </itemheader>
/// <item>
/// <term>HWND_BOTTOM ((HWND)1)</term>
/// <description>
/// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost
/// window, the window loses its topmost status and is placed at the bottom of all other windows.
/// </description>
/// </item>
/// <item>
/// <term>HWND_NOTOPMOST ((HWND)-2)</term>
/// <description>
/// Places the window above all non-topmost windows (that is, behind all topmost windows). This
/// flag has no effect if the window is already a non-topmost window.
/// </description>
/// </item>
/// <item>
/// <term>HWND_TOP ((HWND)0)</term><description>Places the window at the top of the Z order.</description>
/// </item>
/// <item>
/// <term>HWND_TOPMOST ((HWND)-1)</term>
/// <description>
/// Places the window above all non-topmost windows. The window maintains its topmost position
/// even when it is deactivated.
/// </description>
/// </item>
/// </list>
/// <para>For more information about how this parameter is used, see the following Remarks section.</para>
/// </param>
/// <param name="X">C++ ( X [in]. Type: int )<br />The new position of the left side of the window, in client coordinates.</param>
/// <param name="Y">C++ ( Y [in]. Type: int )<br />The new position of the top of the window, in client coordinates.</param>
/// <param name="cx">C++ ( cx [in]. Type: int )<br />The new width of the window, in pixels.</param>
/// <param name="cy">C++ ( cy [in]. Type: int )<br />The new height of the window, in pixels.</param>
/// <param name="uFlags">
/// C++ ( uFlags [in]. Type: UINT )<br />The window sizing and positioning flags. This parameter can be a combination
/// of the following values.
/// <list type="table">
/// <itemheader>
/// <term>HWND sizing and positioning flags</term>
/// <description>Where to place and size window. Can be a combination of any</description>
/// </itemheader>
/// <item>
/// <term>SWP_ASYNCWINDOWPOS (0x4000)</term>
/// <description>
/// If the calling thread and the thread that owns the window are attached to different input
/// queues, the system posts the request to the thread that owns the window. This prevents the calling
/// thread from blocking its execution while other threads process the request.
/// </description>
/// </item>
/// <item>
/// <term>SWP_DEFERERASE (0x2000)</term>
/// <description>Prevents generation of the WM_SYNCPAINT message. </description>
/// </item>
/// <item>
/// <term>SWP_DRAWFRAME (0x0020)</term>
/// <description>Draws a frame (defined in the window's class description) around the window.</description>
/// </item>
/// <item>
/// <term>SWP_FRAMECHANGED (0x0020)</term>
/// <description>
/// Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message
/// to the window, even if the window's size is not being changed. If this flag is not specified,
/// WM_NCCALCSIZE is sent only when the window's size is being changed
/// </description>
/// </item>
/// <item>
/// <term>SWP_HIDEWINDOW (0x0080)</term><description>Hides the window.</description>
/// </item>
/// <item>
/// <term>SWP_NOACTIVATE (0x0010)</term>
/// <description>
/// Does not activate the window. If this flag is not set, the window is activated and moved to
/// the top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter
/// parameter).
/// </description>
/// </item>
/// <item>
/// <term>SWP_NOCOPYBITS (0x0100)</term>
/// <description>
/// Discards the entire contents of the client area. If this flag is not specified, the valid
/// contents of the client area are saved and copied back into the client area after the window is sized or
/// repositioned.
/// </description>
/// </item>
/// <item>
/// <term>SWP_NOMOVE (0x0002)</term>
/// <description>Retains the current position (ignores X and Y parameters).</description>
/// </item>
/// <item>
/// <term>SWP_NOOWNERZORDER (0x0200)</term>
/// <description>Does not change the owner window's position in the Z order.</description>
/// </item>
/// <item>
/// <term>SWP_NOREDRAW (0x0008)</term>
/// <description>
/// Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies
/// to the client area, the nonclient area (including the title bar and scroll bars), and any part of the
/// parent window uncovered as a result of the window being moved. When this flag is set, the application
/// must explicitly invalidate or redraw any parts of the window and parent window that need redrawing.
/// </description>
/// </item>
/// <item>
/// <term>SWP_NOREPOSITION (0x0200)</term><description>Same as the SWP_NOOWNERZORDER flag.</description>
/// </item>
/// <item>
/// <term>SWP_NOSENDCHANGING (0x0400)</term>
/// <description>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</description>
/// </item>
/// <item>
/// <term>SWP_NOSIZE (0x0001)</term>
/// <description>Retains the current size (ignores the cx and cy parameters).</description>
/// </item>
/// <item>
/// <term>SWP_NOZORDER (0x0004)</term>
/// <description>Retains the current Z order (ignores the hWndInsertAfter parameter).</description>
/// </item>
/// <item>
/// <term>SWP_SHOWWINDOW (0x0040)</term><description>Displays the window.</description>
/// </item>
/// </list>
/// </param>
/// <returns><c>true</c> or nonzero if the function succeeds, <c>false</c> or zero otherwise or if function fails.</returns>
/// <remarks>
/// <para>
/// As part of the Vista re-architecture, all services were moved off the interactive desktop into Session 0.
/// hwnd and window manager operations are only effective inside a session and cross-session attempts to manipulate
/// the hwnd will fail. For more information, see The Windows Vista Developer Story: Application Compatibility
/// Cookbook.
/// </para>
/// <para>
/// If you have changed certain window data using SetWindowLong, you must call SetWindowPos for the changes to
/// take effect. Use the following combination for uFlags: SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
/// SWP_FRAMECHANGED.
/// </para>
/// <para>
/// A window can be made a topmost window either by setting the hWndInsertAfter parameter to HWND_TOPMOST and
/// ensuring that the SWP_NOZORDER flag is not set, or by setting a window's position in the Z order so that it is
/// above any existing topmost windows. When a non-topmost window is made topmost, its owned windows are also made
/// topmost. Its owners, however, are not changed.
/// </para>
/// <para>
/// If neither the SWP_NOACTIVATE nor SWP_NOZORDER flag is specified (that is, when the application requests that
/// a window be simultaneously activated and its position in the Z order changed), the value specified in
/// hWndInsertAfter is used only in the following circumstances.
/// </para>
/// <list type="bullet">
/// <item>Neither the HWND_TOPMOST nor HWND_NOTOPMOST flag is specified in hWndInsertAfter. </item>
/// <item>The window identified by hWnd is not the active window. </item>
/// </list>
/// <para>
/// An application cannot activate an inactive window without also bringing it to the top of the Z order.
/// Applications can change an activated window's position in the Z order without restrictions, or it can activate
/// a window and then move it to the top of the topmost or non-topmost windows.
/// </para>
/// <para>
/// If a topmost window is repositioned to the bottom (HWND_BOTTOM) of the Z order or after any non-topmost
/// window, it is no longer topmost. When a topmost window is made non-topmost, its owners and its owned windows
/// are also made non-topmost windows.
/// </para>
/// <para>
/// A non-topmost window can own a topmost window, but the reverse cannot occur. Any window (for example, a
/// dialog box) owned by a topmost window is itself made a topmost window, to ensure that all owned windows stay
/// above their owner.
/// </para>
/// <para>
/// If an application is not in the foreground, and should be in the foreground, it must call the
/// SetForegroundWindow function.
/// </para>
/// <para>
/// To use SetWindowPos to bring a window to the top, the process that owns the window must have
/// SetForegroundWindow permission.
/// </para>
/// </remarks>
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
/// <summary>
/// Special window handles
/// </summary>
public enum SpecialWindowHandles {
// ReSharper disable InconsistentNaming
/// <summary>
/// Places the window at the top of the Z order.
/// </summary>
HWND_TOP = 0,
/// <summary>
/// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window, the window loses its topmost status and is placed at the bottom of all other windows.
/// </summary>
HWND_BOTTOM = 1,
/// <summary>
/// Places the window above all non-topmost windows. The window maintains its topmost position even when it is deactivated.
/// </summary>
HWND_TOPMOST = -1,
/// <summary>
/// Places the window above all non-topmost windows (that is, behind all topmost windows). This flag has no effect if the window is already a non-topmost window.
/// </summary>
HWND_NOTOPMOST = -2
// ReSharper restore InconsistentNaming
}
}
}
// WindowPositioner.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;
namespace WindowQuadrantPositioner {
public static class WindowPositioner {
private static void Main(string[] args) {
int processId;
if (args == null || args.Length != 2) {
MessageBox.Show("This program expects to command-line parameters:\n1) The screen/quadrant placement: L1, L2, L3, L4, R1, R2, R3, R4\n2) The Process ID for the PowerShell window", "Command-line arguments not provided", MessageBoxButtons.OK, MessageBoxIcon.Error);
var msg = "";
MessageBox.Show(msg);
return;
}
if (!int.TryParse(args[1], out processId)) {
MessageBox.Show("The Process ID for the PowerShell window must be provided as a command-line argument", "Process ID not provided", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
switch (args[0].ToUpper()) {
case "L1":
PositionWindow(processId, ScreenPosition.Left, Quadrant.UpperLeft);
return;
case "L2":
PositionWindow(processId, ScreenPosition.Left, Quadrant.UpperRight);
return;
case "L3":
PositionWindow(processId, ScreenPosition.Left, Quadrant.LowerRight);
return;
case "L4":
PositionWindow(processId, ScreenPosition.Left, Quadrant.LowerLeft);
return;
case "R1":
PositionWindow(processId, ScreenPosition.Right, Quadrant.UpperLeft);
return;
case "R2":
PositionWindow(processId, ScreenPosition.Right, Quadrant.UpperRight);
return;
case "R3":
PositionWindow(processId, ScreenPosition.Right, Quadrant.LowerRight);
return;
case "R4":
PositionWindow(processId, ScreenPosition.Right, Quadrant.LowerLeft);
return;
default:
MessageBox.Show("The screen/quadrant code must must be provided as a command-line argument. Valid values are: 'L1, L2, L3, L4, R1, R2, R3, R4", "Screen/quadrant code not provided", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
public enum ScreenPosition {
Left,
Right
}
public enum Quadrant {
UpperLeft,
UpperRight,
LowerRight,
LowerLeft,
}
public static void PositionWindow(int processId, ScreenPosition screen, Quadrant quadrant) {
try {
var process = Process.GetProcessById(processId).MainWindowHandle;
var coords = GetQuadrantCoords(screen, quadrant);
Interop.SetWindowPos(process, (IntPtr) Interop.SpecialWindowHandles.HWND_TOP, coords.Left, coords.Top, coords.Width, coords.Height, Interop.SetWindowPosFlags.ShowWindow);
} catch (Exception ex) {
MessageBox.Show(ex.Message, "Couldn't set window position", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private static Rectangle GetQuadrantCoords(ScreenPosition screenPosition, Quadrant quadrant) {
int screenCount = Screen.AllScreens.Count();
Screen screen;
if (screenPosition == ScreenPosition.Left) {
screen = Screen.AllScreens.OrderBy(_ => _.WorkingArea.Left).First();
} else {
screen = Screen.AllScreens.OrderBy(_ => _.WorkingArea.Left).Last();
}
var a = screen.WorkingArea;
switch (quadrant) {
case Quadrant.UpperLeft:
return new Rectangle(a.Left, a.Top, a.Width / 2, a.Height / 2);
case Quadrant.UpperRight:
return new Rectangle(a.Left + a.Width / 2, a.Top, a.Width / 2, a.Height / 2);
case Quadrant.LowerRight:
return new Rectangle(a.Left + a.Width / 2, a.Top + a.Height / 2, a.Width / 2, a.Height / 2);
case Quadrant.LowerLeft:
return new Rectangle(a.Left, a.Top + a.Height / 2, a.Width / 2, a.Height / 2);
default:
throw new NotImplementedException();
}
}
}
}
// r1.ps1
$Id = (Start-Process PowerShell -PassThru); Start-Process "WindowQuadrantPositioner.exe" -ArgumentList ("R1 " + $Id.Id)
Similar .ps1
files for the other screen/quadrants.
To position the current window, create scripts following this pattern:
# ml1.ps1
if (-not ("Tricks" -as [type])) {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Tricks {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"@
}
$a = [tricks]::GetForegroundWindow()
$Id = get-process | ? { $_.mainwindowhandle -eq $a }
Start-Process "WindowQuadrantPositioner.exe" -ArgumentList ("L1 " + $Id.Id)
Upvotes: 0
Reputation: 26
I'm not able to comment, but, have you seen a console window tool like ConEmu?
It can achieve the window location desires you seem to have. Hope this helps.
Upvotes: 1