2017-07-28

Bye Bye Backtick: Natural Line Continuations in PowerShell

Intro

PowerShell is a language which lends itself to lengthy lines of code. Recommended Best Practice (RBP) is that functions and cmdlets along with their parameters be named descriptively and without abbreviation. It is also RBP to not use aliases for them in your code. Additionally, RBP is to use full names for variables in Pascal Case or Camel Case. The point of this practice is to make the code read more like a story and less like a cheap furniture construction manual. As far as RBP’s go, these I whole heartedly agree with. In addition to the verbosity of keywords, combining multiple commands in a pipeline will also greatly increase the number of characters per line.

PowerShell offers several ways to break up your lengthy lines into multiple shorter lines. Some of these are well known and others not so much. One of them many PowerShell users will encounter very early and it is the worst option. Most often this is the first method for multi-line commands a PowerShell novice learns and sometimes it is the only one. Thus, this worst possible method has ingrained itself into the annals of PowerShell history and permeates like a cancer.

I’m talking, here, about the backtick (or backquote, or grave accent). You know, this hard to see character:

`

Line continuation, word wrapping, line wrapping, and line breaking are all terms used to describe the process of splitting a single command across multiple line. I prefer the term Line Continuation because I have only seen it used in the context of programming and not in typesetting or other crafts.

In this blog post I will cover why line length matters, why we should avoid the backtick for line continuations, and various alternative methods and strategies for reducing line length and splitting lines of code. This post should be suitable for new and experienced PowerShell users. Some terminology may be foreign to new users. The PowerShell major versions targeted by this post are 5 & 6. The methods described in this post may work in later versions or earlier versions but have not been tested or verified in those versions.


Table of Contents


Size Matters: Why Line Length is Important

This topic is one that has been hashed out many times in many programming courses, books and blogs. At this point, it is near axiomatic that shorter lines of code are preferable to longer lines. Rather than go in depth on a subject that is more eloquently described elsewhere, I will seek here to offer a brief and incomplete review of the various reasons why line length matters. I won’t waste space defending these reasons in depth, so if you have interests in arguing this, I suggest you first read pretty much any book that covers coding best practices and style guides. You will find few, if any, that disagree.

My primary gripe with long lines of code is that I have to scroll horizontally. I am lazy and modern GUIs are designed to make vertical scroll easy. But horizontal scroll is often an afterthought. This is very apparent on GitHub. For example, look at Line 75 of ClearRecycleBinCommand.cs in the PowerShell Source code. Several of the method arguments are not visible unless you highlight the line and drag to the right.

This might seem like a minor annoyance for viewing code, but it is actually a large problem for editing and debugging code (especially code written by others). When you are working in your favorite code editor (who am I kidding, we are all using VS Code with the PowerShell extension now, right?) you will notice that even on a wide screen monitor, much of the horizontal space is often dominated by various tools and menus. You may still have a great deal of code on a long line visible, but what about the code that is not visible? How easy is it to spot errors in that code? Maybe you have a line that looks like this in the editor:

Invoke-SomeFunction -SomeParameter 'Some value' -AnotherParameter 'Another Value'

But is actually this:

Invoke-SomeFunction -SomeParameter 'Some value' -AnotherParameter 'Another Value' | Invoke-UnseenFunction -UnseenParameter 'Unseen Value'

The line is, perhaps, so long that the editor just happened to visibly cut off right before the pipe. Casually reading the script, you might miss the horizontal scroll and thus miss the rest of that pipeline. Sure, you may eventually figure out that line is more than it seems, but how much time and effort was wasted to get there?

My second gripe with long lines of code is that they are hard to parse as a human. I don’t know why this is or how much this affects everyone, but it is easier to parse a vertical list than a horizontal one. If you think of lines of code as lists of words rather than a complete syntax this makes more sense, in my opinion. Given these two list of animals:

Cow Dog Cat Duck Goose Echinda Porcupine Narwhal Shia LaBeouf

or

Cow
Dog
Cat
Duck
Goose
Echinda
Porcupine
Narwhal
Shia LaBeouf

which is easier to parse, look for inclusion and exclusions, or even spot the actual “cannibal” accidently placed in the list of “animals”?

You may be wondering what the maximum line length in PowerShell should be. This is a good question. Many other languages recommend 80 characters, but for PowerShell, that can be a bit too restrictive. I have seen 120 and 150 characters recommended and I personally use and recommend 120. It's not a hard rule, but any line that is longer than 120 character is something I always look at refactoring or introducing a line break.


Blasted Backtick Begone!

We know long lines of code are undesirable, but we also know PowerShell naturally creates long lines of code. So how do we deal with this? Let’s say you are new to PowerShell and you google something like “PowerShell multiline command”. The top results will be all about the backtick. To Don Jones’s credit, he also has a quick blog entry denouncing the backtick that shows up in the results, but the rest is a sea of grave accents.

This section will cover what the backtick actually does in PowerShell and why it should be avoided for line continuations.

What is the Backtick in PowerShell?

The backtick, backquote or grave accent serves a single purpose in PowerShell: It is the escape character. This is intuitive when you deal with special characters in double quoted strings such as the dollar sign, double quotes, newlines, or even the backtick character itself.

# Consider using single quotes instead
"This `"string`" `$useses `r`n Backticks '``'"

That’s it! That’s its only job. This escape character can be used outside of double quotes in the same way to escape characters in command lines.

# Never do this in production code
Write-Host `$Hello `" ``


How Does the Backtick Allow for Line Continuation?

As mentioned, the backtick is an escape character. If you consider a line break as special character instead of magic that puts text on another line, then what happens if you escape that special newline character?

# Never do this in production code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' `
    -Method 'GET'

