Functions/GenXdev.Windows/Send-Key.ps1
############################################################################### <# .SYNOPSIS Sends simulated keystrokes to a window or process. .DESCRIPTION This function sends keyboard input to a target window or process using the Windows Script Host Shell object. It can target windows by process name, process ID, or window handle. The function supports special key sequences, escape characters, and various timing options for reliable key delivery. .PARAMETER KeysToSend The keyboard input to send as an array of strings. Supports special key sequences like {ENTER}, {TAB}, {F1}, etc. Each string in the array is processed sequentially with optional delays between them. .PARAMETER SendKeyEscape When specified, escapes curly braces in the input text so they are sent as literal characters rather than interpreted as special key sequences. .PARAMETER ProcessName The name of the target process to send keys to. Supports wildcard patterns for flexible process matching. .PARAMETER ProcessId The process ID of the target process to send keys to. Provides precise targeting when multiple processes have similar names. .PARAMETER WindowHandle The window handle (HWND) of the target window to send keys to. Offers direct window targeting when the handle is known. .PARAMETER SendKeyHoldKeyboardFocus When specified, keeps keyboard focus on the target window after sending keys instead of returning focus to the PowerShell window. .PARAMETER SendKeyUseShiftEnter When specified, converts newline characters to Shift+Enter key sequences instead of regular Enter key sequences. .PARAMETER SendKeyDelayMilliSeconds The delay in milliseconds between sending different key sequences. Helps ensure reliable delivery when sending multiple key strings. .EXAMPLE Send-Key -KeysToSend "Hello World{ENTER}" -ProcessName "notepad" Sends text to Notepad followed by Enter key using process name targeting. .EXAMPLE Send-Key "Special {F11} key" -SendKeyEscape -ProcessId 1234 Sends literal "{F11}" text rather than F11 key using process ID targeting. .EXAMPLE sendkeys "Line 1{ENTER}Line 2" -WindowHandle 123456 -SendKeyDelayMilliSeconds 50 Sends multi-line text with custom delay using window handle targeting. #> function Send-Key { [CmdletBinding(DefaultParameterSetName = 'ByProcessName')] [Alias('sendkeys', 'invokekeys')] param ( ############################################################################### [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'The keyboard input to send as an array of strings' )] [string[]] $KeysToSend, ############################################################################### [Parameter( ParameterSetName = 'ByProcessName', Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Name of the process to send keys to (supports wildcards)' )] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string] $ProcessName, ############################################################################### [Parameter( ParameterSetName = 'ByProcessId', Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'ID of the process to send keys to' )] [ValidateNotNull()] [Alias('Id', 'PID')] [int] $ProcessId, ############################################################################### [Parameter( ParameterSetName = 'ByWindowHandle', Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Window handle to send keys to' )] [ValidateNotNull()] [Alias('Handle', 'hWnd')] [long] $WindowHandle, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Escape control characters and modifiers in input text' )] [Alias('Escape')] [switch] $SendKeyEscape, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Hold keyboard focus on target window after sending keys' )] [Alias('HoldKeyboardFocus')] [switch] $SendKeyHoldKeyboardFocus, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Use Shift+Enter instead of Enter for newlines' )] [Alias('UseShiftEnter')] [switch] $SendKeyUseShiftEnter, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Delay between different input strings in milliseconds' )] [ValidateRange(0, [int]::MaxValue)] [Alias('DelayMilliSeconds')] [int] $SendKeyDelayMilliSeconds = 10 ) begin { # initialize the windows script host shell object for sending keystrokes $helper = Microsoft.PowerShell.Utility\New-Object -ComObject WScript.Shell # initialize window variable for target window operations $window = $null; # attempt to locate the target window if targeting parameters are provided if ($null -eq $window) { try { # copy relevant parameters for get-window cmdlet invocation $invocationArguments = GenXdev.Helpers\Copy-IdenticalParamValues ` -FunctionName 'GenXdev.Windows\Get-Window' ` -BoundParameters $PSBoundParameters ` -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable ` -Scope Local ` -ErrorAction SilentlyContinue) # check if any window targeting parameters were provided if ((-not [string]::IsNullOrWhiteSpace($ProcessName)) -or ` ($ProcessId -ne 0) -or ` ($WindowHandle -ne 0)) { # retrieve the first matching window for the specified criteria $window = GenXdev.Windows\Get-Window @invocationArguments ` -ErrorAction SilentlyContinue | Microsoft.PowerShell.Utility\Select-Object -First 1 } } catch { Microsoft.PowerShell.Utility\Write-Warning $_.Exception.Message } } # bring the target window to foreground if one was found if ($null -ne $window) { # make the window visible if it was minimized $null = $window.Show(); # bring the window to the foreground $null = $window.SetForeground() # ensure window has foreground focus using win32 api $null = GenXdev.Windows\Set-ForegroundWindow -WindowHandle $window.Handle # allow time for window activation to complete Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds 500 } } process { try { # iterate through each key sequence string to send foreach ($key in $KeysToSend) { Microsoft.PowerShell.Utility\Write-Verbose ` "Processing key sequence: $key" try { # join array elements into a single string for processing $escapedQuery = $key -join ' ' # escape curly braces if escape mode is enabled if ($SendKeyEscape) { $escapedQuery = $escapedQuery -replace '(\{)', '{{}' $escapedQuery = $escapedQuery -replace '(\})', '{}}' } # normalize line endings to consistent newline format $escapedQuery = $escapedQuery -replace '`r', '`n' $escapedQuery = $escapedQuery -replace '`n`n', '`n' # convert newlines to appropriate key sequences based on mode if ($SendKeyUseShiftEnter) { $escapedQuery = $escapedQuery -replace '`n', '+{ENTER}' } else { $escapedQuery = $escapedQuery -replace '`n', '{ENTER}' } Microsoft.PowerShell.Utility\Write-Verbose ` "Sending keys: $escapedQuery" if ($key -eq '^v') { Microsoft.PowerShell.Utility\Start-Sleep 2 } # send the processed key sequence to the target window $null = $helper.sendKeys($escapedQuery, $true) # apply delay between key sequences if specified if ($SendKeyDelayMilliSeconds -gt 0) { Microsoft.PowerShell.Utility\Start-Sleep ` -Milliseconds $SendKeyDelayMilliSeconds } if ($key -eq '^v') { Microsoft.PowerShell.Utility\Start-Sleep 2 } } catch { Microsoft.PowerShell.Utility\Write-Warning $_.Exception.Message } } } finally { # handle window focus restoration after key sending is complete if ($null -ne $window) { try { # restore powershell window focus unless holding focus was requested if (-not $SendKeyHoldKeyboardFocus) { try { Microsoft.PowerShell.Utility\Write-Verbose ` 'Restoring PowerShell window focus' # locate the main powershell window $psWindow = GenXdev.Windows\Get-PowershellMainWindow # restore focus to powershell window if found if ($psWindow) { $null = $psWindow.SetForeground() $null = GenXdev.Windows\Set-ForegroundWindow ` -WindowHandle $psWindow.Handle } } catch { Microsoft.PowerShell.Utility\Write-Warning ` $_.Exception.Message } } } catch { Microsoft.PowerShell.Utility\Write-Warning $_.Exception.Message } } } } end { } } ############################################################################### |