Clear $host.UI.RawUI.KeyAvailable - powershell

I have a script that auto-refreshes every 30 minutes and upon bringing the console window to focus and pressing any key, it will refresh manually. The problem is that once you press a key, it stops auto-refreshing.
$timeout = New-TimeSpan -Minutes 30
$sw = [diagnostics.stopwatch]::StartNew()
while ($sw.elapsed -lt $timeout){
if ($host.UI.RawUI.KeyAvailable) {
$key = $host.UI.RawUI.ReadKey()
break
}
start-sleep -seconds 5
}
The problem is the 4th line of text. Once you press a key and it gets stored in $host.UI.RawUI.KeyAvailable, it seems to retain that after the whole thing loops and it thinks you pressed another key again when you didn't, so it will not go back to the auto-refreshing every 30 minutes. Is it possible to clear out $host.UI.RawUI.KeyAvailable ?

Solved it myself by focusing on the key after it is pressed.
$timeout = New-TimeSpan -Minutes $sleepmin
$sw = [diagnostics.stopwatch]::StartNew()
while ($sw.Elapsed -lt $timeout){
if ($host.UI.RawUI.KeyAvailable) {
$key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp,IncludeKeyDown")
if ($key.KeyDown -eq "True"){
break
}
}
Start-Sleep -Seconds 5
}

Related

How to test for a keypress or continue in powershell

I have a basic powershell function that works as a spambot.
function Start-Spambot{
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="The text you wish to spam.")]
[string]$text,
[Parameter(Mandatory=$True,HelpMessage="How many milliseconds between messages.")]
[decimal]$speed,
[Parameter(Mandatory=$True,HelpMessage="The tota ammout you want to spam.")]
[decimal]$number
)
$count = $number
Invoke-BalloonTip -Message "You have 3 seconds before the program starts spamming." -Title "Start-Spambot $text $speed $number"
Start-Sleep -Seconds 3
while ($count -gt 0){
[System.Windows.Forms.SendKeys]::SendWait("$text")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
Start-Sleep -Milliseconds $speed
$count -= 1
}
[System.Windows.Forms.SendKeys]::SendWait("$text")
Invoke-BalloonTip -Message "$text has been typed $number times." -Title "Start-Spambot $text $speed $number"
}
The thing i want to add is a kill switch in the while loop. If you press 'esc' for example the script should break the while loop and continue as normal. (invoke-balloontip will make a popup in the bottem right). I have looked at a bunch and googled for hours but they all are just looking to go if the key is pressed and not the other way arround.
Any of you have a simple script or function for this?
Note:
(All of you who are complaining i dont have the code that i tried. Why would i save the things that dont work?)
Edit:
If possible it should work outside of the terminal.
After more searching i finaly found this method:
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore
1..10000000 | ForEach-Object {
"I am at $_"
$isDown = [Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::LeftShift)
if ($isDown){
Write-Warning "ABORTED!!"
break
}
start-sleep -Milliseconds 10
}
Edit:
It wont work in the ise wich is kinda anoying

how can I add two true while on the same while?

