CalvinDale
CalvinDale

Reputation: 9721

How to launch PowerShell from PowerShell and subsequently locate launched window in specific quadrant of designated monitor

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

Answers (2)

CalvinDale
CalvinDale

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

n4pd3f
n4pd3f

Reputation: 26

I'm not able to comment, but, have you seen a console window tool like ConEmu?

http://conemu.github.io/

It can achieve the window location desires you seem to have. Hope this helps.

Upvotes: 1

Related Questions