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)
##
##############################################################################
<#
.SYNOPSIS
Displays syntax highlighting, line numbering, and range highlighting for
PowerShell scripts.
.EXAMPLE
PS > Show-ColorizedContent Invoke-MyScript.ps1
001 | function Write-Greeting
002 | {
003 | param($greeting)
004 | Write-Host "$greeting World"
005 | }
006 |
007 | Write-Greeting "Hello"
.EXAMPLE
PS > Show-ColorizedContent Invoke-MyScript.ps1 -highlightRange (1..3+7)
001 > function Write-Greeting
002 > {
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
-Version
3
## 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
=
6
if
(
$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
)
|
Sort
StartLine
,
StartColumn
## Write a formatted line -- in the format of:
## <Line Number> <Separator Character> <Text>
function
WriteFormattedLine
(
$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 separator
if
(
$highlightRange
-contains
$line
)
{
$hColor
=
$highlightColor
$separator
=
$highlightCharacter
}
## Write the formatted line
$text
=
$formatString
-f
$line
,
$separator
Write-Host
-NoNewLine
-Fore
$hColor
-Back
White
$text
}
## Complete the current line with filler cells
function
CompleteLine
(
$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 filler
if
(
$lineRemaining
-lt
0
)
{
$lineRemaining
+=
$host
.
UI
.
RawUI
.
WindowSize
.
Width
}
Write-Host
-NoNewLine
-Back
White
(
" "
*
$lineRemaining
)
}
## Write the first line of context information (line number,
## highlight character.)
Write-Host
WriteFormattedLine
"{0:D3} {1} "
1
## Now, go through each of the tokens in the input
## script
$column
=
1
foreach
(
$token
in
$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
## information
if
((
$token
.
Type
-eq
"NewLine"
)
-or
(
$token
.
Type
-eq
"LineContinuation"
))
{
CompleteLine
$column
WriteFormattedLine
"{0:D3} {1} "
(
$token
.
StartLine
+
1
)
$column
=
1
}
else
{
## Do any indenting
if
(
$column
-lt
$token
.
StartColumn
)
{
$text
=
" "
*
(
$token
.
StartColumn
-
$column
)
Write-Host
-Back
White
-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 comments
if
(
((
$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 content
foreach
(
$stringLine
in
$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
$column
WriteFormattedLine
"{0:D3} {1} "
$lineCounter
$column
=
1
}
## Now write the text of the current line
Write-Host
-NoNewLine
-Fore
$color
-Back
White
$stringLine
$column
+=
$stringLine
.
Length
$lineCounter
++
}
}
## Write out a regular token
else
{
## 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
-Back
White
$text
}
## Update our position in the column
$column
=
$token
.
EndColumn
}
}
CompleteLine
$column
Write-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”