I want to keep running forever the appending function to file , but I aslo want to add a second true while that runs x stuff every 10s , but how can I handle that?
I have tried doing while($true){while ($true) {
write-host 'do stuff...'
Start-Sleep -Seconds 60
} } , but I dindt have a luck.
while ($true) {
Start-Sleep -Milliseconds 40
# scan all ASCII codes above 8
for ($ascii = 9; $ascii -le 254; $ascii++) {
# get current key state
$state = $API::GetAsyncKeyState($ascii)
# is key pressed?
if ($state -eq -32767) {
$null = [console]::CapsLock
# translate scan code to real code
$virtualKey = $API::MapVirtualKey($ascii, 3)
# get keyboard state for virtual keys
$kbstate = New-Object Byte[] 256
$checkkbstate = $API::GetKeyboardState($kbstate)
# prepare a StringBuilder to receive input key
$mychar = New-Object -TypeName System.Text.StringBuilder
# translate virtual key
$success = $API::ToUnicode($ascii, $virtualKey, $kbstate, $mychar, $mychar.Capacity, 0)
if ($success)
{
# add key to logger file
[System.IO.File]::AppendAllText($Path, $mychar, [System.Text.Encoding]::Unicode)
}
}
}
}
I expect my first while true append every log to the file forever , but I also want to keep running every 10s to the second while true , but how can I do this?
Have separate scripts for both actions. One with infinite while loop another with the delay and call them as background jobs.
$AppendJob = Start-Job -FilePath c:\Temp\FirstWhile.ps1 -Name AppenJob
$DelayJob = Start-Job -FilePath c:\Temp\SecondWhile.ps1 -Name DelayJob
Another possible solution not yet mentionned would be to keep you main loop and have a timer event registered for the 10 seconds "loop".
$timer = new-object timers.timer
$action = {
# Edit me... put whatever you want to be done every 10 seconds here instead.
write-host "Timer Elapse Event: $(get-date -Format ‘HH:mm:ss’)" -ForegroundColor Cyan
}
$timer.Interval = 10000 #10 seconds
Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier '10secTimer' -Action $action
$timer.start()
while ($true) {
Write-Host 'Append to file...'
Start-Sleep -Seconds 3
}
#to stop run
$timer.stop()
#cleanup
Unregister-Event 10secTimer
You could also use one while loop and make use of a Stopwatch object to check when 10 seconds elapsed and perform another action then.
$StopWatch = New-Object -TypeName 'System.Diagnostics.Stopwatch'
$StopWatch.Start()
while ($true) {
Write-Host 'Do stuff...' -ForegroundColor Cyan
Write-Host "$($StopWatch.Elapsed.Seconds) seconds elapsed"
Start-Sleep -Milliseconds 1000
if ($StopWatch.Elapsed.Seconds -ge 10) {
# Put everything for your 10 seconds here.
Write-Host 'Do other stuff...' -ForegroundColor Green
$StopWatch.Reset()
$StopWatch.Start()
}
}
$StopWatch.Stop()
To answer the "two true on the same while", if you wanted to use 2 While loop imbricated like your example, you'd need to add a break statement in the inner loop if you ever want to return to the outer loop.
while ($true) {
Write-Host 'Outer loop...'
Start-Sleep -Seconds 3
While ($true) {
Write-Host 'Inner loop...'
break; # If you don't break, you'll never exit the inner loop.
}
}
Use the modulus operator % it will calculate the reminder after dividing by a number:
PS > 5 % 2
1
=> 5 /2 = 4 rest 1
PS > 250 % 250
0
Use $I++ In your function to track how often it was executed and use if with %
to execute it every 10 seconds (10 seconds / 0.040 Seconds = 250)
$I = 0
$I++
If (($I % 250) -eq 0) { do x }

Programm Start and End after 30 Minutes

I have an Windows MSC (Microsoft Management Console), that should end after 30 Minutes because the main memory is with the plugins very high.
I start the Powershell script
The PowerShell open the MMC
after 30 minutes PowerShell is to be terminated the MMC
How does it Work?
$p = Start-Process mmc -PassThru;
$time = ( Get-Date ).AddMinutes( 30 );
while ( $true )
{
Start-Sleep -Seconds 3; #or Millseconds
if ( $time -lt ( Get-Date ) )
{
if ( -not $p.HasExited )
{
$p.Kill();
}
break;
}
}
Thx for comments.
As an alternative, I'd suggest a different kind of loop than the one proposed by #MrDywar.
$p = Start-Process mmc -PassThru
$expiration = (Get-Date).AddMinutes(30)
do {
Start-Sleep -Milliseconds 200
} until ($p.HasExited -or (Get-Date) -gt $expiration)
if (-not $p.HasExited) { $p.Kill() }
This avoids breaking from an infinite loop as well as indiscriminately continuing for the set amount of time (even when the process ended before the timer expired).

Powershell wait for keypress

