Listen for a key press with Powershell, but don’t wait for it

Listen for a key press with Powershell, but don’t wait for it

I’m trying to write a script that toggles the caps lock key periodically, but I also want to be able to toggle the script because it causes some issues with other functionality (like alt tabbing, and rolling over windows in the task bar to see a preview)
The script I have so far is
:outer while($true){
do{
echo “Toggle with F12”;
$x = [System.Console]::ReadKey()
}while( $x.Key -ne “F12” )
while($true){
$wsh = New-Object -ComObject WScript.Shell
$wsh.SendKeys(‘{CAPSLOCK}’)
sleep 0.3
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsh)
Remove-Variable wsh
if ($Host.UI.RawUI.KeyAvailable) {
$key = $Host.UI.RawUI.ReadKey()
if($key.Key -ne “F12”){
break outer
}
}
}
}

The script waits for the user to press F12, and then once they press F12 I want it to start toggling the caps lock key ever 0.3 seconds until the user presses F12 again. Ideally, I want this to happen in the background, but I don’t know if that’s possible. I don’t want the user to have to have the console window open to toggle the script.
The way this runs now, after the user presses F12 the script will toggle capslock once and then exit. If I remove the conditional after Remove-Variable, the script will run as I want it to except the only way it can be stopped is if the console window is closed.

Solutions/Answers:

Solution 1:

Here is a try. BTW I changed the sleep to 1 second so it doesn’t flash capslock too much while testing:

$continue = $true
while($continue)
{

    if ([console]::KeyAvailable)
    {
        echo "Toggle with F12";
        $x = [System.Console]::ReadKey() 

        switch ( $x.key)
        {
            F12 { $continue = $false }
        }
    } 
    else
    {
        $wsh = New-Object -ComObject WScript.Shell
        $wsh.SendKeys('{CAPSLOCK}')
        sleep 1
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsh)| out-null
        Remove-Variable wsh
    }    
}
Related:  Get-Help about_Automatic_Variables not working

Solution 2:

add-type -Path '.\documents\WindowsPowerShell\VISE_WinKeyboardHook.dll'
$KeyboardInterceptor = new-object VISE.WinKeyboardHook.KeyboardInterceptor
function HandleKeyDown($keyId)
{
    write-host $keyID.KeyCode
    if($keyID.KeyCode -eq "Escape"){
        $KeyboardInterceptor.StopCapturing()
    }
}
Unregister-Event -SourceIdentifier KeyDown -ErrorAction SilentlyContinue
$keyevent = Register-ObjectEvent -InputObject $KeyboardInterceptor -EventName KeyDown -SourceIdentifier KeyDown -Action {HandleKeyDown($event.sourceEventArgs)}
$KeyboardInterceptor.StartCapturing()

Here is a C# assembly which provides event for global keybord events.
https://ianmorrish.wordpress.com/v-ise/keyboard-hook/
Advantage of this is that it is non-blocking and also works in ISE.

References