Escaping the newline allows you split the command to newline because the newline is no longer interpreted as the end of the command line.

So, Why is That Such a Bad Thing?

It seems harmless. Using the backtick to escape the newline seems like and easy and effective way to avoid long lines. I know this train of thought because for the first year of my PowerShell coding I liberally used the backtick for those exact reasons. But, it is bad… very bad.

Backticks are Easy to Miss

Look at the backtick by itself:

`

Is that a backtick? is that a single quote? is that a spec on your screen or maybe a dead pixel? Imagine this in printed text, like a PowerShell text book. It could appear to be just a smudge or ignored as a typo or typesetting issue.

In /r/PowerShell, once every few months we get a question from an OP (Original Poster) about a piece of code copied from a college professor’s PowerPoint slides where the code is seemingly copied verbatim but it does not run. To many of us whom often answer questions, the problem is obvious as the code the OP posts has a parameter on a line by itself. We finally had an OP include the slide form their professor and it went something like this:

# Professor's Code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' `
    -Method 'GET'
# OP's Code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/'
    -Method 'GET'

Poor OP is new to the language and trying to learn, but the professor chose to distribute code via PowerPoint and make heavy use of the backtick to make their long lines fit to the slide width. If you are this professor, please stop doing this to your poor students.

Backticks Don’t Create Line Continuations

“But you just said they did?” No, I said that the backtick serves the purpose of an escape character. They don’t create a line continuation so much as they escape the newline character which effectively results in a line continuation. This is an important distinction because this…

# Never do this in production code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' `
    -Method 'GET'

and this…

# Never do this in production code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' `
    -Method 'GET'

Look exactly the same visually. The difference is that the first one is escaping the newline and the second one is escaping a space. The first block will run, the second will have errors.  Because they are not visually distinct, to someone new to PowerShell and unaware that the backtick is an escape character, it is possible they may assume that backticks just allow for the line to continue. When they end up with whitespace after the backtick, it causes confusion due to odd behavior or errors.

Backticks Make for Hard-to-Maintain Code

This is a bit more subjective, but personally, I find maintaining code with backticks to be a complete pain nearing torture. Backticks have to go at the end of the line right before the line break which makes them prone to errors. Plus, you have to remember not to include one on the last line or else you will mess with the flow.

Let’s say you have the following code:

