Pausing a Windows PowerShell Script to Receive Keyboard Input

Pausing a Windows PowerShell Script to Receive Keyboard Input

  • Comments 4
  • Likes

 

Summary: Pausing execution of a Windows PowerShell script to receive keyboard input can be as simple as using the Read-Host cmdlet. But other methods are available.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I would like to pause a Windows PowerShell script and wait for user input. When the user types a particular key on the keyboard, I would like the script to continue. I found an old Hey, Scripting Guy! post called How Can I Pause a Script and Then Resume It When a User Presses a Key on the Keyboard? but it is written for VBScript. I do not think I want to attempt using stdin from a Windows PowerShell script. I also found an article called Can I Pause and Resume Scripts with Windows PowerShell? that was written for the 2009 Summer Scripting Games, but that does not work with user input. Is the absence of a solution indicative that I cannot accomplish this using Windows PowerShell?

-- TC

 

Hey, Scripting Guy! AnswerHello TC,

Microsoft Scripting Guy Ed Wilson here. I was up late last night talking to the System Administrators Guild of Australia (SAGE-AU) at their conference in Tasmania. It was fun, but I would rather have been there in person. Tasmania is one of the places I have not had the chance to visit. I fell in love with Tasmanian strawberries when I was in Sydney because they are super sweet, but recently they have started growing strawberries that are in the shape of a heart. Those would make a wonderful present for my significant other.

Anyway, TC, sometimes things are so simple that they do not receive much publicity. One such example is pausing a script to wait for user input.

PauseScriptReadName.ps1

$a = read-host "what is your name?"
"hello $a"

When the PauseScriptReadName.ps1 script runs in the Windows PowerShell ISE, a pop-up window appears, as shown in the following image.

There is, of course, one teeny tiny problem with the script as written. It will pause forever and ever, until the user enters data, or until the computer either restarts or shuts down. This behavior could have adverse consequences to say the least.

TC, a completely different approach is seen in the pauseTimer.ps1 script shown here:

pauseTimer.ps1

#Requires -version 2.0
Param($timer = 10)
Function Test-IsIseHost
{
<#
.Synopsis
Determines if you are running in the Windows PowerShell ISE
.Description
This function determines if you are running in the Windows Powershell
ISE by querying the $ExecutionContext automatic variable.
.Example
Test-IsIseHost
Prints out True if running inside the ISE, False if run in console
.Example
if(Test-IsIseHost) { "Using the ISE" }
Prints out Using the ISE when run inside the ISE, otherwise nothing
.Inputs
None
.Outputs
[Boolean]
.Notes
Name: Test-IsIseHost
Book: Windows PowerShell Best Practices, Microsoft Press, 2009
Author: Ed Wilson
Version: 1.0
Date: 4/5/2009
.Link
about_Automatic_variables
Http://www.ScriptingGuys.Com
#>
$ExecutionContext.Host.name -match "ISE Host$"
} #end Test-IsIseHost
function Select-Destination($strIN)
{ 
Clear-Host
switch($strIN.character)
{
"l"
{ "$($strIN.Character): local selected" ; exit }
"r"
{ "$($strIN.Character): Remote selected" ; exit }
DEFAULT { "$($strIN.Character) is not valid. CountDown Resuming ..." ; break}
}
} #end function Select-Destination
# *** Entry point to script ***
If(Test-IsIseHost) {"This script does not run in ISE" ; exit}
"Select machine:"
" l <ocal> r <emote> "
$i = 1
Do
{
Write-host -ForeGroundColor green -noNewLine "Script will time out in $($timer-$i) seconds"
$pos = $host.UI.RawUI.get_cursorPosition()
# $pos.set_x(0) # This was 1.0 syntax
$pos.X = 0 # this is 2.0 syntax
$host.UI.RawUI.set_cursorPosition($Pos)
if($host.UI.RawUI.KeyAvailable) 
{ 
Select-Destination($host.ui.rawui.readkey()) 
}
start-Sleep -Seconds 1
if( [math]::log10($timer-$i) -eq [math]::truncate([math]::log10($timer-$i)) )
{ Clear-Host }
$i++
}While ($i -le $timer) ; Clear-Host ; "Script timed out after $timer seconds" ; exit

Keep in mind that the pauseTimer.ps1 script does not run in the Windows PowerShell ISE. As a result, the Test-IsIseHost function is used. If the ISE host is found, the script will exit after displaying a message that the script does not run in the ISE host. The Test-IsIseHost function is shown here:

Function Test-IsIseHost
{
<#
.Synopsis
Determines if you are running in the Windows PowerShell ISE
.Description
This function determines if you are running in the Windows Powershell
ISE by querying the $ExecutionContext automatic variable.
.Example
Test-IsIseHost
Prints out True if running inside the ISE, False if run in console
.Example
if(Test-IsIseHost) { "Using the ISE" }
Prints out Using the ISE when run inside the ISE, otherwise nothing
.Inputs
None
.Outputs
[Boolean]
.Notes
Name: Test-IsIseHost
Book: Windows PowerShell Best Practices, Microsoft Press, 2009
Author: Ed Wilson
Version: 1.0
Date: 4/5/2009
.Link
about_Automatic_variables
Http://www.ScriptingGuys.Com
#>
$ExecutionContext.Host.name -match "ISE Host$"
} #end Test-IsIseHost

