Send-Keys

Firstly, I am standing on the shoulders of giants.  As the comments below state, the essential first function, Set-WindowState, is only slightly modified from the original form at

https://www.snip2code.com/Snippet/50118/Hide--Show--Minimize--Maximize--etc-wind/

Specifically, it now tests the current PID’s ParentProcessID.  Much hair-pulling has shown that these calls only work if the PowerShell window is the one started from either a shortcut, the Start menu/screen, or Win+R.  If the current PowerShell process is the child process of another shell, be it PowerShell or cmd.exe, then the calls to Set-WindowState fail silently.

 ####################
function Set-WindowState
####################
{
    <#
    .SYNOPSIS 
    Show, Hide Minimize, Maximize, or Restore the Powershell Console or other Window. 
        
    .DESCRIPTION 
    Show, Hide Minimize, Maximize, or Restore the Powershell Console or other Window. 

    Very slightly modified form of 
    https://www.snip2code.com/Snippet/50118/Hide--Show--Minimize--Maximize--etc-wind/
            
    .PARAMETER WindowState
    [string] The New Window state Mode.
        May be one of the following:
                        
            Hide                Hides the window and activates another window.
            Normal              Activates and displays a window. This is the Default. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
            ShowMinimized       Activates the window and displays it as a minimized window.
            Maximize            Maximizes the specified window.
            ShowNoActivate      Displays a window in its most recent size and position. This value is similar to SW_SHOWNORMAL, except that the window is not activated.
            Show                Activates the window and displays it in its current size and position. 
            Minimize            Minimizes the specified window and activates the next top-level window in the Z order.
            ShowMinNoActive     Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, except the window is not activated.
            ShowNA              Displays the window in its current size and position. This value is similar to SW_SHOW, except that the window is not activated. 
            Restore             Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.
            ShowDefault         Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application. 
            ForceMinimize       Minimizes a window, even if the thread that owns the window is not responding. This flag should only be used when minimizing windows from a different thread.

    .PARAMETER ID
    [Int] The Process Identifier (PID) of the Target Window. If this parameter is not specified the Target window defaults to the current Powershell Console Window.

    .INPUTS
    [Int] $ID You can pipe Process Identifier (PID) of the Target Window.

    .OUTPUTS
    Returns $false if run in a subshell, returns $null otherwise.

    .Example
    PS C:\Users\User\Documents> Set-WindowState -WindowState Minimize

    This will Minimize the Powershell Console Window.

    .Link
    https://www.snip2code.com/Snippet/50118/Hide--Show--Minimize--Maximize--etc-wind/

    .notes
    This only works in the original PSH window spawned off explorer.exe.  It does not work in subshell, where PSH (or cmd.exe) calls PowerShell.exe.

    #>

    param (  
        [parameter(
            Mandatory=$false, 
            ValuefromPipeline = $false
        )] 
        [String] [ValidateSet( 
            "Hide", 
            "Normal", 
            "ShowMinimized", 
            "Maximize", 
            "ShowNoActivate", 
            "Show", 
            "Minimize", 
            "ShowMinNoActive", 
            "ShowNA", 
            "Restore", 
            "ShowDefault", 
            "ForceMinimize"
        )] $WindowState = "Normal",
            
        [parameter(
            Mandatory=$false, 
            ValuefromPipeline=$true,
            ValueFromPipeLineByPropertyName=$true
        )]  
        [Int] $ID = $PID
    );

    #region check if we are a subshell or not.  If we're a subshell, this won't work.

    $parentProcessId = (
        Get-WmiObject -Query "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId=$PID"
    ).ParentProcessId;

    if ((Get-Process -Id $parentProcessId).ProcessName -ne 'explorer')
    {
            $message = "$($MyInvocation.MyCommand.Name) can only be run in a PowerShell window started from explorer.  It cannot control windows when it is running in a subshell.  Stopping.";
            Write-Error -ErrorAction SilentlyContinue -Message $message;
            Write-Warning -Message $message;
            return $false;
            break __outOfScript;

    } # if ((Get-Process -Id $parentProcessId).ProcessName -ne 'explorer')

    #endregion
    #region set up the magic here

    # this really should be an [Enum]
    $windowStateHash = @{
        Hide             =  0;
        Normal           =  1;
        ShowMinimized    =  2;
        Maximize         =  3;
        ShowNoActivate   =  4;
        Show             =  5;
        Minimize         =  6;
        ShowMinNoActive  =  7;
        ShowNA           =  8;
        Restore          =  9;
        ShowDefault      = 10;
        ForceMinimize    = 11;
    };

    $showWindowAsync = Add-Type -MemberDefinition '
        [DllImport("user32.dll")] 
        public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
    ' -Name "Win32ShowWindowAsync" -Namespace Win32Functions -PassThru;

    #endregion

    # pull off the magic
    $showWindowAsync::ShowWindowAsync((Get-Process -id $ID).MainWindowHandle, $windowStateHash.$WindowState) | Out-Null;
    
} # function Set-WindowState