# Never do this in production code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' `
    -Method 'GET' `
    -UseBasicParsing
$Date = Get-Date

and you realize you can’t do basic parsing in this instance so that option needs to be removed. You adjust the code to this:

# Never do this in production code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' `
    -Method 'GET' `
$Date = Get-Date

and the code blows up. In addition to removing the –UseBasicParsing line, you have to also remember to remove the backtick on the line before it. A more elegant solution is splatting which I will cover later.

Also, let’s say someone else decides they need to basic parsing back to the code and they do this

# Never do this in production code
Invoke-WebRequest -Uri 'http://get-powershellblog.blogspot.com/' ` -UseBasicParsing
|    -Method 'GET'
$Date = Get-Date

I have a large amount of code that I wrote that I absolutely dread making changes to because I the code is littered with backtick landmines. Granted, some of the line continuation methods listed here suffer some of the same maintenance issues, they are slightly more forgiving about trailing whitespace.

When Is It OK to Use The Backtick?

I personally avoid backticks like the plague. First, a hard rule for me is to never use backticks for line continuation. If the temptation exists to use one, then there is probably other superior means for breaking up the code.

I also avoid using backticks in strings as escape characters. I make heavy use of single quotes for strings. This eliminates the need for escape characters. For variable substitution in strings I use the -f format operator instead of including the variable in double quotes. This eliminates the majority of use cases for backticks.

# This
"`$, `", and `` `r`nAre special characters!"
# Becomes this
'$, ", and `{0}Are special characters' -f [Environment]::NewLine

There are times when using a backtick in a string is just more convenient. Avoiding backticks in strings is not a hard rule in my book.

Another time you might use a backtick would be this:

$IntList = [System.Collections.Generic.List``1[[System.Int32, System.Private.CoreLib, Version = 4.0.0.0]]]::new()

This monstrosity allows you to pass assembly qualified type names to generic types. I'm not exactly sure when this would be used in PowerShell, but I won't rule it out from being a legitimate use of the backtick.


What are Natural Line Continuations and Natural Line Continuators?

Great! No more backticks! However, we still have the problem or PowerShell’s propensity for lengthy lines vs. RBP. The good news is that there are many options for breaking long lines into multiple lines that don’t require the backtick. I'm not sure what the exact technical terms are for these, but I call them Natural Line Continuations. These are parts of the PowerShell language which naturally allow you to continue on to the next line without any special character or consideration. The Natural Line Continuators in PowerShell occur after most Operators (with notable exceptions being the . dot sourcing operator, the & call operator, and output redirection operators like > and >>).

When you use a Natural Line Continuator in PowerShell you can add a line break in your code before continuing with the next token. In fact, they are whitespace agnostic, so spaces and tabs are also acceptable. This affords you great flexibility in laying out your code.


Arithmetic Operators: + - * / % -shl -shr

Addition, subtraction, multiplication, division, modulo, shift bits left and shift bits right are all Arithmetic operations in PowerShell and their Arithmetic Operators all support Natural Line Continuation. As an extreme, but useless example, take a look at this:

5 *
5 /
5 %
5 +
5 -
5 -shl
5 -shr
5

That will return 0, but it does demonstrate line breaking after each operation. As another example:

# Calculate the Total Anime Episodes in the Monogatari Series
$TotalMonogatariEpisodes =
    $Kizumongatari.Episodes.Count   +
    $Bakemonogatari.Episodes.Count  +
    $Nekomonogatari.Episodes.Count  +
    $Nisemonogatari.Episodes.Count  +
    $SecondSeason.Episodes.Count    +
    $Tsukimonogatari.Episodes.Count +
    $Owarimonogatari.Episodes.Count +
    $Koyomimonogatari.Episodes.Count  

There are certainly better ways to do this kind of operation. But if you ever run into a long line with a a bunch of arithmetic, the option exists to break it up to the next line after any of these operators.


Assignment Operators: = += -= *= /= %= ++ --

