Been trying to find time to get back to my WinForms using Classes series but work and home life have me slammed. So I thought I’d kick of a series of disjointed but hopefully fun and interesting snippets. Lots of these will just be my own interpretations of things that probably exist out there already these are just my own mainly QOL things I’ve put together.
Before you can get the right answer you must FIRST prompt the right question.
Sure I might have swapped Asked for Prompt but it’s MY poorly conceived segue so I’m sticking to it. Anyways the Prompt, good old PS: C:\>
nice, unassuming, elegant, simp…SWEET MERCIFUL BALLMER what is THAT?!?!
PS C:\Users\Administrator\AppData\Roaming\Mozilla\Firefox\Profiles\ac8x904e.default-release\storage\default\moz-extension+++808770c0-f3e6-4021-844a-4d95c4217ba2^userContextId=4294967295\idb\3647222921wleabcEoxlt-eengsairo.files>
Right there! Pretty sure that (or something similar to that) was where something snapped in my brain. Too many times having to jump into a powershell shell from some ungodly long unc path or local path riddled with guids and version numbers leaving tiny bits of space after the >
to type out my commands. Well NO MORE!!
So let’s talk about what we’re going do to fix this. Might as well get the disclaimer out of the way. There are a decent amount of people’s solutions for changing up the powershell prompt. Ranging from the insanely detailed to the oddly specific. This one is not meant to be either of those it’s a practical solution to a recurring problem I run into. Along the way we can learn a few things.
Hopefully our intention is clear from that contrived path example though it DOES accurately represent some of the INSANE UNC, or Registry paths you can run across. But if its not we want to maximize how much we have left to type out our commands after our prompt without sacrificing the information we want to know. We’ve got 2 basic means to that end Condensing, and Wrapping, at the end we’ll actually combine both of them.
First Condensing:
Function prompt { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # When we glue our path back together this will be what we use $Joiner = "\" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' # Set How Many Child Path parts we want $ChildCount = 2 # Set how many Parent Path parts we want $ParentCount = 2 # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{PSC} $($PathParts -join $Joiner)$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{PSC} $( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$End" } }
First we initialize some variables for later (Check if we’re debugging, setup our joiner,our condensing limiters, and our >’s), and chop up our path
# Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # When we glue our path back together this will be what we use $Joiner = "\" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' # Set How Many Child Path parts we want $ChildCount = 2 # Set how many Parent Path parts we want $ParentCount = 2
Next up we have to check if the total of our ParentCount and ChildCount limiters are greater than the total count of our path parts. If so we just join our path parts with our Joiner and return our newly formed prompt
# If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{PSC} $($PathParts -join $Joiner)$End" }
Or if we have MORE path parts in which case we use the array indexers to grab a ParentCount’s worth of path parts from the front, a ChildCount’s worth from the back, join them all with the delimiter and return our alternative Prompt.
# Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{PSC} $( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$End" }
That’s it. Now our prompt from before is a more manageable
{PSC} C:\Users..\idb\3647222921wleabcEoxlt-eengsairo.files>
Second Wrapping:
Our other option is wrapping. This one is a lot simpler than Condensing we’re basically just swapping our path delimiter
Function prompt { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # When we glue our path back together this will be what we use $Joiner = "`r`n" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' "$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End" }
We start out almost exactly like Condensing setting up our starting variables the only difference is our Joiner is now
"`r`n"
# Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # When we glue our path back together this will be what we use $Joiner = "`r`n" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\'
Then its a single line to put it all back together. We do add a final Joiner at the end so our >’s end up alone on a line
"$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End"
So now our same prompt looks like this
{PSML} C: Users Administrator AppData Roaming Mozilla Firefox Profiles ac8x904e.default-release storage default moz-extension+++808770c0-f3e6-4021-844a-4d95c4217ba2^userContextId=4294967295 idb 3647222921wleabcEoxlt-eengsairo.files >
Yeah its a LOT of lines BUT the point was to give us the MAXIMUM line space to type out our commands and JUST > is pretty darn close.
But what if we want the best of both worlds? well
Third Part By Our Powers Combined…
This one will look uncannily familiar to Condensed. As a matter of fact there is only 3 things different. More on that later.
Function prompt { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # When we glue our path back together this will be what we use $Joiner = "`r`n" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' # Set How Many Child Path parts we want $ChildCount = 2 # Set how many Parent Path parts we want $ParentCount = 2 # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{PSMLC}`r`n$($PathParts -join $Joiner)`r`n$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{PSMLC}`r`n$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)`r`n$End" } }
We start of pretty much the same as condensed just swapping out our Joiner’s "\"
for
"`r`n"
# Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # When we glue our path back together this will be what we use $Joiner = "`r`n" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' # Set How Many Child Path parts we want $ChildCount = 2 # Set how many Parent Path parts we want $ParentCount = 2
Next up is our same check if we’re condensing more than we started with
# If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{PSMLC}`r`n$($PathParts -join $Joiner)`r`n$End" }
Followed by our same alternative when we have more to condense
# Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{PSMLC}`r`n$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)`r`n$End" }
Now our same prompt looks like this
{PSMLC} C: Users .. idb 3647222921wleabcEoxlt-eengsairo.files >
A good balance between the two. We’ve still got a little cleanup work to do though. Time to Reset back to the original (or as close as we can get) Prompt
Fourth Part the Great Reset
Function prompt { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) # Set us back to as close to the original function "$($Start)PS $($executionContext.SessionState.Path.CurrentLocation)$End" }
Fairly close to what the prompt function looks like out of the box. As a small point of trivia it seems the prompt function is actually regenerated from the shell itself when you are debugging or not debugging something unless you have overridden the function with your own code so you HAVE to include your own check for Debugging even when you reset it back to default.
There we go we’ve taken our Prompt from an unwieldy monster into several tame little things we can work better with but you might be thinking “what if I want 3 Parents and only 1 Child paths?” or “What if I want to switch between these ‘Styles’ of prompts?” and you would do well to think like that. So for our final trick we’re going to setup a Module with a single Cmdlet Set-Prompt and its going to let us change our prompt on the fly with a single line.
Fifth Part I love It When a Prompt Comes Together
As always we’ll start with the WHOLE shebang then talk breakdown
#region Helpers Enum PromptStyle { Condensed MultiLine MultiLineCondensed } # Helper Class for building our prompt functions Class PromptParts { #region The Hidden Stuff # holds our single instance of our class hidden static [PromptParts] $Instance # Gets or creates our single instance of our class hidden static [PromptParts] Get() { # Do we have an instance? No? Well Create One If ($null -eq [PromptParts]::Instance) { [PromptParts]::Instance = [PromptParts]::new() } # Return it return [PromptParts]::Instance } # Create our 1st ScriptBlock that all prompts share hidden [ScriptBlock] $AllStartRaw = { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) } # Create our 2nd ScriptBlock that ALL Styles share hidden [ScriptBlock] $AllStylesRaw = { # When we glue our path back together this will be what we use $Joiner = "$JoinerString" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' } # Create our 3rd ScriptBlock for both Condensed Styles hidden [ScriptBlock] $CondensedRaw = { # Set How Many Child Path parts we want $ChildCount = $ChildCountValue # Set how many Parent Path parts we want $ParentCount = $ParentCountValue # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{$PromptType}$Token$($PathParts -join $Joiner)$EndToken$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{$PromptType}$Token$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$EndToken$End" } } # Create our 3rd ScriptBlock for MultiLine hidden [ScriptBlock] $MultiLineRaw = { "$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End" } # Create our 2nd ScriptBlock for Reset hidden [ScriptBlock] $ResetRaw = { # Set us back to as close to the original function "$($Start)PS $($executionContext.SessionState.Path.CurrentLocation)$End" } #endregion #region The Visible Stuff # Glues together all the ScriptBlocks to make Reset static [string] $Reset = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().ResetRaw)" # Glues together all the ScriptBlocks to make both Styles of Condensed static [string] $Condensed = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().CondensedRaw)" # Glues together all the ScriptBlocks to make Multiline static [string] $MultiLine = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().MultiLineRaw)" #endregion } #endregion #region Cmdlets Function Set-Prompt { [CmdletBinding()] Param( [Parameter(Mandatory=$true,HelpMessage="Prompt Style", ParameterSetName="Style")] [PromptStyle] $Style, [Parameter(Mandatory=$true,HelpMessage="Reset To default prompt", ParameterSetName="Reset")] [Switch] $Reset ) DynamicParam { # If a user selects either of the Condensed options we want to prompt how much we want to condense $DynamicParameters = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() if ($Style -in @([PromptStyle]::Condensed,[PromptStyle]::MultiLineCondensed )) { $Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new() $ParameterAttribute.ParameterSetName = "Style" $Attributes.Add($ParameterAttribute) $Attributes.Add([System.Management.Automation.ValidateRangeAttribute]::new(1,10)) # How Many Parents do We show $ParentCount = [System.Management.Automation.RuntimeDefinedParameter]::new("ParentCount",[int],$Attributes) $ParentCount.Value = 1 $DynamicParameters.Add("ParentCount",$ParentCount) # How Many Children do we show $ChildCount = [System.Management.Automation.RuntimeDefinedParameter]::new("ChildCount",[int],$Attributes) $ChildCount.Value = 1 $DynamicParameters.Add("ChildCount",$ChildCount) } return $DynamicParameters } process { # Simple Reset back to the standard prompt If ($Reset) { $Prompt = [PromptParts]::Reset Write-Verbose "Reseting Prompt" } Else { switch ( $Style ) { { $_ -in @([PromptStyle]::Condensed, [PromptStyle]::MultiLineCondensed) } { Write-Verbose "Parent Count: $($ParentCount.Value)" Write-Verbose "Child Count: $($ChildCount.Value)" # Set the Condensing Limiters in the function Body we just created $PromptString = [PromptParts]::Condensed. Replace('$ParentCountValue',$ParentCount.Value). Replace('$ChildCountValue',$ChildCount.Value) } Condensed { $Prompt = $PromptString. Replace('$JoinerString',"\"). Replace('$PromptType',"PSC"). Replace('$Token'," "). Replace('$EndToken', "") } MultiLineCondensed { $Prompt = $PromptString. Replace('$JoinerString',"``r``n"). Replace('$PromptType',"PSMLC"). Replace('$Token',"``r``n"). Replace('$EndToken', "``r``n") } MultiLine { $Prompt = [PromptParts]::MultiLine. Replace('$JoinerString',"``r``n") } } Write-Verbose "Set Prompt Style to $Style" } $CompletedPrompt = [ScriptBlock]::Create($Prompt) Write-Verbose "Writing new prompt function:`r`n`r`n$CompletedPrompt" Set-Content -Path Function:\prompt -Value $CompletedPrompt } } #endregion
First up we’ve got some helpers we want to setup.
#region Helpers Enum PromptStyle { Condensed MultiLine MultiLineCondensed } # Helper Class for building our prompt functions Class PromptParts { #region The Hidden Stuff # holds our single instance of our class hidden static [PromptParts] $Instance # Gets or creates our single instance of our class hidden static [PromptParts] Get() { # Do we have an instance? No? Well Create One If ($null -eq [PromptParts]::Instance) { [PromptParts]::Instance = [PromptParts]::new() } # Return it return [PromptParts]::Instance } # Create our 1st ScriptBlock that all prompts share hidden [ScriptBlock] $AllStartRaw = { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) } # Create our 2nd ScriptBlock that ALL Styles share hidden [ScriptBlock] $AllStylesRaw = { # When we glue our path back together this will be what we use $Joiner = "$JoinerString" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' } # Create our 3rd ScriptBlock for both Condensed Styles hidden [ScriptBlock] $CondensedRaw = { # Set How Many Child Path parts we want $ChildCount = $ChildCountValue # Set how many Parent Path parts we want $ParentCount = $ParentCountValue # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{$PromptType}$Token$($PathParts -join $Joiner)$EndToken$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{$PromptType}$Token$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$EndToken$End" } } # Create our 3rd ScriptBlock for MultiLine hidden [ScriptBlock] $MultiLineRaw = { "$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End" } # Create our 2nd ScriptBlock for Reset hidden [ScriptBlock] $ResetRaw = { # Set us back to as close to the original function "$($Start)PS $($executionContext.SessionState.Path.CurrentLocation)$End" } #endregion #region The Visible Stuff # Glues together all the ScriptBlocks to make Reset static [string] $Reset = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().ResetRaw)" # Glues together all the ScriptBlocks to make both Styles of Condensed static [string] $Condensed = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().CondensedRaw)" # Glues together all the ScriptBlocks to make Multiline static [string] $MultiLine = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().MultiLineRaw)" #endregion } #endregion
Starting with a simple Enum. I use Enums whenever I can when I’m dealing with Parameter’s that I want a fixed list of options its cleaner than using a validation list and they “just work” with switches, plus I vibe with the sudo strongly typed nature of using them
Enum PromptStyle { Condensed MultiLine MultiLineCondensed }
Next our other helper is our PromptParts class. I tend to put things that would normally be a script level or global level variable into a class that I can call wherever I want in my module to retrieve values just keeps things cleaner in my opinion
# Helper Class for building our prompt functions Class PromptParts { #region The Hidden Stuff # holds our single instance of our class hidden static [PromptParts] $Instance # Gets or creates our single instance of our class hidden static [PromptParts] Get() { # Do we have an instance? No? Well Create One If ($null -eq [PromptParts]::Instance) { [PromptParts]::Instance = [PromptParts]::new() } # Return it return [PromptParts]::Instance } # Create our 1st ScriptBlock that all prompts share hidden [ScriptBlock] $AllStartRaw = { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) } # Create our 2nd ScriptBlock that ALL Styles share hidden [ScriptBlock] $AllStylesRaw = { # When we glue our path back together this will be what we use $Joiner = "$JoinerString" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' } # Create our 3rd ScriptBlock for both Condensed Styles hidden [ScriptBlock] $CondensedRaw = { # Set How Many Child Path parts we want $ChildCount = $ChildCountValue # Set how many Parent Path parts we want $ParentCount = $ParentCountValue # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{$PromptType}$Token$($PathParts -join $Joiner)$EndToken$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{$PromptType}$Token$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$EndToken$End" } } # Create our 3rd ScriptBlock for MultiLine hidden [ScriptBlock] $MultiLineRaw = { "$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End" } # Create our 2nd ScriptBlock for Reset hidden [ScriptBlock] $ResetRaw = { # Set us back to as close to the original function "$($Start)PS $($executionContext.SessionState.Path.CurrentLocation)$End" } #endregion #region The Visible Stuff # Glues together all the ScriptBlocks to make Reset static [string] $Reset = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().ResetRaw)" # Glues together all the ScriptBlocks to make both Styles of Condensed static [string] $Condensed = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().CondensedRaw)" # Glues together all the ScriptBlocks to make Multiline static [string] $MultiLine = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().MultiLineRaw)" #endregion }
This class has 2 basic sections the first is our hidden fields these hold a single instance of our class, and the actual ScriptBlocks that make up our different styles of Prompt function
#region The Hidden Stuff # holds our single instance of our class hidden static [PromptParts] $Instance # Gets or creates our single instance of our class hidden static [PromptParts] Get() { # Do we have an instance? No? Well Create One If ($null -eq [PromptParts]::Instance) { [PromptParts]::Instance = [PromptParts]::new() } # Return it return [PromptParts]::Instance } # Create our 1st ScriptBlock that all prompts share hidden [ScriptBlock] $AllStartRaw = { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) } # Create our 2nd ScriptBlock that ALL Styles share hidden [ScriptBlock] $AllStylesRaw = { # When we glue our path back together this will be what we use $Joiner = "$JoinerString" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' } # Create our 3rd ScriptBlock for both Condensed Styles hidden [ScriptBlock] $CondensedRaw = { # Set How Many Child Path parts we want $ChildCount = $ChildCountValue # Set how many Parent Path parts we want $ParentCount = $ParentCountValue # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{$PromptType}$Token$($PathParts -join $Joiner)$EndToken$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{$PromptType}$Token$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$EndToken$End" } } # Create our 3rd ScriptBlock for MultiLine hidden [ScriptBlock] $MultiLineRaw = { "$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End" } # Create our 2nd ScriptBlock for Reset hidden [ScriptBlock] $ResetRaw = { # Set us back to as close to the original function "$($Start)PS $($executionContext.SessionState.Path.CurrentLocation)$End" } #endregion
We start off with our static property and method to ensure we only ever have one instance of this class created
# holds our single instance of our class hidden static [PromptParts] $Instance # Gets or creates our single instance of our class hidden static [PromptParts] Get() { # Do we have an instance? No? Well Create One If ($null -eq [PromptParts]::Instance) { [PromptParts]::Instance = [PromptParts]::new() } # Return it return [PromptParts]::Instance }
Next we have our ScriptBlocks that make up our different Prompt functions. We begin with the code they ALL share, followed by the code that all the Styles share. These should look familiar from earlier but with some twists. Notice $Joiner
is now set to a string with another variable $JoinerString
more on that when we get down to the Cmdlet itself
# Create our 1st ScriptBlock that all prompts share hidden [ScriptBlock] $AllStartRaw = { # Setup to show when we are debugging $Start = If ($PSDebugContext) { "[DBG]" } Else {""} # Setup how many > we need $End = '>' * ($nestedPromptLevel + 1) } # Create our 2nd ScriptBlock that ALL Styles share hidden [ScriptBlock] $AllStylesRaw = { # When we glue our path back together this will be what we use $Joiner = "$JoinerString" # Chop up our path $PathParts = $ExecutionContext.SessionState.Path.CurrentLocation -split '\\' }
Next up is the meat of the different Styles (and reset) Prompts. Like previously this should also look familiar but again things like $ChildCount
and $ParentCount
are no longer hard coded values. We even have some other new undeclared variables in here $PromptType
, $Token
, $EndToken
# Create our 3rd ScriptBlock for both Condensed Styles hidden [ScriptBlock] $CondensedRaw = { # Set How Many Child Path parts we want $ChildCount = $ChildCountValue # Set how many Parent Path parts we want $ParentCount = $ParentCountValue # If we don't have enough to condense we just return the full path glued together with our Joiner If ($PathParts.Count -le $ParentCount + $ChildCount) { "$Start{$PromptType}$Token$($PathParts -join $Joiner)$EndToken$End" } # Otherwise we grab the ParentCount of path items from the front and ChildCount from the end # leaving .. in the middle Else { "$Start{$PromptType}$Token$( $PathParts[0..($ParentCount - 1)] -join $Joiner)$Joiner..$Joiner$( $PathParts[($PathParts.Count - $ChildCount)..($PathParts.Count)] -join $Joiner)$EndToken$End" } } # Create our 3rd ScriptBlock for MultiLine hidden [ScriptBlock] $MultiLineRaw = { "$Start{PSML}$Joiner$($PathParts -join $Joiner)$Joiner$End" } # Create our 2nd ScriptBlock for Reset hidden [ScriptBlock] $ResetRaw = { # Set us back to as close to the original function "$($Start)PS $($executionContext.SessionState.Path.CurrentLocation)$End" }
Then we have the actual visible stuff we’ll be using from outside the class. This is pretty straight forward we create string that is all the hidden ScriptBlocks above combined together for each Style and Reset
#region The Visible Stuff # Glues together all the ScriptBlocks to make Reset static [string] $Reset = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().ResetRaw)" # Glues together all the ScriptBlocks to make both Styles of Condensed static [string] $Condensed = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().CondensedRaw)" # Glues together all the ScriptBlocks to make Multiline static [string] $MultiLine = "$([PromptParts]::Get().AllStartRaw)$([PromptParts]::Get().AllStylesRaw)$([PromptParts]::Get().MultiLineRaw)" #endregion
After all that we get down to our actual Cmdlet Set-Prompt
#region Cmdlets Function Set-Prompt { [CmdletBinding()] Param( [Parameter(Mandatory=$true,HelpMessage="Prompt Style", ParameterSetName="Style")] [PromptStyle] $Style, [Parameter(Mandatory=$true,HelpMessage="Reset To default prompt", ParameterSetName="Reset")] [Switch] $Reset ) DynamicParam { # If a user selects either of the Condensed options we want to prompt how much we want to condense $DynamicParameters = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() if ($Style -in @([PromptStyle]::Condensed,[PromptStyle]::MultiLineCondensed )) { $Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new() $ParameterAttribute.ParameterSetName = "Style" $Attributes.Add($ParameterAttribute) $Attributes.Add([System.Management.Automation.ValidateRangeAttribute]::new(1,10)) # How Many Parents do We show $ParentCount = [System.Management.Automation.RuntimeDefinedParameter]::new("ParentCount",[int],$Attributes) $ParentCount.Value = 1 $DynamicParameters.Add("ParentCount",$ParentCount) # How Many Children do we show $ChildCount = [System.Management.Automation.RuntimeDefinedParameter]::new("ChildCount",[int],$Attributes) $ChildCount.Value = 1 $DynamicParameters.Add("ChildCount",$ChildCount) } return $DynamicParameters } process { # Simple Reset back to the standard prompt If ($Reset) { $Prompt = [PromptParts]::Reset Write-Verbose "Reseting Prompt" } Else { switch ( $Style ) { { $_ -in @([PromptStyle]::Condensed, [PromptStyle]::MultiLineCondensed) } { Write-Verbose "Parent Count: $($ParentCount.Value)" Write-Verbose "Child Count: $($ChildCount.Value)" # Set the Condensing Limiters in the function Body we just created $PromptString = [PromptParts]::Condensed. Replace('$ParentCountValue',$ParentCount.Value). Replace('$ChildCountValue',$ChildCount.Value) } Condensed { $Prompt = $PromptString. Replace('$JoinerString',"\"). Replace('$PromptType',"PSC"). Replace('$Token'," "). Replace('$EndToken', "") } MultiLineCondensed { $Prompt = $PromptString. Replace('$JoinerString',"``r``n"). Replace('$PromptType',"PSMLC"). Replace('$Token',"``r``n"). Replace('$EndToken', "``r``n") } MultiLine { $Prompt = [PromptParts]::MultiLine. Replace('$JoinerString',"``r``n") } } Write-Verbose "Set Prompt Style to $Style" } $CompletedPrompt = [ScriptBlock]::Create($Prompt) Write-Verbose "Writing new prompt function:`r`n`r`n$CompletedPrompt" Set-Content -Path Function:\prompt -Value $CompletedPrompt } } #endregion
We begin here with our Cmdlet house keeping. We set [CmdletBinding()]
to allow us to use things like -Verbose
then we get into setting our Parameters we begin with our 2 main parameters each with its own Parameter Set name so you can’t set a style AND Reset at the same time
[CmdletBinding()] Param( [Parameter(Mandatory=$true,HelpMessage="Prompt Style", ParameterSetName="Style")] [PromptStyle] $Style, [Parameter(Mandatory=$true,HelpMessage="Reset To default prompt", ParameterSetName="Reset")] [Switch] $Reset )
Finally we have to create 2 Dynamic parameters but only if the Style the user has selected is one that condenses and thus needs a value for $ParentCount
and $ChildCount
DynamicParam { # If a user selects either of the Condensed options we want to prompt how much we want to condense $DynamicParameters = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() if ($Style -in @([PromptStyle]::Condensed,[PromptStyle]::MultiLineCondensed )) { $Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new() $ParameterAttribute.ParameterSetName = "Style" $Attributes.Add($ParameterAttribute) $Attributes.Add([System.Management.Automation.ValidateRangeAttribute]::new(1,10)) # How Many Parents do We show $ParentCount = [System.Management.Automation.RuntimeDefinedParameter]::new("ParentCount",[int],$Attributes) $ParentCount.Value = 1 $DynamicParameters.Add("ParentCount",$ParentCount) # How Many Children do we show $ChildCount = [System.Management.Automation.RuntimeDefinedParameter]::new("ChildCount",[int],$Attributes) $ChildCount.Value = 1 $DynamicParameters.Add("ChildCount",$ChildCount) } return $DynamicParameters }
Now we’re finally into functionality of our Cmdlet. We have to use the process block because we have a dynamic parameter
process { # Simple Reset back to the standard prompt If ($Reset) { $Prompt = [PromptParts]::Reset Write-Verbose "Reseting Prompt" } Else { switch ( $Style ) { { $_ -in @([PromptStyle]::Condensed, [PromptStyle]::MultiLineCondensed) } { Write-Verbose "Parent Count: $($ParentCount.Value)" Write-Verbose "Child Count: $($ChildCount.Value)" # Set the Condensing Limiters in the function Body we just created $PromptString = [PromptParts]::Condensed. Replace('$ParentCountValue',$ParentCount.Value). Replace('$ChildCountValue',$ChildCount.Value) } Condensed { $Prompt = $PromptString. Replace('$JoinerString',"\"). Replace('$PromptType',"PSC"). Replace('$Token'," "). Replace('$EndToken', "") } MultiLineCondensed { $Prompt = $PromptString. Replace('$JoinerString',"``r``n"). Replace('$PromptType',"PSMLC"). Replace('$Token',"``r``n"). Replace('$EndToken', "``r``n") } MultiLine { $Prompt = [PromptParts]::MultiLine. Replace('$JoinerString',"``r``n") } } Write-Verbose "Set Prompt Style to $Style" } $CompletedPrompt = [ScriptBlock]::Create($Prompt) Write-Verbose "Writing new prompt function:`r`n`r`n$CompletedPrompt" Set-Content -Path Function:\prompt -Value $CompletedPrompt }
We start with the simplest the Reset we just pull the string representation of its ScriptBlock from our helper Class
# Simple Reset back to the standard prompt If ($Reset) { $Prompt = [PromptParts]::Reset Write-Verbose "Reseting Prompt" }
Otherwise we dive into our Prompt Styles.
Else { switch ( $Style ) { { $_ -in @([PromptStyle]::Condensed, [PromptStyle]::MultiLineCondensed) } { Write-Verbose "Parent Count: $($ParentCount.Value)" Write-Verbose "Child Count: $($ChildCount.Value)" # Set the Condensing Limiters in the function Body we just created $PromptString = [PromptParts]::Condensed. Replace('$ParentCountValue',$ParentCount.Value). Replace('$ChildCountValue',$ChildCount.Value) } Condensed { $Prompt = $PromptString. Replace('$JoinerString',"\"). Replace('$PromptType',"PSC"). Replace('$Token'," "). Replace('$EndToken', "") } MultiLineCondensed { $Prompt = $PromptString. Replace('$JoinerString',"``r``n"). Replace('$PromptType',"PSMLC"). Replace('$Token',"``r``n"). Replace('$EndToken', "``r``n") } MultiLine { $Prompt = [PromptParts]::MultiLine. Replace('$JoinerString',"``r``n") } } Write-Verbose "Set Prompt Style to $Style" }
This Switch statement has some things worth talking about starting with the first Case. This one we’re setting up a case for either of the Condensed Styles here like in Reset above we get our string from our Class but then we run some replacements on the string. Remember I mentioned those undeclared variables up in our Class’s hidden ScriptBlocks? well here is where they get swapped out for the actual values we want starting with our Dynamic ParentCount
and ChildCount
parameters. What you might also notice is a complete lack of a break
at the end of any of these case statements. This is because we are taking advantage of case fall through where if no break is found the code will execute ALL cases that are satisfied when falling through the switch statement.
{ $_ -in @([PromptStyle]::Condensed, [PromptStyle]::MultiLineCondensed) } { Write-Verbose "Parent Count: $($ParentCount.Value)" Write-Verbose "Child Count: $($ChildCount.Value)" # Set the Condensing Limiters in the function Body we just created $PromptString = [PromptParts]::Condensed. Replace('$ParentCountValue',$ParentCount.Value). Replace('$ChildCountValue',$ChildCount.Value) }
Meaning we ALSO hit these cases and this is where we finally address the rest of those undeclared variables by swapping out for the values of each Condensed style. here we don’t need break because its only ever going to be one of the Enum
Condensed { $Prompt = $PromptString. Replace('$JoinerString',"\"). Replace('$PromptType',"PSC"). Replace('$Token'," "). Replace('$EndToken', "") } MultiLineCondensed { $Prompt = $PromptString. Replace('$JoinerString',"``r``n"). Replace('$PromptType',"PSMLC"). Replace('$Token',"``r``n"). Replace('$EndToken', "``r``n") }
And our final simpler MultiLine case
MultiLine { $Prompt = [PromptParts]::MultiLine. Replace('$JoinerString',"``r``n") }
Finally we convert our string into a full blown ScriptBlock, and set our prompt function to that ScriptBlock (since PowerShell treats Function: as a drive each function is basically a “File” and you can use the same Set-Content
Cmdlet to write to it.
$CompletedPrompt = [ScriptBlock]::Create($Prompt) Write-Verbose "Writing new prompt function:`r`n`r`n$CompletedPrompt" Set-Content -Path Function:\prompt -Value $CompletedPrompt
Save that whole file as SetPrompt.psm1 create a folder named SetPrompt under whatever level of module you want (user, system, ise, etc) I put mine right under C:\Windows\System32\WindowsPowerShell\v1.0\Modules since I want this module to be available anywhere I jump into powershell (even works inside VS Code and other shells) and copy our psm1 file to SetPrompt you don’t really need a .psd1 file as this is a DEAD simple module as long as the folder name patches the .psm1 file name it will automatically pull it into the shell as available. Then the next time you fire up a fresh Shell you can jump right into calling
Set-Prompt -Style Condensed -ParentCount 2 -ChildCount 2 -Verbose
No need to import the module, Tab Completion will work just fine since ParentCount and ChildCount have default values of 1 you can also omit either or both
Set-Prompt -Style Condensed -Verbose
To reset just call
Set-Prompt -Reset -Verbose
That’s it nothing glamorous. But I’ve always prized functionality of form and we’ve solved our problem and turned it into an easy Cmdlet we can use to switch between Prompt Styles with minimal effort so yay.
Until the next one in this series where we go hog wild with “drive” letters