Hey, Scripting Guy! Interacting with the Windows PowerShell Console Window

Hey, Scripting Guy! Interacting with the Windows PowerShell Console Window

  • Comments 1
  • Likes

Bookmark and Share


About the author
: Tibor Soós is a Windows PowerShell MVP who lives in Hungary.

 

I started to learn programming in 1982 at the age of 12. My father bought a little Z80 based computer, named Mickey '80, which was produced by a computer manufacturing plant of an agricultural collective farm. Strange things happened in Hungary, behind the iron curtain, during those times. That computer had a BASIC interpreter, but was not capable of any graphics. It could only draw monochrome characters and some symbols on the monitor. But it was perfect for a beginner like me.

 

I first met Windows PowerShell 25 years later, and when I looked at the console window, I felt nostalgic. The character-based Windows PowerShell window reminded me of the monitor of the Mickey ’80. So I decided to bring my first simple computer games I wrote in BASIC to life. Those games are mostly based on three capabilities of that computer: the capability to write characters to a specific position of the screen, the ability to check and return the character of the key that was pressed on the keyboard, and the capability to read a character from a specific position on the screen. Unfortunately, none of these features are included in Windows PowerShell, so it was a challenge for me to explore the capabilities of the Windows PowerShell environment and the .NET Framework to find the necessary components to build the required functions.

 

These functions will not only serve my childish goals. Printing data to a specific position on the console window can be very handy in any script that runs for a longer period of time, and if you want to see some status information in the same position of the window over time without the scrolling of the screen. Checking if a keyboard button is pressed or not and giving back the character of the key can be useful for those scripts, which you want to control by the keyboard but without interrupting its run. For example, doing a bulk change of thousands of files, you just want to see the actual file that your script is working on by a key press. It’s like getting a snapshot of the status by hitting a key. Using the third function in an IT environment needs some more imagination that I don’t possess.

 

Let’s see the first function:

function WriteTo-Pos ([string] $str, [int] $x = 0, [int] $y = 0,

      [string] $bgc = [console]::BackgroundColor,

      [string] $fgc = [Console]::ForegroundColor)

{

      if($x -ge 0 -and $y -ge 0 -and $x -le [Console]::WindowWidth -and

            $y -le [Console]::WindowHeight)

      {

            $saveY = [console]::CursorTop

            $offY = [console]::WindowTop       

            [console]::setcursorposition($x,$offY+$y)

            Write-Host -Object $str -BackgroundColor $bgc `

                  -ForegroundColor $fgc -NoNewline

            [console]::setcursorposition(0,$saveY)

      }

}

The main idea behind this is the [console] class. More precisely it is called [system.console], but in Windows PowerShell we can omit the system part. The static properties and methods of this class represent many of the aspects of the Windows PowerShell console window. The only gotcha is that the coordinates of the characters in the window are relative to the whole window buffer, not just to the visible part of the window. So I had to use an offset.

 

Understanding this, the definition of the WriteTo-Pos function is quite straightforward. The function has a $str string parameter that is the data that should be written to the screen. The coordinates are $x and $y, where the data should be written within the visible part of the screen. The default is the upper left corner of the screen, which is the $x=0 and $y=0 position. There is also an option to set the background and foreground color of the text that will be written.

 

In the body of the definition, I check if the coordinates fit in the window. After that I save the current cursor position, which is relative to the windows buffer, and I calculate the offset of the y coordinate to the WindowsTop static property of the [console] class.

 

Then I set the position of the cursor to the proper place within the visible part of console window, and write the $str there in the given colors. With the NoNewLine switch I ensure that the window content will not scroll. At the end, I reset the cursor position back to the saved value.

 

With this function we can draw wonderful shapes to the console window. For example, this function below produces a circle. The trick here was to find out the aspect ratio of the selected console font, which is stored in the $deform variable and is calculated by the current video resolution data read from WMI and the maximum console window dimensions:

function Draw-Circle ($x, $y, $r)

{

      $step = [int] ([Math]::Atan(1/$r)*30)

      $px = (Get-WmiObject -Class Win32_VideoController

            ).currenthorizontalresolution

      $py = (Get-WmiObject -Class Win32_VideoController

            ).currentverticalresolution

      $cx = [Console]::LargestWindowWidth

      $cy = [Console]::LargestWindowHeight

      $deform = $py/$cy/$px*$cx

      for($i =0; $i -lt 360; $i+=$step)

      {

            $oy = [int] ($y + $r*[math]::sin($i/180*[math]::pi))

            $ox = [int] ($x + $r*[math]::cos($i/180*[math]::pi)*$deform)

            switch($i)

            {

                  {$i -gt 337} {$c = "|"; break}

                  {$i -gt 292} {$c = "\"; break}

                  {$i -gt 247} {$c = "-"; break}

                  {$i -gt 202} {$c = "/"; break}

                  {$i -gt 157} {$c = "|"; break}

                  {$i -gt 112} {$c = "\"; break}                 

                  {$i -gt 67} {$c = "-"; break}

                  {$i -gt 22} {$c = "/"; break}

                  default {$c = "|"}

            }

            WriteTo-Pos $c $ox $oy

      }

}

The script output is shown in the following image.

Image of the script output

The next function reads the key of the keyboard, but only if the key is pressed:

function Get-KeySilent

{

    if([console]::KeyAvailable){

            while([console]::KeyAvailable){

                  $key = [console]::readkey("noecho").Key}}

    else{$key = "nokey"}

    $key

}

Unfortunately the [console]::readkey() method alone did not meet my expectations, because it waits until a key is pressed, so it would halt the script. The trick here in my function is that I call the ReadKey method only when I already know by the KeyAvailable property that a key have been pressed, and when it has been in the keyboard buffer. It might happen that multiple key presses are accumulated by the time my Get-KeySilent function is called, so I clear the buffer by reading it with a While loop. If there is no key pressed, the function returns nokey; otherwise, it returns the key last pressed.

 

The last function is the one that reads a character from a position of the console window:

function ReadFrom-Pos ($x, $y)

{

      if($x -ge 0 -and $y -ge 0 -and $x -le [Console]::WindowWidth -and

            $y -le [Console]::WindowHeight) {

      $y += [console]::WindowTop

      $r = New-Object System.Management.Automation.Host.Rectangle $x,$y,$x,$y

      $host.UI.RawUI.GetBufferContents($r)[0,0]

      }

}

The idea behind this is similar to my first function, but I had to ask for help from another object, the $host automatic variable. Its UI.RawUI property has properties that are more or less the same as with the properties of the [console] class, but its GetBufferContents method is unique. It expects a Rectangle type of object as a parameter. In my case, that rectangle contains only one character and is stored in the $r variable. Generally, that method returns a two-dimensional array. In my case, because it has only one element, I referenced it by the [0,0] index.

 

This function returns a BufferCell object, which has the following properties:

PS C:\> ReadFrom-Pos 0 0

 

          Character     ForegroundColor     BackgroundColor     BufferCellType

          ---------     ---------------     ---------------     --------------

                  P               Black               White           Complete

In this example I could tell that the “p” character was in the (0,0) position of the console window, its foreground color was black, and its background color was white.

 

With these building blocks, you can write little games like Snake or Autorace.

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • This helped me finally capture the latest keypress input *without* stopping my script. Much obliged!!!