The Assignment Operators assign one or more value to a variable, and some perform operations while doing so. Oddly enough, the ++ increment and -- decrement operators support line continuation when used in their prefix form. It will not work with the postfix form. I would never recommend doing this because it looks really confusing.

# Don’t do this in production code
$Int = 5
++
$Int
# $Int is now 6
--
$Int
# $Int is now 5 again

Whereas this will have an error:

$Int
++
# Error: Missing expression after unary operator '++'

Again, I wouldn’t do this. In fact, I almost never see the prefix version of these operators used. It is more common to use the postfix version. I would recommend sticking with that common practice.

The rest, however, are very useful. I gave a demonstration of how do use = in the Arithmetic Operators section. I use that one quit a bit

$FileNames =
    1..15 | ForEach-Object {
        'Bakemonogatari - s01e{0:D2}.mkv' -f $_
    }

+= is pretty handy as well

$FileNames +=
    1..11 | ForEach-Object {
        'Nisemonogatari - s01e{0:D2}.mkv' -f $_
    }

This is also helpful when you have a deep property or long variable name to update with a long command as well:

$Object.Context.SubContext.Localization.Content =
    Invoke-WebRequest -Uri $Uri -UseBasicParsing -Headers $Headers -Body $Body -Method 'POST' |
    Select-Object -ExpandProperty Content

There are other ways to shorten those, that are preferable, but this works in a pinch. The other operators I don’t use as often, but they all work the same.


Comparison Operators: -eq -ne -gt -lt -le -ge -match -like -in -contains -replace

The Comparison Operators compare two values and return true or false if the condition is or is not met. Though I did not list them in the section title, the  inverse comparison operators (-notmatch  -notlike  -notin, -notconatins ) and case sensitivity variants (-ieq -ceq -ine -cne -imatch -cmatch) also work. The PowerShell Documentation includes -replace, but that’s not really a comparison operator and you would suffer if you placed it in an if condition.

I wouldn’t recommend using these as line continuators unless the two things they are comparing happen to be really long. It is better to keep the two things you are comparing as close together as possible. 

# Compare the RunTimes of the first chapters of the first episodes
$Kizumongatari.Episodes[0].Chapters[0].RunTime -lt
$Bakemonogatari.Episodes[0].Chapters[0].RunTime

Using -replace as a line continuator, though, can really help readability in some cases, especially with very complex regex patterns. It would likely be better to define the search and replace patterns as separate variables and use them, but some may argue against that. Here is an example of using -replace as a line continuator:

# Remove the URL from the text
'This is some text with https://someurl.com/ in the middle' -Replace
    '(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?',
    '<URL redacted>'

I prefer the below method as it becomes more flexible, it is easier to maintain, and it brings some clarity as to what the mass of string literals are

# Remove the URL from the text
$SearchString = 'This is some text with https://someurl.com/ in the middle'
$SearchPattern = '(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?'
$ReplacePattern = '<URL redacted>'
$SearchString -Replace $SearchPattern, $ReplacePattern



Logical Operators: -and -or -xor -not !

Logical Operators connect multiple statements and expressions in order to evaluate multiple conditions at one time. These are very useful as line continuators because they are generally connecting two distinct statements. It becomes easier to read them when the comparison statements are not all on one line.

If (
    $Kizumongatari.Episodes.Count -lt $Bakemonogatari.Episodes.Count  -and
    $Kizumongatari.Episodes.Count -lt $Nekomonogatari.Episodes.Count  -and
    $Kizumongatari.Episodes.Count -lt $Nisemonogatari.Episodes.Count  -and
    $Kizumongatari.Episodes.Count -lt $SecondSeason.Episodes.Count    -and
    $Kizumongatari.Episodes.Count -lt $Tsukimonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Owarimonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Koyomimonogatari.Episodes.Count
){
    'Kizumonogatari has the lowest episode count.'
}

I started making heavy use of this about 6 months ago. I think it really makes reading complex conditions easier. You can also see why I suggested not using the Comparison Operators as line continuators. With the two values being compared on the same line with their Comparison Operator it is much easier to parse each comparison. Then separating different comparisons on different lines makes it easier to parse the entire complex condition.