####################
function Send-Keys
####################
{
    <#
    .Synopsis
    Send keystrokes to another window.

    .Description
    A really brittle PSH implementation of the beloved and oft-abused SendKeys.SendWait().  
    
    If a window title is specified AND UNIQUE (i.e. there cannot be another window with the same title), then that window is given focus.

    If a window title is not specified, the current PowerShell window will be minimized and whichever window previously had focus will be given focus.

    Keystrokes are sent one-at-a-time, with a set delay between each.

    .Parameter InputString
    [string[]] Data to be sent.

    .parameter WindowTitle
    Title of window to be sent keystrokes.  Must be unique of all windows on dekstop, opened or minimized.

    .parameter Milliseconds
    Delay between keystrokes. Default is 100ms (1/10 of a second).  Note that the application that received incoming keystrokes may buffer them, so this only specifies the MAXIMUM keystrokes that can be sent per second.

    .parameter NoActivate
    Default behaviour is to restore the PowerShell window and give it focus.  This leaves focus with the application that received the incoming keystrokes.

    .link
    Send-Keys (gc .\_vimrc) -WindowTitle 'Windows PowerShell ISE'

    #>

    param ( 
        [parameter(ValueFromPipeline=$true)][string[]]$InputString = @(),
        [string]$WindowTitle = $null,
        [int]$Milliseconds = 100,
        [switch]$NoActivate
    );

    begin
    {

        #region check if we are a subshell or not.  If we're a subshell, this won't work.

        $parentProcessId = (
            Get-WmiObject -Query "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId=$PID"
        ).ParentProcessId;

        if ((Get-Process -Id $parentProcessId).ProcessName -ne 'explorer')
        {
                $message = "$($MyInvocation.MyCommand.Name) can only be run in a PowerShell window started from explorer.  It cannot control windows started in a subshell.  Stopping.";
                Write-Error -ErrorAction SilentlyContinue -Message $message;
                Write-Warning -Message $message;
                return $false;
                break __outOfScript;

        } # if ((Get-Process -Id $parentProcessId).ProcessName -ne 'explorer')

        #endregion
        #region determine which window to target (specified one, or one with last focus)

        if ($WindowTitle)
        {
            $windowHandles = Get-Process | 
                ? { $_.MainWindowTitle -eq "$WindowTitle"} |
                Select-Object -ExpandProperty MainWindowHandle;

            if (!$windowHandles)
            {
                $message = "$($MyInvocation.MyCommand.Name) -WindowTitle '$WindowTitle' not found.  Stopping.";
                Write-Error -ErrorAction SilentlyContinue -Message $message;
                Write-Warning -Message $message;
                return $false;
                break __outOfScript;
            
            } # (if !$windowHandles)
            elseif ($windowHandles.Count -gt 2)
            {
                $message = "$($MyInvocation.MyCommand.Name) -WindowTitle '$WindowTitle' found $($windowHandles.Count) windows, expected to find only 1.  Stopping.";
                Write-Error -ErrorAction SilentlyContinue -Message $message;
                Write-Warning -Message $message;
                return $false;
                break __outOfScript;

            } # if (!$WindowsHandles) ... elseif

             Add-Type '
                using System;
                using System.Runtime.InteropServices;
                public class WindowControl {
                    [DllImport("user32.dll")]
                    [return: MarshalAs(UnmanagedType.Bool)]
                    public static extern bool SetForegroundWindow(IntPtr hWnd);
                }
            ';
            
            [WindowControl]::SetForegroundWindow(($windowHandles | Select-Object -First 1));

        } # if ($WindowTitle)
        else
        {
            Set-WindowState -ID $PID -WindowState Minimize;

        } # if ($WindowTitle) ... else

        #endregion
        #region other initializations

        #save the window size, because restoring focus may reset it
        $windowSize = $Host.UI.RawUI.WindowSize

        # counter for number of characters sent;
        $i = 0;

        [void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms");

        #endregion

    } # begin

    process {
        
        foreach ($line in $InputString)
        {
            foreach ($char in [char[]]$line)
            {
                if ($char -match "[\+\^\%\~\(\)\{\}\[\]]")
                {
                    [string]$char = "{$char}";

                } # if ($char -match "...

                [System.Windows.Forms.SendKeys]::SendWait($char);
                Start-Sleep -Milliseconds $Milliseconds;
                $i += 1;
                
            } # foreach ($char in [char[]]$line)
            
            [System.Windows.Forms.SendKeys]::SendWait("{ENTER}");
            $i += 1;

        } # foreach ($line in $InputString)

    } # process

    end
    {

        if (!$NoActivate)
        { # if -NoActivate specified, don't reopen the PSH window

            # otherwise, set it back as it ever was.
            Set-WindowState -ID $PID -WindowState Restore;
            $Host.UI.RawUI.WindowSize = $windowSize;

        } # if (!$NoActivate)

        return $i;

    } #end

} # function Send-Keys

Comments

  • Anonymous
    May 12, 2015
    It's not that hard / not that easy.  Once you have an IE COM object, you interact with it via the object methods.  Toss $ieComObject | Get-Member to see all the methods.  For example, $ieComObject.Browse($url) will point the browser to $url.