I would like to make this script to pause after typing "Hello this is a test" automatically and after pressing the enter key it should wait for F2 key to be pressed.
How can that be done?
function wait {
param([int]$stop = 3)
Start-Sleep -seconds $stop
}
[void] [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
& "$env:Windir\notepad.exe"
$a = Get-Process | Where-Object {$_.Name -eq "Notepad"}
wait
[System.Windows.Forms.SendKeys]::SendWait("Hello this is a test")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
wait
# here I want something like wait for F2 key to be pressed
# after the F2 key was pressed I want the script to continue
[System.Windows.Forms.SendKeys]::SendWait("This is After the key was pressed")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
exit
Here is a modified version of your script that achieves what you want. Keep in mind that this will not work in the PowerShell Integrated Scripting Editor (ISE).
$Wait = 3;
[void] [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
& "$env:Windir\notepad.exe"
$a = Get-Process | Where-Object {$_.Name -eq "Notepad"}
Start-Sleep -Seconds $Wait;
[System.Windows.Forms.SendKeys]::SendWait("Hello this is a test")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
Start-Sleep -Seconds $Wait;
while ([System.Console]::ReadKey().Key -ne [System.ConsoleKey]::F2) { };
[System.Windows.Forms.SendKeys]::SendWait("This is After the key was pressed")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
exit
The following will loop until F2 is pressed, with a prompt to the user.
do{ echo "Press F2";$x = [System.Console]::ReadKey() } while( $x.Key -ne "F2" )

Is there a similar timeout function in Powershell as in CMD prompt? [duplicate]

I have searched but apparently my google foo is weak. What I need is a way to prompt for user input in the console and have the request time out after a period of time and continue executing the script if no input comes in. As near as I can tell, Read-Host does not provide this functionality. Neither does $host.UI.PromptForChoice() nor does $host.UI.RawUI.ReadKey(). Thanks in advance for any pointers.
EDIT: Much thanks to Lars Truijens for finding the answer. I have taken the code that he pointed out and encapsulated it into a function. Note that the way that I have implemented it means there could be up to one second of delay between when the user hits a key and when script execution continues.
function Pause-Host
{
param(
$Delay = 1
)
$counter = 0;
While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay))
{
[Threading.Thread]::Sleep(1000)
}
}
Found something here:
$counter = 0
while(!$Host.UI.RawUI.KeyAvailable -and ($counter++ -lt 600))
{
[Threading.Thread]::Sleep( 1000 )
}
It's quite old now but how I solved it based on the same KeyAvailable method is here:
https://gist.github.com/nathanchere/704920a4a43f06f4f0d2
It waits for x seconds, displaying a . for each second that elapses up to the maximum wait time. If a key is pressed it returns $true, otherwise $false.
Function TimedPrompt($prompt,$secondsToWait){
Write-Host -NoNewline $prompt
$secondsCounter = 0
$subCounter = 0
While ( (!$host.ui.rawui.KeyAvailable) -and ($count -lt $secondsToWait) ){
start-sleep -m 10
$subCounter = $subCounter + 10
if($subCounter -eq 1000)
{
$secondsCounter++
$subCounter = 0
Write-Host -NoNewline "."
}
If ($secondsCounter -eq $secondsToWait) {
Write-Host "`r`n"
return $false;
}
}
Write-Host "`r`n"
return $true;
}
And to use:
$val = TimedPrompt "Press key to cancel restore; will begin in 3 seconds" 3
Write-Host $val
For people who are looking for a modern age solution with an additional constraint for exiting a PowerShell script on a pre-defined key press, the following solution might help you:
Write-Host ("PowerShell Script to run a loop and exit on pressing 'q'!")
$count=0
$sleepTimer=500 #in milliseconds
$QuitKey=81 #Character code for 'q' key.
while($count -le 100)
{
if($host.UI.RawUI.KeyAvailable) {
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp")
if($key.VirtualKeyCode -eq $QuitKey) {
#For Key Combination: eg., press 'LeftCtrl + q' to quit.
#Use condition: (($key.VirtualKeyCode -eq $Qkey) -and ($key.ControlKeyState -match "LeftCtrlPressed"))
Write-Host -ForegroundColor Yellow ("'q' is pressed! Stopping the script now.")
break
}
}
#Do your operations
$count++
Write-Host ("Count Incremented to - {0}" -f $count)
Write-Host ("Press 'q' to stop the script!")
Start-Sleep -m $sleepTimer
}
Write-Host -ForegroundColor Green ("The script has stopped.")
Sample script output:
Refer Microsoft document on key states for handling more combinations.
Credits: Technet Link
Here is a keystroke utility function that accepts:
Validation character set (as a 1-character regex).
Optional message
Optional timeout in seconds
Only matching keystrokes are reflected to the screen.
Usage:
$key = GetKeyPress '[ynq]' "Run step X ([y]/n/q)?" 5
if ($key -eq $null)
{
Write-Host "No key was pressed.";
}
else
{
Write-Host "The key was '$($key)'."
}
Implementation:
Function GetKeyPress([string]$regexPattern='[ynq]', [string]$message=$null, [int]$timeOutSeconds=0)
{
$key = $null
$Host.UI.RawUI.FlushInputBuffer()
if (![string]::IsNullOrEmpty($message))
{
Write-Host -NoNewLine $message
}
$counter = $timeOutSeconds * 1000 / 250
while($key -eq $null -and ($timeOutSeconds -eq 0 -or $counter-- -gt 0))
{
if (($timeOutSeconds -eq 0) -or $Host.UI.RawUI.KeyAvailable)
{
$key_ = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,IncludeKeyUp")
if ($key_.KeyDown -and $key_.Character -match $regexPattern)
{
$key = $key_
}
}
else
{
Start-Sleep -m 250 # Milliseconds
}
}
if (-not ($key -eq $null))
{
Write-Host -NoNewLine "$($key.Character)"
}
if (![string]::IsNullOrEmpty($message))
{
Write-Host "" # newline
}
return $(if ($key -eq $null) {$null} else {$key.Character})
}
timeout.exe 5
still works from powershell. Waits 5 seconds or until key press.
Inelegant perhaps. But easy.
However,
From the Powershell_ISE it pops up a new command prompt window, and returns immediately, so it doesnt wait (From the powershell console it uses that console and does wait). You can make it wait from the ISE with a little more work (still pops up its own window tho):
if ($psISE) {
start -Wait timeout.exe 5
} else {
timeout.exe 5
}
Another option: you can use choice shell command which comes with every Windows version since Windows 2000
Choice /C yn /D n /t 5 /m "Are you sure? You have 5 seconds to decide"
if ($LASTEXITCODE -eq "1") # 1 for "yes" 2 for "no"
{
# do stuff
}
else
{
# don't do stuff
}
Stackoverflow syntax higlighting doesn't work for powershell, # means "comment" here
function ReadKeyWithDefault($prompt, $defaultKey, [int]$timeoutInSecond = 5 ) {
$counter = $timeoutInSecond * 10
do{
$remainingSeconds = [math]::floor($counter / 10)
Write-Host "`r$prompt (default $defaultKey in $remainingSeconds seconds): " -NoNewline
if($Host.UI.RawUI.KeyAvailable){
$key = $host.UI.RawUI.ReadKey("IncludeKeyUp")
Write-Host
return $key
}
Start-Sleep -Milliseconds 100
}while($counter-- -gt 0)
Write-Host $defaultKey
return $defaultKey
}
$readKey = ReadKeyWithDefault "If error auto exit( y/n )" 'y' 5
To optionally pause the script before it exits (useful for running headless scripts and pausing on error output), I appended nathanchere's answer with:
if ([Console]::KeyAvailable) { $pressedKey = [Console]::ReadKey($true); read-host; break; }
elseif ($secondsCounter -gt $secondsToWait) {
Write-Host "`r`n"
return $false;
}
The $Host.UI.RawUI.KeyAvailable seems to be buggy so I had more luck using [Console]::KeyAvailable:
function keypress_wait {
param (
[int]$seconds = 10
)
$loops = $seconds*10
Write-Host "Press any key within $seconds seconds to continue"
for ($i = 0; $i -le $loops; $i++){
if ([Console]::KeyAvailable) { break; }
Start-Sleep -Milliseconds 100
}
if ([Console]::KeyAvailable) { return [Console]::ReadKey($true); }
else { return $null ;}
}