Another example from a psake build script:

if (
    $ENV:BHBuildSystem   -ne    'Unknown' -and
    $ENV:BHBranchName    -eq    "master"  -and
    $ENV:BHCommitMessage -match '!deploy'
) {
    #...
}

Compare that to this:

if ($ENV:BHBuildSystem -ne 'Unknown' -and $ENV:BHBranchName -eq "master" -and $ENV:BHCommitMessage -match '!deploy'){
    #...
}

I personally feel the this last example is much harder to parse as a human.


-split and -join Operators

The -split Operator and the -join Operator work on strings and collections to split a string into a collection or join a collection into a string. This section also applies to the case sensitivity variants -isplit and -csplit.  These operators both have a Unary and Binary usage. Using them in their Unary usage works really well was a line continuator.

$Strings = -split
    'This really long string will become a collection of strings split at the spaces'
$String = -join
(
    'This is a paragraph of strings that are rather long. ',
    'They will be joined together to form a single string. ',
    "This isn't the best way to concatenate long strings like this, ",
    'however, it is an option.'
)

It generally doesn't make sense in their binary form because the patterns used to split and join are usually short strings. There is a usage of -split I don't see that often where it might make sense.

$Strings = $SomeString -split
    $SomePattern,
    0,
    'RegexMatch,IgnorePatternWhitespace,IgnoreCase,Multiline,ExplicitCapture'

This would be better if the split delimiter, maximum substrings, and split option were stored in variables and called like this:

$Strings = $SomeString -split $DelimiterPatern, $MaxSubStrings, $SplitOptions

But, if you had a particularly long split pattern or join string and wanted to keep them close to the operation this is an option.

$ViewXML = $ViewNames -join
@'
">
        <height>10</height>
        <width>11</width>
        <MaxRows>50</MaxRows>
    </View>
    <View Name="
'@


Type Operators: -is -isnot -as

Type Operators evaluate and transform object types.

$RuleCollection[0] -is
    [Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.AvoidUsingConvertToSecureStringWithPlainText]


Subexpression Operator: $( )

The sub expression operator executes the code with in it and returns the value(s). It is a convenient tool, but I see is used as a crutch far too often. It really shines when you are doing console activities, but if you find it in your production code, you should probably refactor it out for friendlier methods. However, it does support line continuation.

$FireworksStart = Get-Date $(
    ('July', '4,', '2017', '9:00:00', 'PM') -join ' '
)
$FireworksEnd = Get-Date "July $(
    '4,'
) 2017 9:45:00 PM"

As you can see, it can even be use to stretch a single line string over multiple lines.


Array Subexpression Operator: @( )

The Array Subexpression Operator returns an array from a list of expression. Most commonly, the array is a list of literals such as strings or integers. But, it can be a list of command statements.

$Names = @(
    'Bob'
    'Sue'
    'Joe'
    'Jane'
    'Yumiko'
    'Jose'
    'Yan'
    'Petr'
)
$Dates = @(
    Get-Date '2017/01/01'
    Get-Date '2016/01/01'
    Get-Date '2015/01/01'
    Get-Date '2014/01/01'
)

note that you do not need to include commas between elements and newlines will be interpreted as an element separator.

While a bit off topic, the cool thing about the array subexpression operator that separates it from the Subexpression and Grouping operators is that it always returns an array, even if it is empty. If you group statements that may not produce output in an Array Subexpression Operator, you are guaranteed to have an array. The others will return null if there was no output.

function NoOp {}
$Null -eq  (NoOp)
$Null -eq $(NoOp)
$Null -eq @(NoOp)

# Results:
True
True
False



HashTable Operator: @{ }

The HashTable Operator allows you to create a HashTable literal. This works very much like the Array Subexpression Operator with the exception that you must have a string key name followed by an equals sign and then the value for the key. The values can be literals or command statements.

$Movie = @{
    Title          = '君の名は。'
    RomanizedTitle = 'Kimi no Na wa.'
    LocalizedTitle = 'Your Name.'
    DateAdded      = Get-Date
}

