Draw Boxes and Lines in the Windows PowerShell Console Host

Draw Boxes and Lines in the Windows PowerShell Console Host

  • Comments 2
  • Likes

 

Summary: Microsoft MVP Sean Kearney shows how to draw boxes and lines in the Windows PowerShell console while scripting three reusable functions.

 

Microsoft Scripting Guy Ed Wilson here. Just when I thought it was safe to open my email, I get a tweet from Sean Kearney saying he has a present for me. Low and behold, Sean has sent me a really cool article.

Sean Kearney is a network administrator, a Microsoft Certified Technology Specialist in Windows Server Virtualization and Configuration, a Windows PowerShell MVP, and a Microsoft Certified Systems Engineer. Sean is a devoted and passionate computer enthusiast from the early 80s to the present day, having used just about every microcomputer ever. Sean taught himself computer programming with 65xxmachine code, working with many technologies―but primarily Microsoft. Sean deals with “anything thrown at him,” from gnawed keyboards to recovery of Exchange servers to networking setups and isolating the realm of the unknown. Currently, he tests and deploys just about any new Microsoft Technology; he also deals with users in an enterprise class environment. Sean loves Windows PowerShell, Windows 7, and Hyper-V, in that order. You will often find him hanging out online at http://www.powershell.ca.

Here is Sean’s article.

 

I decided to have some fun. Because isn’t that why we got into computers in the first place? It was fun on some level? We enjoyed it? Right? So I was thinking back to my BBS days and ASCII art and plotting information on the console screen.

You see, using the $HOST variable in Windows PowerShell allows you to move the cursor to any spot (within reason) on the console. After you have moved it, you can put something there. So I’m thinking to myself, “Boy, I’d love to build a nice box on the screen!” So I sat down and wrote a really bad script.  It was bad because it worked, but the coding was just horrible and repetitive. What I ended up with (for a single box) was about four foreach loops, a whole pile of $host.ui.raw.cursorposition settings, and about 90 lines of code.

Then I started reexamine it. Because a lot of the code did actually repeat in most respects, I thought there had to be a better way. The first part was easy. Make a function to position the cursor, mostly to make the code more readable, but also because I happen to like readable functions.

--------------- Set Console Position ----------------
function global:set-ConsolePosition ([int]$x,[int]$y) {
# Get current cursor position and store away
$position=$host.ui.rawui.cursorposition
# Store new X and Y Co-ordinates away
$position.x=$x
$position.y=$y
# Place modified location back to $HOST
$host.ui.rawui.cursorposition=$position
}
--------------- Set Console Position ----------------

Next, I realized the code kept drawing lines. So I thought, “Wouldn’t it make more sense to have a smart function that could draw those lines"?”

So onto task number two. Make a function that doesn’t just draw a line on the screen but would be smart enough to know whether I was doing it vertically or horizontally. It would have to know how long the line was and ideally what coordinates to draw it on.

The function has to receive the X and Y position of where the line has to go, receive length, and know if it is a vertical line:

function global:draw-line([int]$x, [int]$y, [int]$length,[int]$vertical){ 

Then set the line to start at the defined coordinates. My last function, set-ConsolePosition, makes this very easy:

set-ConsolePosition $x $y

Now here’s where the fun starts. I could just draw a box with asterisks and be done, but I’d like it to look nicer. So each line will start with an asterisk but have a “-“ or a “!”, depending on whether it’s drawn vertically or horizontally:

If ([boolean]$vertical) 
   
{ $linechar="!"; $vert=1;$horz=0}
else
   
{ $linechar="-"; $vert=0;$horz=1} 

Instead of a big funky { if then else }, I decided to tie it into the math. So I sat down and thought out the flow of the code. Okay, all I did really was think “If I’m drawing vertically, the Y coordinate will always change; otherwise, X is always going to change”:

set-ConsolePosition (($horz*$count)+$x) (($vert*$count)+$y)

So with this little line above here I make sure to keep the counter changing the value of the X position or the Y position (but not both) depending on how it receives the $Vertical from the function. This line

write-host $linechar -nonewline

will always be the same for drawing because we have predefined the character we are drawing with. Overall, you get this to draw a line now:

--------------- Draw line Function ----------------
function global:draw-line([int]$x, [int]$y, [int]$length,[int]$vertical){ 
# Move to assigned X/Y position in Console 
set-ConsolePosition $x $y
# Draw the Beginning of the line
write-host "*" -nonewline 
# Is this vertically drawn?  Set direction variables and appropriate character to draw 
If ([boolean]$vertical) 
   
{ $linechar="!"; $vert=1;$horz=0}
else
   
{ $linechar="-"; $vert=0;$horz=1} 
# Draw the length of the line, moving in the appropriate direction 
   
foreach ($count in 1..($length-1)) { 
       
set-ConsolePosition (($horz*$count)+$x) (($vert*$count)+$y)
       
write-host $linechar -nonewline
   
}
# Bump up the counter and draw the end
$count++
   
set-ConsolePosition (($horz*$count)+$x) (($vert*$count)+$y) 
write-host "*" -nonewline 
} 
--------------- Draw line Function ----------------

Now here is where I went overboard. My brain went, “Hey, I can draw a line, I can pick a direction, I could make a box!” You can tell my brain didn’t get a lot of vacation time during the summer. But I gave in and thought it out. Really, a box wasn’t so difficult. Two horizontal lines, two vertical lines. I already have a function to draw lines.

So I could just call up the line draw function with the appropriate data and just run it right? Nope. I could but there was this geeky little voice saying “Reuse the code!” So with a Foreach loop and some fancy math, I figured out a way to reuse almost every piece of sanity.

So I thought, what is unique about each line in a box? The position, the X/Y start position, and the length. So:

  • The square would draw two horizontal lines and then two vertical lines.
  • Each horizontal line the “X” position does not change for starting point.
  • Each vertical line the “Y” position does not change for starting point.

So you end up with a pattern like this:

  • 0 Horizontal
  • 0 Horizontal
  • 1 Vertical
  • 1 Vertical

Each Row was either Top Left or Bottom Right:

  • 0 Top
  • 1 Bottom
  • 0 Left
  • 1 Right

Which gave me this pattern:

  • 0 0 Top Horizontal
  • 0 1 Bottom Horizontal
  • 1 0 Left Vertical
  • 1 1 Right Vertical

We have a loop that goes through four times and can flip variables and do all sorts of funky stuff to make a box.

---------------- Draw the Box ----------------------------------
function global:draw-box ([int]$width, [int]$length, [int]$x, [int] $y) { 
# Do the four sides 
foreach ($box in 0..3) { 
    
# Variable to flip whether we’re on the left / top of the box or not
    
$side=$box%2 
    
# Variable to switch whether it’s a vertical or horizontal line
    
$vert=[int](($box-.5)/2) 
    
# compute the Width and Length so we can “switch them”
    
$totalside=$width+$length 
    
# Length of line will be dependant on the Direction
    
# (vertical or Horizontal)
    
$linelength=($vert*$length)+([int](!$vert)*$width) 
    
$result=$totalside-$linelength 
    
# flip in the correct X Y coordinates for the maximum 
    
$ypass=([int](!$vert)*$side*$result)+$y 
    
$xpass=($vert*$side*$result)+$x 
    
# Draw the line 
    
draw-line $xpass $ypass $linelength $vert 
} 
}
---------------- Draw the Box ---------------------------------- 

So what do we have ? A series of three functions, and a pretty box on the screen.

What have we learned?

  1. You can use some incredibly powerful functions in Windows PowerShell.
  2. With proper math and functions, you can make a lot of code reusable.
  3. Sean has way too much time on his hands.

 

Sean, thank you for sending me your post. I put all three functions into a single script, loaded it into my Windows PowerShell console, moved my console point, and drew a box and a line. The results are shown in the following image.

Image of script results

 

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 when we begin a new week on the Script Center. We will have an awesome series of articles about Windows PowerShell cmdlets and SharePoint 2010, written by celebrated SharePoint author Niklas Goude. 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
  • Aww.... don't draw boxes with alphanumeric characters when you've got the whole ANSI/ASCII character set, Sean! :) That's what box characters are made for! Imagine how you'd feel if we tried to make PowerShell songs without you! ;-)

    (fye: unicode characters in the range 9472..9575)

    On another note, for stuff like drawing boxes, you should at least consider the advanced $Host.UI.RawUI.SetBufferContents and GetBufferContents ... check out http://poshcode.org/2238 for some a couple scenarios involving boxes with text inside them and inline scrolling/prompting (I just posted it, but it's just an improvement over an old script that was up there).

  • Joel

    I was feeling "Really Retro" that day :)

    I wanted "Pure Ascii" not that polluted stuff :P