Since I’m using the PowerShell Community Extensions, it makes sense for me to also use the prompt mechanism defined there.  In this article I will describe what PSCX provides over and above what PowerShell provides and how I have chosen to integrate it into my environment.

PSCX implements its prompt mechanism as a layer over the PowerShell prompt function.  The Eye Candy scripts in $Env:PscxHome\Profile implement a prompt function that references functions and variables that you can define to customize the prompt.  This function provides support for the following functionality:

  • Displaying the current location (e.g. C:\Users).
  • Displaying the next entry in the history stack.
  • Setting the foreground and background colors of the prompt.
  • Updating the title bar.

By default PSCX v1.1.1 includes four Eye Candy scripts - three that are personalized versions (EyeCandy.Jachym.ps1, EyeCandy.Keith.ps1, and EyeCandy.Vista.ps1) and one that is called by those three (EyeCandy.ps1).  EyeCandy.ps1 provides the base-level of supported described above.

My philosophy for using extensions like PSCX is that I would prefer to make as few changes as possible to those files so that it will be a relatively simple matter for me to upgrade my installation.  Fortunately PSCX allows me to use its infrastructure without requiring me to make changes.  Here is a rundown of the settings that you can make in your profile before calling the EyeCandy.ps1 script.

Prompt Functions

EyeCandy.ps1 defines three functions that implement the PSCX prompt infrastructure:

Prompt

This function is called by PowerShell whenever it needs to display a prompt.  It calls the other two functions to set the prompt and update the title bar of the PowerShell host window.

Write-Prompt($Id, $ForeColor = $PscxPromptForeColor, $BackColor = $PscxPromptBackColor)

This function writes the prompt text with partial support for displaying the next history stack entry number and setting the colors of the prompt.  It references the $PscxPromptPreferences variable to get the actual text of the prompt.

If the $PscxPromptPreferences variable is not defined as a script object, the prompt will be set like so:

$prompt = "PS $(get-location)>"

If the $PscxPromptPreference variable IS defined as a script object, the prompt will be set like so:

$prompt = "$(& $PscxPromptPreference $Id)"

As you can see, the PscxPromptPreference script can be written to take the history stack ID.

Update-HostTitle

This function updates the title bar of the PowerShell host window.  It references the $PscxHostTitlePreference variable to get the actual text to display on the title bar.

The EyeCandy.Vista.ps1 script defines the $PscxHostTitlePreference this way:

$Global:PscxHostTitlePreference = {
    "$(if ($IsAdmin){'Admin:'}) $(Get-Location) - Windows PowerShell"
}

This displays the current location and an indication of whether it is an elevated PowerShell window in the title bar.  Note that the $IsAdmin variable referenced by that script block is defined in the PSCX profile.

Colors

These variables allow you to specify the color of the PowerShell window and text and must be set prior to your profile script invoking the Eye Candy script.

  • $PscxForeColor - Specifies the foreground color for text in the PowerShell window.
  • $PscxBackColor - Specifies the background color for the PowerShell window.

These variables allow you to specify the color of prompt text.

  • $PscxPromptForeColor - Specifies the foreground color for prompt text.
  • $PscxPromptBackColor - Specifies the background color for prompt text.

Default values for all colors are taken from $Host.UI.RawUI.BackgroundColor.  If only window colors are specified (the first set), they will also be used for prompt colors.

>Here is an example from EyeCandy.Jachym.ps1:

$Global:PscxPromptBackColor = $null
if($IsAdmin) {
    $Global:PscxForeColor = 'White'
    $Global:PscxBackColor = 'DarkRed'
    $Global:PscxPromptForeColor = 'Gray'
    $Global:PscxRepeatPromptColor = 'DarkCyan'
} else {
    $Global:PscxForeColor = 'White''
    $Global:PscxBackColor = 'Black'
    $Global:PscxPromptForeColor = 'Yellow'
    $Global:PscxRepeatPromptColor = 'DarkCyan'
}

(Note that the $PscxRepeatPromptColor variable above is not referenced anywhere, so this must be left over from a previous version.)

Message of the Day

This variable specifies a script that will display a Message of the Day whenever the Eye Candy script is invoked (aka when your PowerShell window is opened).

  • $PscxMotDayPreference

Here is the value of this variable as defined in EyeCandy.Vista.ps1:

$Global:PscxMotDayPreference = {
    if ($DebugPreference -eq 'SilentlyContinue') {
        Clear-Host
    }

    $MachineArchitecture = $(if([IntPtr]::Size -eq 8 ) { "64-bit" } else { "32-bit" })
    $PSVersionString     = (Get-Command "$PSHome\PowerShell.exe").FileVersionInfo.ProductVersion

    "Windows PowerShell $($host.Version), ProductVersion $PSVersionString ($MachineArchitecture)"
    "PowerShell Community Extensions $PscxVersion"
    ""

    $user =    "Logged in on $([DateTime]::Now.ToString((get-culture))) as $($NTIdentity.Name)"

    if ($IsAdmin) {
        $user += ' (Elevated).'
    }
    else {
        $user += '.'
    }

    $user
}