Note that you do not need semicolons if you separate your key value pairs with newlines. it is also possible to use the = as a line continuator in the HashTable:

$Movie = @{
    Title =
        '君の名は。'
    RomanizedTitle =
        'Kimi no Na wa.'
    LocalizedTitle =
        'Your Name.'
    DateAdded =
        Get-Date
}



Comma Operator and Method Argument Separator: ,

The comma has several purposes in PowerShell but all of them can be used as a natural line continuator. I have already demonstrated two examples in the -split and -join section. But, here are a few more.

Lists:

$MonogatariSeries =
    $Kizumongatari,
    $Bakemonogatari,
    $Nekomonogatari,
    $Nisemonogatari,
    $SecondSeason,
    $Tsukimonogatari,
    $Owarimonogatari,
    $Koyomimonogatari

Method Arguments:

$SomeObject.SomeMethod(
    $Arugment1,
    $Argument2,
    $Argument3
)



Format Operator: -f

The Format Operator can be used to format strings. I use the format operator heavily as a means for string interpolation. Using the Format Operator as a line continuator  comes in handy when you are doing a large number of string insertions into a template

$EmailBody = $EmailTemplate -f
    $RecipientFirstName,
    $RecipientLastName,
    $AccountBalance,
    $DueDate,
    $LoginUrl

Again this makes use of the comma operator to break each substitution argument. Being able to break after the -f operator makes it a bit easier to read than if the first element is on the same line and the rest make use of the comma for line continuation.

The Format Operator can also accept an array variable. If you have a distaste for commas or if you want to move the format string variables into an array. You can do so In-line (the array starts right after the Format Operator) or external (using a previously defined array variable).

# In-line Array
$EmailBody = $EmailTemplate -f @(
    $RecipientFirstName
    $RecipientLastName
    $AccountBalance
    $DueDate
    $LoginUrl
)
# External Array
$EmailStrings = @(
    $RecipientFirstName
    $RecipientLastName
    $AccountBalance
    $DueDate
    $LoginUrl
)
$EmailBody = $EmailTemplate -f $EmailStrings



Index Operator, Casting Operator and Generic Type Enclosure: [ ]

The square brackets serve multiple purposes in PowerShell, but not all of them support natural line continuations. However, it does work in these situations.

With the Casting Operator:

$Rule = [
    Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.AvoidUsingConvertToSecureStringWithPlainText]::new()

It's important to note that the closing bracket must be on the same line as the type name.

The Index Operator is a bit more useful. You can use entire expressions inside the Index operators, but you would need to enclose the expressions in one of the  subexpression operators.

$QuoteOfTheDay = $QuotesArray[
    (get-date).DayOfYear
]
$Definition = $EnglishDictionary[
    'Pneumonoultramicroscopicsilicovolcanoconiosis'
]

It is a better practice store the calculations in a variable and use the variable as the index:

$DayOfTheYear = (get-date).DayOfYear
$QuoteOfTheDay = $Qoutes[$DayOfTheYear]
$Lookup = 'Pneumonoultramicroscopicsilicovolcanoconiosis'
$Definition = $EnglishDictionary[$Lookup]

The Generic Type Enclosure works similar to the Casting Operator, except the closing bracket can go after a newline. It is used in Generic Types to specify the type parameter(s) that will be consumed by the Generic Type.

$UserDictionary = [System.Collections.Generic.Dictionary[
    [System.Guid],
    [Microsoft.ActiveDirectory.Management.ADUser]
]]::new()

There are quite a few square brackets in that example. the ones I'm referring to as the Generic Type Enclosure are the open square bracket directly after Dictionary and the closing square bracket at the beginning of the last line. I think that is significantly easier to read than this:

$UserDictionary = [System.Collections.Generic.Dictionary[[System.Guid],[Microsoft.ActiveDirectory.Management.ADUser]]]::new()


ScriptBlock Operator: { }

