El método
SendKeys() es muy limitado, igual que el lenguaje VBS, donde ni tan siquiera se puede llamar a la API de Windows sin recurrir a la instalación de componentes de terceros.
Tengamos presente que Windows no es solo Batch y VBS, hay otros lenguajes soportados de forma nativa y son mucho más potentes/completos, como JS, y PowerShell.
En este último lenguaje podemos simplificar muchas tareas, pero para intentar demostrar el potencial de este lenguaje yo he optado por compilar una clase escrita en otro lenguaje adicional, VB.NET (aunque también podría haber sido C#, F# o VC++), con una clase donde he implementado los métodos necesarios y los P/Invokes para hacer lo que pides (ejecutar el notepad y activar el foco de ventana, y luego mantener pulsada una tecla). No es necesario conocer el lenguaje VB.NET, tan solo debes fijarte en las 3 últimas lineas al final de este código:
Script.ps1$vbCode = @'
Imports Microsoft.VisualBasic
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Threading
Imports System.Windows.Forms
Namespace Elektro
Public NotInheritable Class NativeMethods
Friend Const KEYEVENTF_KEYUP As UInteger = 2UI
<DllImport("User32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Sub keybd_event(ByVal vkey As Byte,
ByVal scanCode As Byte,
ByVal flags As UInteger,
ByVal extraInfo As UInteger)
End Sub
<DllImport("User32.dll", SetLastError:=True)>
Friend Shared Function GetForegroundWindow() As IntPtr
End Function
<DllImport("User32.dll", SetLastError:=True)>
Friend Shared Function SetForegroundWindow(ByVal hwnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
End Class
Public NotInheritable Class ProcessTools
''' <summary>
''' Runs and activates Notepad process.
''' </summary>
Public Shared Sub RunNotepad()
Using p As New Process() With {
.StartInfo = New ProcessStartInfo("notepad.exe") With {
.UseShellExecute = True
}
}
p.Start()
p.WaitForInputIdle(Timeout.Infinite)
Dim hwnd As IntPtr = IntPtr.Zero
Do While (hwnd = IntPtr.Zero)
hwnd = p.MainWindowHandle
Loop
Debug.
WriteLine("Window Handle: " & hwnd.
ToInt32())
Do Until (NativeMethods.GetForegroundWindow() = hwnd)
Dim result As Boolean = NativeMethods.SetForegroundWindow(hwnd)
Loop
'' Alternativa administrada:
' Interaction.AppActivate(p.Id)
End Using
End Sub
End Class
Public NotInheritable Class KeyboardTools
' Press and holds the specified key.
Public Shared Sub PressKey(ByVal key As Keys)
NativeMethods.keybd_event(CByte(key), 0, 0UI, 0UI)
End Sub
' Releases a previous hold key.
Public Shared Sub ReleaseKey(ByVal key As Keys)
NativeMethods.keybd_event(CByte(key), 0, NativeMethods.KEYEVENTF_KEYUP, 0UI)
End Sub
End Class
End Namespace
'@
$vbType = Add-Type -TypeDefinition $vbCode `
-CodeDomProvider (New-Object Microsoft.VisualBasic.VBCodeProvider) `
-PassThru `
-ReferencedAssemblies "Microsoft.VisualBasic.dll", `
"System.dll", `
"System.ComponentModel.dll", `
"System.Runtime.InteropServices.dll", `
"System.Threading.dll", `
"System.Windows.Forms.dll" `
| where { $_.IsPublic }
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# End of VB.NET CodeDom Provider Compiler Services Sample
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Add-Type -AssemblyName "System.Windows.Forms"
$Keys = [System.Windows.Forms.Keys]
[Elektro.ProcessTools]::RunNotepad()
[Elektro.KeyboardTools]::PressKey($Keys::ShiftKey)
# [Elektro.KeyboardTools]::ReleaseKey($Keys::ShiftKey)
El modo de empleo es muy sencillo, abstraido de cualquier complejidad. Con el método
Elektro.ProcessTools.RunNotepad() se ejecuta una nueva instancia del proceso
Notepad.exe (o el
hijack correspondiente que haya definido en el sistema) y se activa el foco de ventana. Con el método
Elektro.KeyboardTools.PressKey(Keys) se simula una pulsación del teclado de la tecla que deseemos (la tecla se mantendrá pulsada), y con el método
Elektro.KeyboardTools.ReleaseKey(Keys) liberamos la tecla que previamente habiamos pulsado.
Espero que te sirva de ayuda.
PD: Nótese que el miembro
keybd_event de la API de Windows está considerado obsoleto, esto en realidad debería reemplazarse por la utilización de la infraestructura
SENDINPUT, sin embargo la implementación de los miembros necesarios habría extendido el código de ejemplo en varios cientos de lineas, así que simplemente lo he preferido hacer así como una decisión de diseño y simplificación; esto es tan solo un ejemplo ordinario y funcional, no una solución optimizada.
Saludos