This script will display a banner indicating the machine architecture (32-bti vs 64-bit), which version of PSCX has been installed, the current user, and whether the shell is running in admin mode.

My Prompt Implementation

I’ve implemented infrastructure for use by everyone in my company which will give them additional features over and above what PSCX provides without having to implement it all themselves.  My infrastructure provides the following additional features:

  • Which character to display before and after the prompt ($PromptPrefix, $PromptPostfix, defaults to ‘[' and ']‘).
  • Whether to display history information or not.
  • An indication of the current depth of the location stack (incremented using Push-Location and decremented using Pop-Location).
  • An indication of the current depth of the nested prompt level.
  • The ability to automatically display the current location on the previous line if it exceeds a specified length.
  • The ability to display a shortened version of the current location if it exceeds a specified length.

Here are my prompt definitions:

# String displayed before and after the prompt.
# Prefix not displayed if prompt is split.
$Global:PromptPrefix = '['
$Global:PromptPostfix = ']'

# Indicates whether the history count should be displayed in the prompt.
$Global:DisplayHistoryCountInPrompt = $False

# If the length of the current location is greater than this value the path
# will be displayed on one line and the rest of the prompt will be displayed
# on the next line.  A value of 0 disables this functionality.
$Global:SplitPromptMaxLength = 0

# If the length of the current location is greater than this value the path
# will be truncated to only show this many characters preceded by an elipsis (...).
# A value of 0 disables this functionality.
$Global:PromptLocationMaxLength = 0

# Specifies a scirpt that will be executed to set the title on the title bar.
## $Global:PscxHostTitlePreference = { }

# Specifies a script that will be executed to display the prompt.
$Global:PscxPromptPreference =
{
    param($HistoryId)

    # Construct the strings that are the same for both types of path displays.
    $_nestedPromptLevelString = New-Object string ([char] 0xB7), $NestedPromptLevel
    $_locationStackDepthString = New-Object string ([char] '+'), (Get-Location -Stack).Count
    if ($DisplayHistoryCountInPrompt)
    {
        $_historyCountString = "#$HistoryId-"
    }
    if ($SplitPromptMaxLength -and $PWD.Path.Length -gt $SplitPromptMaxLength)
    {
        $_prompt =
            $PWD.Path +
            [System.Environment]::NewLine +
            $_nestedPromptLevelString +
            $_locationStackDepthString +
            $_historyCountString +
            $PromptPostfix
    }
    else
    {
        $_location = $PWD.Path
        if ($PromptLocationMaxLength)
        {
            $_location = Get-StringTail $PWD.Path $PromptLocationMaxLength
        }
        $_prompt =
            $_nestedPromptLevelString +
            $_locationStackDepthString +
            $PromptPrefix + $_historyCountString + $_location + $PromptPostfix
    }
    $_prompt
}

The Get-StringTail function is defined like so (taken from John D. Cook’s prompt function):

function Global:Get-StringTail([string] $Value, [int] $MaxLength)
{
    if (!$MaxLength)
    {
        $MaxLength = 30
    }
    if ($Value.Length -gt $MaxLength)
    {
        # The value will begin with "..." and will contain as many of the
        # characters as will fit, reading from the end of the string.
        $Value = '...' + $Value.SubString($Value.Length - $MaxLength + 4)
    }
    $Value
}

Conclusions/Comments for Improving PSCX

One issue I see is that the default prompt executes the Get-Location command when it could simply use the $PWD variable, which seems to me should be much lighter weight.

Secondly, I ran into a problem with the PSCX scripts where they expect to be called from the profile script.  If they are ever called form another place (e.g. on the PowerShell command line or when invoking powershell.exe with the -Command option), you will not get the results you expect.  Specifically all functions, variables, and aliases that are defined must be defined in the Global scope:

function Global:Write-Prompt(...)
$Global:PscxPromptPreference = ...
Set-Alias -Scope "Global" ...

A third problem, which may be due simply to my desire to minimize changes to default PSCX files, is that the default profile sets the default Eye Candy script variable ($PscxEyeCandyScriptPreference) and executes it.  I’ll address the main PSCX profile script in a separate article.

Aside from these two issues, however, the infrastructure provided by PSCX is very flexible, allowing PSCX to be integrated into a shared PowerShell environment so that all users can customize any aspect of their PowerShell environment without requiring all parts to be customized.