The ScriptBlock Operator allows you to encapsulate multiple statements into a single unit of code.  Using the ScriptBlock Operator as a line continuator should be obvious to anyone who has done any kind of logic or looping in PowerShell. The opening curly brace in declared ScriptBlocks or those that are part of definitions such as functions, classes, DSC configuration, loops, If/Else, and Try/Catch/Finally can be used as a natural line continuator.

If ($True) {
    # Do things
}
While ($True) {
    # Do things
}
$ScriptBlock = {
    Get-Date
}
Class MyClass {
    [string]$Property
MyClass () {
        $This.Property = 'New'
    }
    MyClass ([string]$Property) {
        $This.Property = $Property
    }
}
$Numbers | ForEach-Object -Begin {
    $Multiplier = 5
    $Count = 0
} -Process {
    $_ * $Multiplier
    $Count++
} -end {
    Write-Verbose "Multiplied $Count numbers."
}

This is one of the reasons that Stroustrup and OTBS Indentation are popular in PowerShell. ScriptBlock Parameters must be opened on the same line as the parameter name or command but a newline after opening the ScriptBlock is supported. If you use other Indentation styles, you might run into some inconsistencies when dealing with ScriptBlock parameters vs ScriptBlocks which follow conditional expressions.


Grouping Operator, Conditional Expression Operator and Argument Enclosure: ( )

The open and close parenthesis serve several purposes in PowerShell. The Open Parenthesis always supports line continuation.

# Grouping
$Dates = (
    (Get-Date).AddDays(-1),
    (Get-Date).AddDays(1),
    (Get-Date).AddDays(2)
)
# Method Arguments
$SomeObject.SomeMethod(
    (Get-Date)
)
# Parameter Block
Param (
    # Attribute Properties
    [Parameter(
        Mandatory = $True,
        ValueFromPipeline = $True,
        ValueFromPipelineByPropertyName = $True
    )]
    [String]
    $ComputerName
)

When used as a Conditional Expression Operator, both the open and close parenthesis support line continuation. This allows for indentation styles like Allman, Whitesmiths, GNU, etc. which open curly braces on a new line after the conditional expression. An example of this is to modify the one I used for the Logical operators section and simply move the opening curly brace to a new line:

If (
    $Kizumongatari.Episodes.Count -lt $Bakemonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Nekomonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Nisemonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $SecondSeason.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Tsukimonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Owarimonogatari.Episodes.Count -and
    $Kizumongatari.Episodes.Count -lt $Koyomimonogatari.Episodes.Count
)
{
    'Kizumonogatari has the lowest episode count.'
}

Note the newlines after the open paren and close paren.

The reason I put this section right after the ScriptBlock Operator is to draw attention to the differences between Foreach and ForEach-Object. Both of these use the same cmdlet definition (Foreach is just an alias of ForEach-Object), however, opening the curly brace on a new line is only supported on the "traditional" Foreach. That is because the conditional expression allows for line continuation. When using ForEach-Object, the default ScriptBlock you supply is actually the -Process positional parameter. As discussed in the previous section, ScriptBlock parameters must begin on the same line as the parameter name.  In the the case of positional parameter they must be on the same line as the command or on the same line as the most recent parameter.

# This works
Foreach ($Object in $Objects)
{
    # Do Something  
}
# This Does Not Work
$Objects | ForEach-Object
{
    # Do Something
}



Property Dereference Operator: .

The dot character when used to access members such as Properties and Methods of an object also supports line continuation. This can be really helpful with Method Chaining or deep object properties.

$StringBuilder = [System.Text.StringBuilder]::New()
$Null = $StringBuilder.
    AppendLine('This is an example of a string builder').
    AppendFormat('It uses {0} to build strings.', 'Method Chaining').
    Append([System.Environment]::NewLine).
    AppendLine('There are quite a few "builder" classes that work this way.')
$StringBuilder.ToString()

As a fun example of a deep object structure:

$Continent = $MiltiVerse.
    Universes['Observable'].
    SuperClusters['Laniakea'].
    GalaxyGroups['Local Group'].
    Galaxies['Milky Way'].
    StarSystems['Solar System'].
    Systems['Earth-Moon'].
    Planets['Earth'].
    Continents['North America']



