
Welcome PowerShell User! This recipe is just one of the hundreds of useful resources contained in the PowerShell Cookbook.
If you own the book already, login here to get free, online, searchable access to the entire book's content.
If not, the Windows PowerShell Cookbook is available at Amazon, or any of your other favourite book retailers. If you want to see what the PowerShell Cookbook has to offer, enjoy this free 90 page e-book sample: "The Windows PowerShell Interactive Shell".
When viewing or demonstrating scripts, syntax highlighting makes the information immensely easier to read. Viewing the scripts in Visual Studio Code is the most natural (and powerful) option, but you might want to view them in the console as well.
In addition to basic syntax highlighting, other useful features during script review are line numbers and highlighting ranges of lines. Range highlighting is especially useful when discussing portions of a script in a larger context.
Example 8-3 enables all of these scenarios by providing syntax highlighting of scripts in a console session. Figure 8-1 shows a sample of the colorized content.
In addition to having utility all on its own, Show-ColorizedContent.ps1 demonstrates how to use PowerShell’s Tokenizer API, as introduced in Recipe 10.10. While many of the techniques in this example are specific to syntax highlighting in a PowerShell console, many more apply to all forms of script manipulation.
################################################################################## Show-ColorizedContent#### From PowerShell Cookbook (O'Reilly)## by Lee Holmes (http://www.leeholmes.com/guide)################################################################################<#.SYNOPSISDisplays syntax highlighting, line numbering, and range highlighting forPowerShell scripts..EXAMPLEPS > Show-ColorizedContent Invoke-MyScript.ps1001 | function Write-Greeting002 | {003 | param($greeting)004 | Write-Host "$greeting World"005 | }006 |007 | Write-Greeting "Hello".EXAMPLEPS > Show-ColorizedContent Invoke-MyScript.ps1 -highlightRange (1..3+7)001 > function Write-Greeting002 > {003 > param($greeting)004 | Write-Host "$greeting World"005 | }006 |007 > Write-Greeting "Hello"#>param(## The path to colorize[Parameter(Mandatory=$true)]$Path,## The range of lines to highlight$HighlightRange=@(),## Switch to exclude line numbers[Switch]$ExcludeLineNumbers)Set-StrictMode-Version3## Colors to use for the different script tokens.## To pick your own colors:## [Enum]::GetValues($host.UI.RawUI.ForegroundColor.GetType()) |## Foreach-Object { Write-Host -Fore $_ "$_" }$replacementColors=@{'Attribute'='DarkCyan''Command'='Blue''CommandArgument'='Magenta''CommandParameter'='DarkBlue''Comment'='DarkGreen''GroupEnd'='Black''GroupStart'='Black''Keyword'='DarkBlue''LineContinuation'='Black''LoopLabel'='DarkBlue''Member'='Black''NewLine'='Black''Number'='Magenta''Operator'='DarkGray''Position'='Black''StatementSeparator'='Black''String'='DarkRed''Type'='DarkCyan''Unknown'='Black''Variable'='Red'}$highlightColor="Red"$highlightCharacter=">"$highlightWidth=6if($excludeLineNumbers){$highlightWidth=0}## Read the text of the file, and tokenize it$content=Get-Content$Path-Raw$parsed=[System.Management.Automation.PsParser]::Tokenize($content,[ref]$null)|SortStartLine,StartColumn## Write a formatted line -- in the format of:## <Line Number> <Separator Character> <Text>functionWriteFormattedLine($formatString,[int]$line){if($excludeLineNumbers){return}## By default, write the line number in gray, and use## a simple pipe as the separator$hColor="DarkGray"$separator="|"## If we need to highlight the line, use the highlight## color and highlight separator as the separatorif($highlightRange-contains$line){$hColor=$highlightColor$separator=$highlightCharacter}## Write the formatted line$text=$formatString-f$line,$separatorWrite-Host-NoNewLine-Fore$hColor-BackWhite$text}## Complete the current line with filler cellsfunctionCompleteLine($column){## Figure how much space is remaining$lineRemaining=$host.UI.RawUI.WindowSize.Width-$column-$highlightWidth+1## If we have less than 0 remaining, we've wrapped onto the## next line. Add another buffer width worth of fillerif($lineRemaining-lt0){$lineRemaining+=$host.UI.RawUI.WindowSize.Width}Write-Host-NoNewLine-BackWhite(" "*$lineRemaining)}## Write the first line of context information (line number,## highlight character.)Write-HostWriteFormattedLine"{0:D3} {1} "1## Now, go through each of the tokens in the input## script$column=1foreach($tokenin$parsed){$color="Gray"## Determine the highlighting color for that token by looking## in the hashtable that maps token types to their color$color=$replacementColors[[string]$token.Type]if(-not$color){$color="Gray"}## If it's a newline token, write the next line of context## informationif(($token.Type-eq"NewLine")-or($token.Type-eq"LineContinuation")){CompleteLine$columnWriteFormattedLine"{0:D3} {1} "($token.StartLine+1)$column=1}else{## Do any indentingif($column-lt$token.StartColumn){$text=" "*($token.StartColumn-$column)Write-Host-BackWhite-NoNewLine$text$column=$token.StartColumn}## See where the token ends$tokenEnd=$token.Start+$token.Length-1## Handle the line numbering for multi-line strings and commentsif((($token.Type-eq"String")-or($token.Type-eq"Comment"))-and($token.EndLine-gt$token.StartLine)){## Store which line we've started at$lineCounter=$token.StartLine## Split the content of this token into its lines## We use the start and end of the tokens to determine## the position of the content, but use the content## itself (rather than the token values) for manipulation.$stringLines=$(-join$content[$token.Start..$tokenEnd]-split"`n")## Go through each of the lines in the contentforeach($stringLinein$stringLines){$stringLine=$stringLine.Trim()## If we're on a new line, fill the right hand## side of the line with spaces, and write the header## for the new line.if($lineCounter-gt$token.StartLine){CompleteLine$columnWriteFormattedLine"{0:D3} {1} "$lineCounter$column=1}## Now write the text of the current lineWrite-Host-NoNewLine-Fore$color-BackWhite$stringLine$column+=$stringLine.Length$lineCounter++}}## Write out a regular tokenelse{## We use the start and end of the tokens to determine## the position of the content, but use the content## itself (rather than the token values) for manipulation.$text=(-join$content[$token.Start..$tokenEnd])Write-Host-NoNewLine-Fore$color-BackWhite$text}## Update our position in the column$column=$token.EndColumn}}CompleteLine$columnWrite-Host
For more information about running scripts, see Recipe 1.2.
Recipe 1.2, “Run Programs, Scripts, and Existing Tools”
Recipe 10.10, “Parse and Interpret PowerShell Scripts”