Tag Archives: console

PowerShell Console Graphing Revised

Many of you have been having fun with my PowerShell Console Graphing tool I posted the other day. But I felt the need to make one more major tweak. I wanted to have the option for conditional formatting. That is, display graphed entries with high values in one color, medium in another and low in yet another.

The default behavior is still to use a single color. But using ParameterSets I added some new parameters, -HighColor, -MediumColor and -LowColor. They are all mandatory so if you use one parameter you have to define them all.


[cmdletbinding(DefaultParameterSetName="Single")]
Param (
[parameter(Position=0,Mandatory=$True,HelpMessage="Enter a property name to graph")]
[ValidateNotNullorEmpty()]
[string]$Property,
[parameter(Position=1,ValueFromPipeline=$True)]
[object]$Inputobject,
[string]$CaptionProperty="Name",
[string]$Title="$Property Report - $(Get-Date)",
[Parameter(ParameterSetName="Single")]
[ValidateNotNullorEmpty()]
[System.ConsoleColor]$DefaultColor="Green",
[Parameter(ParameterSetName="Conditional",Mandatory=$True)]
[ValidateNotNullorEmpty()]
[System.ConsoleColor]$HighColor,
[Parameter(ParameterSetName="Conditional",Mandatory=$True)]
[ValidateNotNullorEmpty()]
[System.ConsoleColor]$MediumColor,
[Parameter(ParameterSetName="Conditional",Mandatory=$True)]
[ValidateNotNullorEmpty()]
[System.ConsoleColor]$LowColor,
[alias("cls")]
[switch]$ClearScreen
)

I also moved the Property parameter and made it positional which should make it easier to use. The conditional coloring works basically by taking the largest possible graph value, which is based on available screen width and dividing it into thirds. The top third is considered high, second third is medium and last third is low.


...
#get remaining available window width, dividing by 100 to get a
#proportional width. Subtract 4 to add a little margin.
$available = ($width-$longest-4)/100
Write-Verbose "Available value is $available"

#calculate high, medium and low ranges based on available
$HighValue = ($available*100) * 0.6666
$MediumValue = ($available*100) * 0.3333
#low values will be 1 to $MediumValue
Write-Verbose "High value will be $HighValue"
Write-Verbose "Medium value will be $MediumValue"
...

When it comes time to graph, I check which parameter set we’re using and set the graph color accordingly.


...
if ($pscmdlet.ParameterSetName -eq "Single") {
$GraphColor = $DefaultColor
}
else {
#using conditional coloring based on value of $graph
if ($Graph -ge $HighValue) {
$GraphColor = $HighColor
}
elseif ($graph -ge $MediumValue) {
$GraphColor = $MediumColor
}
else {
$GraphColor = $LowColor
}
}
Write-Host ($g*$graph) -ForegroundColor $GraphColor
...

But now I can run a command like this:


PS Scripts:\> ps | where {$_.cpu} | out-consolegraph CPU -high Red -medium magenta -low yellow -cls

out-consolegraph-3

What do you think? Download Out-ConsoleGraph-v2.

Graphing with the PowerShell Console

I’ve written before about using the PowerShell console as a graphing tool, primarily using Write-Host. Most of what I’ve published before were really proof of concept. I decided to try and come up with a more formal and re-usable tool that could create a horizontal bar graph based on a numeric property from piped objects. For example, I wanted to get all processes and display a graph of the WorkingSet for each object. My result is an advanced function that should work in v2 or v3 called Out-ConsoleGraph.

Here’s the code, minus, the comment-based help. I’ve commented the quote quite a bit so I won’t spend a lot of time explaining it in detail.


