PoshCode Logo PowerShell Code Repository

BufferBox 3.6 by Joel Bennett 5 years ago (modification of post by Joel Bennett view diff)
View followups from Joel Bennett | diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/2898"></script>download | new post

Added a useful Show-ConsoleMenu (which is also usable outside the buffer box), but this one is coded for 10 items or less.

  1. ####################################################################################################
  2. ## This script is just a demonstration of some of the things you can do with the buffer
  3. ## in the default PowerShell host... it serves as a reminder of how much work remains on
  4. ## PoshConsole, and as an inspiration to anyone who is thinking about trying to create
  5. ## "interactive" PowerShell applications.
  6. ##
  7. ## Try Test-DisplayBox and then Test-BufferBox (note it has tab completion and everything).
  8. ####################################################################################################
  9. $global:BoxChars = new-object PSObject -Property @{
  10.    'HorizontalDouble'            = ([char]9552).ToString()
  11.    'VerticalDouble'              = ([char]9553).ToString()
  12.    'TopLeftDouble'               = ([char]9556).ToString()
  13.    'TopRightDouble'              = ([char]9559).ToString()
  14.    'BottomLeftDouble'            = ([char]9562).ToString()
  15.    'BottomRightDouble'           = ([char]9565).ToString()
  16.    'HorizontalDoubleSingleDown'  = ([char]9572).ToString()
  17.    'HorizontalDoubleSingleUp'    = ([char]9575).ToString()
  18.    'Horizontal'                  = ([char]9472).ToString()
  19.    'Vertical'                    = ([char]9474).ToString()
  20.    'TopLeft'                     = ([char]9484).ToString()
  21.    'TopRight'                    = ([char]9488).ToString()
  22.    'BottomLeft'                  = ([char]9492).ToString()
  23.    'BottomRight'                 = ([char]9496).ToString()
  24.    'Cross'                       = ([char]9532).ToString()
  25.    'VerticalDoubleRightSingle'   = ([char]9567).ToString()
  26.    'VerticalDoubleLeftSingle'    = ([char]9570).ToString()
  27. }
  28. $global:RectType = "system.management.automation.host.rectangle"
  29.  
  30. function Show-ConsoleMenu {
  31. param(
  32.    [Parameter(ValueFromPipeline=$true)]
  33.    [PSObject[]]$choices,
  34.    [Alias("Title")]
  35.    [string]$Caption,
  36.    [int]$indentLeft=8,
  37.    [Switch]$Passthru,
  38.    [Switch]$UseBufferBox
  39. )
  40. begin {
  41.    $allchoices = New-Object System.Collections.ArrayList
  42. }
  43. process {
  44.    if($choices) {
  45.       $allchoices.AddRange($choices)
  46.    }
  47. }
  48. end {
  49.    # Make a hashtable with keys
  50.    for($i=0; $i -lt $allchoices.Count; $i++) { $allchoices[$i] | Add-Member NoteProperty ConsoleMenuKey $i  }
  51.  
  52.    # output the menu to the screen
  53.    $menu = $allchoices | Format-Table -HideTableHeader @{E="ConsoleMenuKey";A="Right";W=$indentLeft}, @{E={$_};A="Left"} | Out-String
  54.    $menu = "`n" + (" " * ($indentLeft/2)) + $Caption + "`n" + $menu.TrimEnd() + "`n"
  55.  
  56.    if($UseBufferBox) {
  57.       $menu -split "`n" | Out-Buffer
  58.    } else {
  59.       $menu
  60.    }
  61.    # get a choice from the user
  62.    do {
  63.       $Key = $Host.UI.RawUI.ReadKey("IncludeKeyDown,NoEcho").Character
  64.       try { [int][string]$choice = $Key } catch { [int][char]$choice = $Key }
  65.    } while(!$allchoices.Count -and !(13,27 -contains $Key))
  66.  
  67.    if($Passthru) { $allchoices[$choice] } else { $choice }
  68. }}
  69.  
  70. function Reset-Buffer {
  71. param(
  72.    $Position = $Host.UI.RawUI.WindowPosition,
  73.    [int]$Height = $Host.UI.RawUI.WindowSize.Height,
  74.    [int]$Width = $Host.UI.RawUI.WindowSize.Width,
  75.    # Note: all edges are padded by 1 for the box edges, but we also pad each side by this ammount:
  76.    [int]$Padding = 1,
  77.    $ForegroundColor = $Host.UI.RawUI.ForegroundColor,
  78.    $BackgroundColor = $Host.UI.RawUI.BackgroundColor,
  79.    $BorderColor     = "Yellow",
  80.    [switch]$NoBorder,
  81.    [switch]$ShowInput,
  82.    [string]$Title = ""
  83. )
  84.  
  85. $global:BufferHeight          = $Height
  86. $global:BufferWidth           = $Width
  87. $global:BufferPadding         = $Padding
  88. $global:BufferForegroundColor = $ForegroundColor
  89. $global:BufferBackgroundColor = $BackgroundColor
  90. $global:BufferBorderColor     = $BorderColor    
  91.  
  92.    if($NoBorder) {
  93.       $global:BufferBoxSides = 0
  94.    } else {
  95.       $global:BufferBoxSides = 2
  96.    }
  97.    if($ShowInput) {
  98.       $global:BufferHeight -= 2
  99.    }
  100.  
  101.    $Host.UI.RawUI.SetBufferContents($Position,(New-BufferBox $BufferHeight $BufferWidth -Title:$Title -NoBorder:$NoBorder -ShowInput:$ShowInput -Background $BufferBackgroundColor -Border $BufferBorderColor))
  102.  
  103.    
  104.    $global:BufferPosition = $Position  
  105.    $global:BufferPosition.X += $global:BufferPadding + ($global:BufferBoxSides/2)
  106.    # this gets set to the BOTTOM line, because I assume text will flow in from the bottom.
  107.    $global:BufferPosition.Y += $global:BufferHeight - 2
  108.    # and this goes below that ...
  109.    $global:BufferPromptPosition = $BufferPosition
  110.    $global:BufferPromptPosition.Y += 2
  111.    $global:BufferPromptPosition.X += 2 - $global:BufferPadding # Prompt = "> "
  112. }
  113.  
  114. function New-BufferBox {
  115. param(
  116.    [int]$Height = $global:BufferHeight,
  117.    [int]$Width = $global:BufferWidth,
  118.    $Title = "",
  119.    [switch]$NoBorder,
  120.    [switch]$ShowInput,
  121.    $BackgroundColor = $global:BufferBackgroundColor,
  122.    $BorderColor = $global:BufferBorderColor
  123. )
  124.    $Width = $Width - $global:BufferBoxSides
  125.    
  126.    $LineTop =( $global:BoxChars.HorizontalDouble * 2) + $Title `
  127.             + $($global:BoxChars.HorizontalDouble * ($Width - ($Title.Length+2)))
  128.    
  129.    $LineField = ' ' * $Width
  130.    $LineBottom = $global:BoxChars.HorizontalDouble * $Width
  131.    $LineSeparator = $global:BoxChars.Horizontal * $Width
  132.    $LinePrompt = '> ' + ' ' * ($Width-2) # Prompt = "> "
  133.    
  134.    if(!$NoBorder) {
  135.       $LineField = $global:BoxChars.VerticalDouble + $LineField + $global:BoxChars.VerticalDouble
  136.       $LinePrompt = $global:BoxChars.VerticalDouble + $LinePrompt + $global:BoxChars.VerticalDouble
  137.       $LineBottom = $global:BoxChars.BottomLeftDouble + $LineBottom + $global:BoxChars.BottomRightDouble
  138.       $LineTop = $global:BoxChars.TopLeftDouble + $LineTop + $global:BoxChars.TopRightDouble
  139.       $LineSeparator = $global:BoxChars.VerticalDoubleRightSingle + $LineSeparator + $global:BoxChars.VerticalDoubleLeftSingle
  140.    }
  141.  
  142.    if($ShowInput) {
  143.       $box = &{$LineTop;1..($Height - 2) |% {$LineField};$LineSeparator;$LinePrompt;$LineBottom}
  144.    } else {
  145.       $box = &{$LineTop;1..($Height - 2) |% {$LineField};$LineBottom}
  146.    }
  147.    $boxBuffer = $Host.UI.RawUI.NewBufferCellArray($box,$BorderColor,$BackgroundColor)
  148.    return ,$boxBuffer
  149. }
  150.  
  151. function Move-Buffer {
  152. param(
  153.    $Position = $global:BufferPosition,
  154.    [int]$Left = $($global:BufferBoxSides/2),
  155.    [int]$Top = (2 - $global:BufferHeight),
  156.    [int]$Width = $global:BufferWidth - $global:BufferBoxSides,
  157.    [int]$Height = $global:BufferHeight,
  158.    [int]$Offset = -1
  159. )
  160.    $Position.X += $Left
  161.    $Position.Y += $Top
  162.    $Rect = New-Object $RectType $Position.X, $Position.Y, ($Position.X + $width), ($Position.Y + $height -1)
  163.    $Position.Y += $OffSet
  164.    $Host.UI.RawUI.ScrollBufferContents($Rect, $Position, $Rect, (new-object System.Management.Automation.Host.BufferCell(' ',$global:BufferForegroundColor,$global:BufferBackgroundColor,'complete')))
  165. }
  166.  
  167. function Out-Buffer {
  168. param(
  169.    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  170.    $Message,
  171.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  172.    $ForegroundColor = $global:BufferForegroundColor,
  173.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  174.    $BackgroundColor = $global:BufferBackgroundColor,
  175.    
  176.    [switch]$NoScroll,
  177.    
  178.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  179.    $Position = $global:BufferPosition,
  180.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  181.    [int]$Left = 0,
  182.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  183.    [int]$Top    = $(3 - $global:BufferHeight),  # Box Edge + New Lines
  184.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  185.    [int]$Width  = ($global:BufferWidth - $global:BufferBoxSides), # Box Edge
  186.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  187.    [int]$Height = ($global:BufferHeight - $global:BufferBoxSides), # Box Edge
  188.    [Parameter(ValueFromPipelineByPropertyName=$true)]
  189.    [int]$Offset = $( 0 - ($Message.Split("`n").Count))
  190. )
  191. process {
  192.    $lineCount = $Message.Split("`n").Count
  193.  
  194.    $Width = $Width - ($global:BufferPadding * 2)
  195.    if(!$NoScroll){ Move-Buffer $Position $Left $Top $Width $Height $Offset }
  196.    
  197.    $MessageBuffer = New-Object "System.Management.Automation.Host.BufferCell[,]" $lineCount, $width
  198.    
  199.    $index = 0
  200.    foreach( $line in $Message.Split("`n") ) {
  201.       $Buffer = $Host.UI.RawUI.NewBufferCellArray( @($line.Trim("`r").PadRight($Width)), $ForegroundColor, $BackgroundColor )
  202.       for($i = 0; $i -lt $width; $i++) {
  203.          $MessageBuffer[$index,$i] = $Buffer[0,$i]
  204.       }
  205.       $index++
  206.    }
  207.    
  208.    $Y = $global:BufferPosition.Y
  209.    $global:BufferPosition.Y -= $lineCount - 1
  210.    $Host.UI.RawUI.SetBufferContents($global:BufferPosition,$MessageBuffer)
  211.    $global:BufferPosition.Y = $Y
  212. }
  213. }
  214.  
  215. function Set-BufferInputLine {
  216. param([String]$Line = "")
  217.    $PromptText = $line.PadRight(($global:BufferWidth - $line.Length - 3)) # Prompt = "> "
  218.    
  219.    $CursorPosition = $BufferPromptPosition
  220.    $CursorPosition.X += $line.Length
  221.    
  222.    $Prompt = $Host.UI.RawUI.NewBufferCellArray( @($PromptText),$global:BufferForegroundColor, $global:BufferBackgroundColor)
  223.    $Host.UI.RawUI.SetBufferContents( $BufferPromptPosition, $prompt )
  224.    $Host.UI.RawUI.CursorPosition = $CursorPosition
  225. }
  226.  
  227. function Test-DisplayBox {
  228.    $Position = $Host.UI.RawUI.WindowPosition
  229.    $Position.X += 10
  230.    $Position.Y += 3
  231.  
  232.    Reset-Buffer $Position 20 66 3 -ForegroundColor 'Gray' -BackgroundColor 'Black' -BorderColor 'Green'
  233.    Out-Buffer 'Greetings!' 'Yellow' 'black'
  234.    sleep -m 600
  235.    Out-Buffer '' 'Gray' 'black'
  236.    sleep -m 600
  237.    Out-Buffer '- - - Thank you for running this simple demo script! - - -' 'Green' 'black'
  238.    sleep -m 600
  239.    Out-Buffer '' 'Gray' 'black'
  240.    sleep -m 600
  241.    Out-Buffer 'We are demonstrating how the scroll buffer works: you can
  242. add more than one line at a time, but you don''t really
  243. need to, since they add almost as fast one at a time.' 'white' 'black'
  244.    sleep -m 3000
  245.    Out-Buffer '' 'Gray' 'black'
  246.    Out-Buffer 'That is, as long as you don''t have any delay, you can just' 'white' 'black'
  247.    Out-Buffer 'write out as many lines as you like, one-at-a-time, like' 'white' 'black'
  248.    Out-Buffer 'this, and there is really no downside to doing that.' 'white' 'black'
  249.    sleep -m 3000
  250.    Out-Buffer '' 'Gray' 'black'
  251.    Out-Buffer 'Right? '.PadRight(58,"-") 'Red' 'black'  
  252.    Out-Buffer '' 'Gray' 'black'
  253.    sleep -m 600
  254.    Out-Buffer 'It''s clearly not as slick to just pop in multiple lines' 'white' 'black'
  255.    sleep -m 1200
  256.    Out-Buffer 'with absolutely no scroll delay, and it makes it a little ' 'white' 'black'
  257.    sleep -m 1200
  258.    Out-Buffer 'hard to tell what you have read already, but that''s ok.' 'white' 'black'
  259.    sleep -m 1200
  260.    Out-Buffer '' 'Gray' 'black'
  261.    sleep -m 600
  262.    Out-Buffer 'If you delay between paragraphs.' 'Red' 'black'  
  263.    sleep -m 600
  264.    Out-Buffer '' 'Gray' 'black'
  265.    sleep -m 600
  266.    Out-Buffer 'But: don''t scroll off-screen faster than I can read!' 'Yellow' 'black'  
  267.    sleep -m 600
  268.    Out-Buffer '' 'Gray' 'black'
  269. }
  270.  
  271. ## Test-BufferBox 3.1 - Now with Tab completion
  272. ####################################################################################################
  273. ## Imagine it's a chat window: you can type, but the whole time, the ongoing conversation in the
  274. ## chat room you have joined is going on in the background.
  275. ##
  276. ## NOTE: because this is a "chat" demo, we treat your input as text, and to execute script in-line
  277. ## you have to enclose it inside $() braces.
  278. ####################################################################################################
  279. function Test-BufferBox {
  280. param( $title = "PowerShell Interactive Buffer Demo" )
  281.  
  282. Reset-Buffer -ShowInput -Title $Title
  283.  
  284. ###################################################################################################
  285. ##### We only need this for the purposes of the demo...
  286. ##### In a real script you would, presumably, be getting stuff from somewhere else to display
  287.    $noise = $MyInvocation.MyCommand.Definition -split "`n"
  288.    $index = 0;
  289.    $random = New-Object "Random"
  290. [regex]$chunker = @'
  291. [^ \"']+|([\"'])[^\\1]*?\\1[^ \"']*|([\"'])[^\\1]*$| $
  292. '@
  293. ##### Printing a "How to Exit" message
  294. Out-Buffer "  " -Fore DarkGray -Back Cyan
  295. Out-Buffer "     This is just a demo. " -Fore Black -Back Cyan
  296. Out-Buffer "     We will stream in the source code of this script ... " -Fore Black -Back Cyan
  297. Out-Buffer "     And you can type at the bottom while it's running. " -Fore Black -Back Cyan
  298. Out-Buffer "     Imagine this as an n-way chat application like IRC, except that " -Fore Black -Back Cyan
  299. Out-Buffer "  FOR THIS PERFORMANCE ONLY: " -Fore Black -Back Cyan
  300. Out-Buffer "         The part of your chatting friends is played by my source code. " -Fore DarkGray -Back Cyan
  301. Out-Buffer "  " -Fore DarkGray -Back Cyan
  302. Out-Buffer "     Press ESC to exit, or enter 'exit' and hit Enter" -Fore Black -Back Cyan
  303. Out-Buffer "  " -Fore DarkGray -Back Cyan
  304. ##### Setting the prompt
  305. Set-BufferInputLine
  306. ##### And initializing our two variables ...
  307. $line=""
  308. $exit = $false
  309.  
  310. switch(Show-ConsoleMenu "Continue the demo","Stop the demo","Exit PowerShell" "What would you like to do now?" -UseBuffer) {
  311. 0 { <# do nothing, will continue #> }
  312. 1 { $exit = $true <#this script exits cleanly below#> }
  313. 2 { exit <# stop abruptly #> }
  314. }
  315.  
  316. while(!$exit){
  317.   while(!$exit -and $Host.UI.RawUI.KeyAvailable) {
  318.      $char = $Host.UI.RawUI.ReadKey("IncludeKeyUp,IncludeKeyDown,NoEcho");
  319.      # we don't want the key up events, but we do want to get rid of them
  320.      if($char.KeyDown) {
  321.      switch([int]$char.Character) {
  322.         13 { # Enter
  323.               if($line.Trim() -eq "exit") { $exit = $true; break; }
  324. ###################################################################################################
  325. ###################################################################################################
  326. ############# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING #############
  327. ############# This executes the user input!                                           #############
  328. ############# Don't use this on, say, content you got from a web page or chat room    #############
  329.            iex "Out-Buffer `"$line`" -Fore Red";                                     #############
  330. ###################################################################################################
  331. ###################################################################################################
  332.             $line = "";
  333.             Set-BufferInputLine
  334.             break;
  335.          }
  336.          27 { # Esc
  337.             $exit = $true; break;
  338.          }
  339.          9 { # Tab
  340.             if($line.Length -gt 0) {
  341.                [Array]$words = $chunker.Matches($line)
  342.                if($words.Count -ge 1) {
  343.                   Out-Buffer ($Words | Out-String) -Fore Black -Back White
  344.                   $lastWord = $words[$($words.Count-1)].Value
  345.                   $trim = $lastWord.TrimEnd("`r","`n")
  346.                   ## This may return more than one ... in which case subsequent tabs should iterate through them
  347.                   ## But for demo purposes, this is more than enough work...
  348.                   $replacement = TabExpansion -Line $line -LastWord ($lastWord.Trim() -replace '"')
  349.                   Out-Buffer ($replacement | Out-String) -Fore Black -Back White
  350.                   $line = $line.SubString(0, $line.Length - $lastWord.Length) + @($replacement)[0]
  351.                   Set-BufferInputLine $line
  352.                }
  353.             }        
  354.             break;
  355.          }
  356.             8 { # Backspace
  357.             if($line.Length -gt 0) {
  358.                $line = $line.SubString(0,$($line.Length-1))
  359.             }
  360.             # $pos = $Host.UI.RawUI.CursorPosition
  361.             Set-BufferInputLine $line
  362.             break;
  363.          }        
  364.          0 {
  365.             # Not actually a key
  366.             # Out-Buffer $($Char | Out-String)
  367.             break;
  368.          }
  369.          default {
  370.             $line += $char.Character
  371.             Set-BufferInputLine $line
  372.          }
  373.       }
  374.       }
  375.    }
  376.    # Simulate doing useful things 25% of the time
  377.    if($random.NextDouble() -gt 0.75) {
  378.       $noise[($index)..($index++)] | Out-Buffer
  379.       if($index -ge $noise.Length){$index = 0}
  380.    }
  381.    sleep -milli 100
  382. }
  383. $CursorPosition = $BufferPromptPosition
  384. $CursorPosition.Y += 2
  385. $CursorPosition.X = 0
  386. $Host.UI.RawUI.CursorPosition = $CursorPosition
  387. }

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me