Summary: Use Windows PowerShell to interact with the console display.
Honorary Scripting Guy, Sean Kearney, here—filling in for our good friend, Ed Wilson.
For some reason, I find a need to open today’s blog post like a soap opera—you know, “Last time for Sean Kearney on the Hey, Scripting Guys! Blog.”
Sigh, it feels that way…
Last time, we played with a hash table and keyboard input to create a simple toy to play with—a loud, off-key piano in Windows PowerShell. It is somewhat (but not much) worse than my singing voice.
Today we’re going to polish it off a bit. A little error trapping. And how about an actual keyboard on the screen to interact with our piano?
First, I’ll show you a simple keyboard that I spent all day drawing up (or maybe it was only my lunch hour):
[array]$keyboard=$Null
$keyboard+=""
$keyboard+=" _____________________________________________________________"
$keyboard+=" ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !"
$keyboard+=" ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !"
$keyboard+=" ! !___! !___! ! !___! !___! !___! ! !___! !___! !"
$keyboard+=" ! ! ! ! ! ! ! ! ! ! !"
$keyboard+=" ! ! ! ! ! ! ! ! ! ! !"
$keyboard+=" !_____!_____!_____!_____!_____!_____!_____!_____!_____!_____!"
$keyboard+=""
$keyboard+=" *** Be A True Rockstar and Use WindowsPowerShell *** "
This may not be a fancy keyboard, but it will work nicely. We are going to map the keyboard that we built yesterday to physical positions on the screen.
What am I trying to do?
I am going to make sure that every time I hit a note on the keyboard, a key is highlighted on the screen. So we’ll need to identify the row and column for each note.
The first part is easy. All sharps are on one row (upper), and the rest of the keys are on the lower row. I played with the following combinations to set the positions on the screen:
$Location=$Host.UI.RawUI.cursorposition
$Location.Y=4
$Location.X=10
$Host.UI.RawUI.cursorposition=$Location; WRITE-HOST “*”
By altering values of Y, I was able to determine my rows:
Upper 3
Lower 6
Next, the less tricky part was to figure out the first position for each set of keys. After playing with the previous set of cmdlets and altering the value of X, I was able to determine the starting point for each: the lower row starts at position 6 and the upper row starts at position 9.
From that point, it was a matter of some simple math to determine the remaining columns because each key is exactly 6 points apart.
So now for some fun. Because we need to apply the X and Y coordinates based on the key that is tapped on the piano keyboard, we have to extend our original hash table as follows by adding a column to hold the Y and X values respectively for each key to display:
[array]$piano=$NULL
$piano+=@{"Q"=290,6,6}
$piano+=@{"2"=310,3,9}
$piano+=@{"W"=330,6,12}
$piano+=@{"3"=345,3,15}
$piano+=@{"E"=360,6,18}
$piano+=@{"R"=390,6,24}
$piano+=@{"5"=415,3,27}
$piano+=@{"T"=440,6,30}
$piano+=@{"6"=460,3,33}
$piano+=@{"Y"=480,6,36}
$piano+=@{"7"=505,3,39}
$piano+=@{"U"=530,6,42}
$piano+=@{"I"=580,6,48}
$piano+=@{"9"=605,3,51}
$piano+=@{"O"=630,6,54}
$piano+=@{"0"=670,3,57}
$piano+=@{"P"=710,6,60}
With the values of the potential X and Y positions mapped to each key, we can search like before. But now, it will return not only the note, but also the Y and X coordinates for each key.
We can use a simple piece of script like the following to obtain the current location, and then write some information to the screen:
$Location=$Host.UI.RawUI.cursorposition
$Location.Y=$keyboarddata[1]
$Location.X=$keyboarddata[2]
$Host.UI.RawUI.CursorPosition=$Location
WRITE-HOST “*”
To return the original cursor position and erase the information, we can simply reset the position:
# Insert a Command we might do to eat up time, like maybe our [console]::beep(500,200)
$Host.UI.RawUI.cursorposition=$Location
WRITE-HOST " "
So really, we’re going to have a loop that runs forever. Or we could insert a special key—maybe such as “ESC”ape to “Escape” the loop?
do {
# Wait until some fool hits the keyboard
# We're not going to show the key on the screen
# And yes, we'll include Shift, Control and all the others
$Key=$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
# Now flush it away so we don't get a pile of keys
# backing up in the queue (Flush? Backup? Ewww)
$Host.UI.RawUI.Flushinputbuffer()
# Grab the character from the $key Object and Match
# It Against the Array
$KeyboardData=$Piano.($Key.Character)
# Now IF we found something of value
# PLAY IT
If ($KeyboardData) {
$note=$KeyboardData[0]
$Location=$Host.UI.RawUI.cursorposition
$Location.Y=$keyboarddata[1]
$Location.X=$keyboarddata[2]
$Host.UI.RawUI.cursorposition=$Location
WRITE-HOST "*"
[console]::beep($note,150)
$Host.UI.RawUI.cursorposition=$Location
WRITE-HOST " "
}
# Key having fun until Somebody tries to ESCape
# Get it? Hit's the ESCape key? Bad Pun?
} until ( $key.VirtualKeyCode -eq 27 )
There you have it. A silly but fun project in Windows PowerShell that leverages hash tables and the console.
For some fun trivia, I did actually try to launch multiple beeps through different Windows PowerShell consoles to try to generate chords. No good. Doesn’t work. Although I would love to figure out later how to generate MIDI tones via Windows PowerShell. That could be more interesting.
If you don’t feel like doing a lot of typing, download it here from the TechNet Gallery: A Windows PowerShell Piano.
I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Sean Kearney,
Honorary Scripting Guy and Windows PowerShell MVP