Function Out-ConsoleGraph {

#comment based help goes here

[cmdletbinding()]
Param (
[parameter(Position=0,ValueFromPipeline=$True)]
[object]$Inputobject,
[parameter(Mandatory=$True,HelpMessage="Enter a property name to graph")]
[ValidateNotNullorEmpty()]
[string]$Property,
[string]$CaptionProperty="Name",
[string]$Title="$Property Report - $(Get-Date)",
[ValidateNotNullorEmpty()]
[System.ConsoleColor]$GraphColor="Green",
[alias("cls")]
[switch]$ClearScreen
)

Begin {
Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"
#get the current window width so that our lines will be proportional
$Width = $Host.UI.RawUI.BufferSize.Width
Write-Verbose "Width = $Width"

#initialize an array to hold data
$data=@()
} #begin

Process {
#get the data
$data += $Inputobject

} #end process

End {
#get largest property value
Write-Verbose "Getting largest value for $property"
Try {
$largest = $data | sort $property | Select -ExpandProperty $property -last 1 -ErrorAction Stop
Write-Verbose $largest
}
Catch {
Write-Warning "Failed to find property $property"
Return
}
If ($largest) {
#get length of longest object property used for the caption so we can pad
#This must be a string so we can get the length
Write-Verbose "Getting longest value for $CaptionProperty"
$sample = $data |Sort @{Expression={($_.$CaptionProperty -as [string]).Length}} |
Select -last 1
Write-Verbose ($sample | out-string)
[int]$longest = ($sample.$CaptionProperty).ToString().length
Write-Verbose "Longest caption is $longest"

#get remaining available window width, dividing by 100 to get a
#proportional width. Subtract 4 to add a little margin.
$available = ($width-$longest-4)/100
Write-Verbose "Available value is $available"

if ($ClearScreen) {
Clear-Host
}
Write-Host "$Title`n"
foreach ($obj in $data) {
#define the caption
[string]$caption = $obj.$captionProperty

<#
calculate the current property as a percentage of the largest
property in the set. Then multiply by the remaining window width
#>
if ($obj.$property -eq 0) {
#if property is actually 0 then don't display anything for the graph
[int]$graph=0
}
else {
$graph = (($obj.$property)/$largest)*100*$available
}
if ($graph -ge 2) {
[string]$g=[char]9608
}
elseif ($graph -gt 0 -AND $graph -le 1) {
#if graph value is >0 and <1 then use a short graph character
[string]$g=[char]9612
#adjust the value so something will be displayed
$graph=1
}

Write-Verbose "Graph value is $graph"
Write-Verbose "Property value is $($obj.$property)"
Write-Host $caption.PadRight($longest) -NoNewline
#add some padding between the caption and the graph
Write-Host " " -NoNewline
Write-Host ($g*$graph) -ForegroundColor $GraphColor
} #foreach
#add a blank line
Write-Host `n
} #if $largest
Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
} #end

} #end Out-ConsoleGraph

The function requires that PowerShell be running in STA mode, which shouldn't really be an issue. The intent is that you will be piping objects to the function. You need to specify a property that you want to graph and an object property to use as the label or caption for each object. The default caption is the Name property which seems pretty common. The property you are graphing must have a numeric value. The function's premise is to get the window width, then write the caption and a graph figure using the remaining available width. The function has a bit of code to calculate the longest caption value so that everything lines up and then determines how much space remains for graphing.

The graph is really more of a proportional representation as opposed to actual value. In short, I find the largest property value which essentially becomes the 100% mark. All other values are calculated as percentages and graphed accordingly. This might be easier to understand if you see it in action.


PS Scripts:\> get-process | where {$_.company -notmatch "microsoft"} | out-consolegraph -property WorkingSet -cls

This is getting all non-Microsoft processes and creating a graph of the WorkingSet property.

out-consolegraph-1

The graph title and color are customizable via parameters. This should work for any type of object as long as you can have a numeric property.


Get-ChildItem C:\Scripts -Directory | foreach {
$data = Get-ChildItem $_.FullName -Recurse -File | Measure-Object -Property Length -sum
$_ | Select Name,@{Name="Size";Expression={$data.sum}}
} | Out-ConsoleGraph -Property Size -Title "Scripts Folder Report" -GraphColor Cyan

out-consolegraph-2

This command will work in the PowerShell ISE but I think it works better in the PowerShell console. Remember, this command is NOT writing to the pipeline so all you can do is view the output.

Download Out-ConsoleGraph and let me know what you think.

Friday Fun: Another PowerShell Console Graph

Late last year I posted a demo script to create a horizontal bar graph in the PowerShell console. I liked it and many of you did as well. But I also wanted to be able to create a vertical bar graph, ie one with columns. This is much trickier since you have to tell PowerShell exactly where to “paint” the graph.

I’ve posted other articles on using the coordinates in the host and that is what I ended up doing for today’s Friday Fun. This demo script only works in the PowerShell console. It will run in the ISE but you won’t get the desired result. Let me post the code and then I’ll go through a few things.


Param([string]$computername=$env:computername)

Clear-Host

#get the data
$drives=Get-WmiObject -Class Win32_LogicalDisk -Filter "drivetype=3" -computername $computername

#define a set of colors for the graphs
$colors=@("Yellow","Magenta","Green","Cyan","Red")

#set cursor position
$Coordinate = New-Object System.Management.Automation.Host.Coordinates
$Coordinate.X= 10
$Coordinate.Y= [int]($host.ui.rawui.WindowSize.Height -5)

#save starting coordinates
$startY=$Coordinate.Y
$startX=$Coordinate.X

#counter for colors
$c=0

#adjust Y so we can write the caption
$Coordinate.Y+=1

foreach ($drive in $drives) {
#set the color to the first color in the array of colors
$color=$colors[$c]
$legend=$drive.DeviceID
#calculate used space value
$used=$Drive.Size - $Drive.FreeSpace
[int]$usedValue=($used/($drive.size))*10
#adjust for values less than 0 so something gets graphed
if ($usedValue -le 0) {
[int]$usedValue=($used/($drive.size))*50
}

#format usage as a percentage
$usedPer="{0:p2}" -f ($used/($drive.size))
#set the cursor to the new coordinates
$host.ui.rawui.CursorPosition=$Coordinate
#write the caption
write-host $legend -nonew
#move the Y coordinate up to start the graph
$coordinate.Y-=1

for ($i=$usedValue;$i -gt 0;$i--) {
$host.ui.rawui.CursorPosition=$Coordinate
#draw the color space for the graph
write-host " " -BackgroundColor $color -nonewline
#move Y up 1
$coordinate.y--
#repeat until we reach the $usedValue
}
#set new coordinate
$host.ui.rawui.CursorPosition=$Coordinate
#write the usage percentage at the top of the bar
write-host $usedPer -nonewline

#reset Y to where we started + 1
$Coordinate.Y=($startY+1)
#move X to the right
$coordinate.x+=8
#reset coordinates
$host.ui.rawui.CursorPosition=$Coordinate
#increment the color counter
$c++

#repeat for the next drive

} #foreach

#reset coordinates so we can write a legend
$coordinate.Y=$StartY+2
$coordinate.X=$startX
$host.ui.rawui.CursorPosition=$Coordinate
write-host ("Drive Usage for {0}" -f $drives[0].__SERVER)

#move cursor to bottom of the screen and write a blank line
$Coordinate.X=1
$coordinate.Y=[int]($host.ui.rawui.WindowSize.Height-2)
$host.ui.rawui.CursorPosition=$Coordinate
write-host ""

#your PowerShell prompt will now be displayed

This script gets drive usage and creates a vertical bar chart displaying disk utilization. Everyone loves a good drive space report and they always make good demos.

The script creates a System.Management.Automation.Host.Coordinates object, setting the value of X and Y that should be starting in the bottom left corner of the console.


#set cursor position
$Coordinate = New-Object System.Management.Automation.Host.Coordinates
$Coordinate.X= 10
$Coordinate.Y= [int]($host.ui.rawui.WindowSize.Height -5)
#save starting coordinates
$startY=$Coordinate.Y
$startX=$Coordinate.X

I’m also saving these values so I can reset. The script will have to “move” the cursor around the screen to draw the graphs. Once I calculate the values for each drive, I write a blank line using Write-Host with -Backgroundcolor to get the desired graphing effect.


for ($i=$usedValue;$i -gt 0;$i--) {
$host.ui.rawui.CursorPosition=$Coordinate
#draw the color space for the graph
write-host " " -BackgroundColor $color -nonewline
#move Y up 1
$coordinate.y--
#repeat until we reach the $usedValue
}

Notice after each write I move the Y point “up”, until I reach the limit of the current value. I set values as a percentage scaled to 10 so the graph doesn’t end up outside of the buffer. I also made an adjustment for low values that wouldn’t normally trigger a graph so that I get something written to the screen.


#calculate used space value
$used=$Drive.Size - $Drive.FreeSpace
[int]$usedValue=($used/($drive.size))*10
#adjust for values less than 0 so something gets graphed
if ($usedValue -le 0) {
[int]$usedValue=($used/($drive.size))*50
}

After drawing the graph I move the cursor position back to the beginning and write a legend.


#reset coordinates so we can write a legend
$coordinate.Y=$StartY+2
$coordinate.X=$startX
$host.ui.rawui.CursorPosition=$Coordinate
write-host ("Drive Usage for {0}" -f $drives[0].__SERVER)

Here’s the end result.

This script is really just a proof of concept. I haven’t created any functions to simply any of this or make it easy to use with other values. This is also a bit advanced so if you look at this and it makes your head hurt, don’t worry about it. You would only use something like this in special cases. Still, I’d like to know what you think, how it works for you and if you extend it to a more re-usable form.

Download demo-bargraph. The script will default to the local computer, but you can run specify any computer you want.


PS C:\Scripts\> .\demo-bargraph.ps1 Mycomputer

Enjoy and have fun.

Maximizing the PowerShell Console Title Bar

A few days ago Boe Prox posted some very nifty PowerShell modules for using the title bar as a ticker for RSS feeds like the weather. I thought this was an awesome idea and an easy way to take advantage of what would otherwise be unused screen space. I was especially intrigued with his use of timer objects and event subscriptions to manage the updating.

Naturally I decided to run with this. My main goal was to take Boe’s fundamental idea and turn it into something more re-usable or extensible. My result is a module called ConsoleTitle.


PS C:\> get-command -Module ConsoleTitle | Select Name

Name
----
Get-Inspiration
Get-SystemStat
Get-Timer
New-Timer
Remove-Timer
Set-ConsoleTitle
Set-TimerInterval
Start-TitleTimer

The overall premise is pretty simple, define a global variable $PSConsoleTitle and use a timer to periodically update the console title bar with this value. During the refresh interval you can run whatever code you like, however you like, to provide a new value to the variable. In the module I’ve included two sample commands, Get-SystemStat and Get-Inspiration. The former uses WMI to gather system information from the local computer.

The other command defines an array of slogans, sayings and suggestions and randomly selects one to use as the title bar text.

The module includes a few commands for working with timer objects. You can use New-Timer in your own scripts. Here’s the function.


Function New-Timer {

<#
.Synopsis
Create an event timer object
.Description
Create an event timer object, primarily to be used by the ConsoleTitle module.
Each timer job will automatically be added to the global variable, $ConsoleTitleEvents
unless you use the -NoAdd parameter. This variable is used by Remove-Timer to clear
console title related timers.

This function is called from within other module functions but you can use it to
create non-module timers.

.Parameter Identifier
A source identifier for your timer
.Parameter Refresh
The timer interval in Seconds. The default is 300 (5 minutes). Minimum
value is 5 seconds.
.Parameter Action
The scriptblock to execute when the timer runs down.
.Parameter NoAdd
Don't add the timer object to the $ConsoleTitleEvents global variable.
#>

Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a source identifier for your timer")]
[ValidateNotNullorEmpty()]
[string]$Identifier,
[Parameter(Position=1)]
[validatescript({$_ -ge 5})]
[int]$Refresh=300,
[Parameter(Position=2,Mandatory=$True,HelpMessage="Enter an action scriptblock")]
[scriptblock]$Action,
[switch]$NoAdd
)

Write-Verbose ("Creating a timer called {0} to refresh every {1} seconds." -f $Identifier,$Refresh)

#create a timer object
$timer = new-object timers.timer
#timer interval is in milliseconds
$timer.Interval = $Refresh*1000
$timer.Enabled=$True

#create the event subscription and add to the global variable
$evt=Register-ObjectEvent -InputObject $timer -EventName elapsed โ€“SourceIdentifier $Identifier -Action $Action

if (-Not $NoAdd) {
#add the event to a global variable to track all events
$global:ConsoleTitleEvents+=$evt
}
#start the timer
$timer.Start()

} #Function

And here’s how you might use it.


Function Get-Inspiration {

Param(
[Parameter(Position=0)]
[ValidateScript({$_ -ge 5})]
[int]$Refresh=600
)

#Define an array of pithy sayings, slogans and quotes

#we'll create as a globally scoped variable so you can add to it anytime you want from PowerShell
$global:slogans=@(
"PowerShell Rocks!",
"Energize!!",
"To Shell and Back",
"I am the Shell",
"PowerShell to the People",
"Powered by PS",
"PowerShell Rulez!",
"PowerShell Fanboy",
"I am the walrus",
"Those who forget to script are doomed to repeat their work.",
"Have you backed up files lately?",
"Is your resume up to date?",
"Is it Beer O'Clock yet?",
"With great power comes great responsibility",
"I came, I saw, I scripted.",
"$env:username, Open the pod bay doors."
)

$sb={ $global:PSConsoleTitle=$global:slogans | get-random }
#invoke the scriptblock
Invoke-Command $sb

New-Timer -identifier "SloganUpdate" -action $sb -refresh $refresh

#start the update timer if not already running
if (-Not (Get-EventSubscriber -SourceIdentifier "TitleTimer" -ea "SilentlyContinue")) {
Start-TitleTimer -refresh $refresh
}

} #function

Think of the module as a framework or SDK for building your own solutions. The module also includes an about topic. I hope you’ll download ConsoleTitleand let me know what you think.

Friday Fun – A PowerShell Console Menu

When working in PowerShell, and especially when scripting, you might want to give the user a choice of actions. For example, you might develop a configuration script that another admin or technician will run. Perhaps one of the steps is to configure networking depending on their location so you want to give the person running the script a menu of choices. Here’s one way you might accomplish this, without resorting to graphical tools or WinForms. Continue reading

Friday Fun Drive Usage Console Graph

I think you’ll like this. Normally, I prefer my PowerShell commands to write objects to the pipeline. But there’s nothing wrong with sending output directly to the console, as long as you know that the output is intended only for the screen. What I find frustrating is the use of Write-Host when really, pipelined objects would be better. But for today, I’m going to revel in the beauty of the console and create a colorized drive utilization graph. Continue reading

PowerShell Deep Dive Treasure

Without a doubt the PowerShell Deep Dive conference was one of the best meetings I’ve ever attended and I wanted to share one tidbit I came away with that I find immensely useful and never knew. During one of Bruce Payette’s talks he tossed out, practically as an aside, a reference to searching command line history with the # character. What!!???

I never heard of this and it is very slick. From the command prompt (either the console or ISE) enter the # character followed by whatever part of a previous command you remember. It does not have to be the start of the command, it can be any part of it. Then press TAB. PowerShell will search your history buffer and return the first match. If there are multiple matches you can keep pressing TAB to cycle through them. Awesome! The beauty for me is that it just brings the command back to the prompt so you can edit it.

Thank you Bruce!