The Select-Destination function is used to evaluate the key that the user presses in response to the prompt. In this script, it does little more than confirm which keystroke was pressed, but it illustrates a technique for parsing the command-line input and determining which action to undertake. The Clear-Host function (yep, it is a function and not a cmdlet) clears the Windows PowerShell console of all output. The Switch statement is then used to determine if l or r was pressed. If any other key is pressed, a message states that the choice is invalid, and the countdown resumes. The Select-Destination function is shown here:

function Select-Destination($strIN)
{ 
Clear-Host
switch($strIN.character)
{
"l"
{ "$($strIN.Character): local selected" ; exit }
"r"
{ "$($strIN.Character): Remote selected" ; exit }
DEFAULT { "$($strIN.Character) is not valid. CountDown Resuming ..." ; break}
}
} #end function Select-Destination

If the script is running inside the Windows PowerShell console, a string is displayed that tells the user to choose between local or remote. The counter variable is also initialized at this time. This section of the script is shown here:

If(Test-IsIseHost) {"This script does not run in ISE" ; exit}
"Select machine:"
" l <ocal> r <emote> "
$i = 1

Inside a Do…While…Loop construction, the Write-Host cmdlet is used to display a string in green that states that the script will time out in a certain number of seconds (the default is 10 seconds). The current cursor position is obtained and stored in the $pos variable. In Windows PowerShell 1.0 there was a set_x method that was used to position the cursor on the Windows PowerShell command line. This method does not exist in Windows PowerShell 2.0; it has been replaced by a variable “x” that is read/write. The Set_CursorPosition method takes a cursorPosition object and moves the cursor position to the position indicated. In this case, the insertion point is moved to the beginning of the Windows PowerShell console line, and therefore the green timer text appears to scroll. Way cool!

Do
{
Write-host -ForeGroundColor green -noNewLine "Script will time out in $($timer-$i) seconds"
$pos = $host.UI.RawUI.get_cursorPosition()
# $pos.set_x(0) # This was 1.0 syntax
$pos.X = 0 # this is 2.0 syntax
$host.UI.RawUI.set_cursorPosition($Pos)

Next the KeyAvailable property is queried. If a key is available, it means that someone pressed a key on the computers keyboard. When this happens, the readkey method is used to determine which key was pressed, and this value is passed to the Select-Destination function to determine what will take place next. This allows the script to respond to a “keypress event” and can lead to some very cool scripts. When I was teaching a Windows PowerShell class in Sydney, Australia, a few years ago, a student wrote a version of the old-fashioned video game Asteroids in class. He had never seen Windows PowerShell prior to class. It was very cool, only required a few lines of code, and used the members of Ui.RawUi I have discussed here.

If a key is not available, the Start-Sleep cmdlet pauses the script for a second, and a couple of math methods are used to determine if it is time to time out the script.

(Don’t ask why I did this. I am mainly playing around here. In fact, this entire script is basically playing around. I could have simply added the Do…While…Loop to the Read-Host cmdlet I began the script with. But then I would not have been able to talk about the Ui.RawUi members, or the change that was introduced into Windows PowerShell 2.0.)

if($host.UI.RawUI.KeyAvailable) 
{ 
Select-Destination($host.ui.rawui.readkey()) 
}
start-Sleep -Seconds 1
if( [math]::log10($timer-$i) -eq [math]::truncate([math]::log10($timer-$i)) )

The last thing that needs to be done is to clear the Windows PowerShell console and increment the counter variable. This is accomplished while the counter is less than the time specified for the timer. When that is no longer the case, the host is again cleared, and a string states the script timed out. This is shown here:

{ Clear-Host }
$i++
}While ($i -le $timer) ; Clear-Host ; "Script timed out after $timer seconds" ; exit

The following video seen illustrates running the script, and contains my description of the script.

Click here to play this video

 

TC, that is all there is to pausing a Windows PowerShell script to wait for user intervention. This also concludes Running Windows PowerShell Scripts Week. Join us tomorrow as we dip into the virtual mail bag to address some of the email sent to the scripter@microsoft.com alias.

We would love for you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Part 2

    # Put the cursor back to the spot it was on the screen prior to calling the progress bar.

    $host.UI.RawUI.set_cursorPosition($posEnd)

    if ($inputCheck -and $inputCheck.ReturnCode -eq 0) {

       # Here I can handle what I want to do with one of the 2 choices.

       "Choice: $($inputCheck.Choice)"

    } else {

       "Script timed out after $timer seconds"

    }

    exit

  • My part 1 of my code never shows up in the comments. I previously wrote:

    "I realize this is an old post, but I thought I would comment since this helped in something I was doing. I wanted a prompt to ask the user if they wanted to continue or not, but also wanted the choice to timeout.  Since I wanted a timeout, that eliminated using $host.ui.PromptForChoice. I also didn't want to fall back and use the dos choice command.  I wanted a pure PowerShell solution.  I ended up modifying your pauseTimer.ps1 script to use the  Write-progress cmdlet to give a slicker solution to the problem.   I'm posting my modified code on the off chance someone else would like to incorporate this solution into their script."

  • PS > sleep 1

    PS > sleep 2

    PS > sleep 3

    PS >sleep ($perchance_to_dream=10);'Aye! - there's the rub!'

    Aye! - there's the rub!

  • The post is spot -on.  It is just showing us how to timeout a read.

    Here is a trivial demo of the same thing:

    How to timeout a keystroke....

    Just probe for the key

    $i=0

    While(-not $host.UI.RawUI.KeyAvailable){

        $i++

        write-host $i

        sleep 2

    }

    $host.UI.RawUI.ReadKey()