Static Member Operator: ::

The Static Member operator works like the Property Deference Operator except for static members (such as Static Properties, Static Methods, and Fields) of class instead of instance members of a class instance. Just like its instance counterpart, the Static Member Operator can also be used as a line continuator:

$Rule = [Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.AvoidUsingConvertToSecureStringWithPlainText]::
    New()

This would useful for a a long .NET class name and a static method that takes several parameters. I couldn't think of a good example or find one in my own code, this this is a fictitious example for the sake of demonstration.

$Result= [ApplicationNameSpace.ModuleNameSpace.FunctionalityNameSpace.SomeUsefulClass]::
    SomeUsefulStaticMethod($Argument1, $Argument2, $Argument3, $Argument4)



Range Operator: ..

The range operator Returns an array of integers between and including two values. I would caution against using this one as a line continuator. If you find yourself in a situation where a the left or right side of the range operator is rather long, it would be best to refactor the code by assigning the operations to variables and then using the range operator between those 2 variables.

2147483627..
    2147483647



Pipeline Operator: |

I saved this one for last because it is my favorite and one of the most useful line continuators in PowerShell. The Pipeline is an essential tool for PowerShell in both console and scripting. The Pipeline Operator allows you to pass the output objects of one command as input to another command. The only problem with the Pipeline is that it tends to make for very lengthy lines of code. Luckily, it can be used as a line continuator and it makes sense to use it as one. For example:

Get-Widget |
    Where-Object {$_.Height -gt 20 -and $_.Width -gt 20 -and -$_.Depth -gt 20} |
    Sort-Object -Property Price |
    Select-Object -First 100 |
    Select-Object -Property Name, Price, Height, Width, Depth, Description |
    Format-Table -AutoSize

This reads like a list of actions to take on Get-Widget, which is exactly what it is!


A Note on Splatting

Splatting is when you pass a collection of parameter values to a command using a HashTable or Array like this:

$Params = @{
    Height = 50
    Width  = 50
    Depth  = 50
    Name   = 'My Widget'
    ID     = '10dbe43f-0269-48b8-96cb-447a755add55'
}
New-Widget @Params

While splatting is not really a line continuator, it is often a far superior method of reducing line length than line continuators. This is not the only method to shorten line length without line continuators, but, it is an important one because it renders many line continuators unnecessary if used properly. They are also a great way to decouple parameters from commands. Parameters change more often than commands, even in complex pipelines. Using the demo in the Pipeline Operator section, here is how you can achieve greater flexibility by introducing splatting:

$GetWidgetParams = @{}
$WhereObjectParams = @{
   FilterScript = {$_.Height -gt 20 -and $_.Width -gt 20 -and -$_.Depth -gt 20}
}
$SortObjectParams = @{
    Property = 'Price'
}
$SelectObjectParms = @{
    First = 100
    Property = 'Name', 'Price', 'Height', 'Width', 'Depth', 'Description'
}
$FormatTableParams = @{
    AutoSize = $True
}
Get-Widget        @GetWidgetParams   |
    Where-Object  @WhereObjectParams |
    Sort-Object @SortObjectParams |
    Select-Object @SelectObjectParms |
    Format-Table  @FormatTableParams

I personally think this is much easier to maintain. It also makes it easier to see what is going on in the pipeline because the details of the command parameters have been abstracted away. Also, notice that the $GetWidgetParams HashTable is empty. You could always go back later and add parameters to it without having to touch the pipeline code.


Conclusion

When I first thought of making this blog post, I didn't realize it would be so long. I learned a few of these in the course of writing it. I think its clear there are many tools available to split up long lines of code across multiple lines.

My biggest goal was to use this a link to provide whenever I see backticks in PowerShell forums. Hopefully I have done a sufficient job providing all of the various methods for natural line continuation. If I missed one, please contact me so I can add it.

With that, I'd like to say "Farewell Backtick!"


Join the conversation on /r/PowerShell.

No comments: