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
- Intro
- Table of Contents
- Size Matters: Why Line Length is Important
- Blasted Backtick Begone!
- What is the Backtick in PowerShell?
- How Does the Backtick Allow for Line Continuation?
- So, Why is That Such a Bad Thing?
- When Is It OK to Use The Backtick?
- What are Natural Line Continuations and Natural Line Continuators?
- Arithmetic Operators: + - * / % -shl -shr
- Assignment Operators: = += -= *= /= %= ++ --
- Comparison Operators: -eq -ne -gt -lt -le -ge -match -like -in -contains -replace
- Logical Operators: -and -or -xor -not !
- -split and -join Operators
- Type Operators: -is -isnot -as
- Subexpression Operator: $( )
- Array Subexpression Operator: @( )
- HashTable Operator: @{ }
- Comma Operator and Method Argument Separator: ,
- Format Operator: -f
- Index Operator, Casting Operator and Generic Type Enclosure: [ ]
- ScriptBlock Operator { }
- Grouping Operator, Conditional Expression Operator and Argument Enclosure: ( )
- Property Dereference Operator: .
- Static Member Operator: ::
- Range Operator: ..
- Pipeline Operator: |
- A Note on Splatting
- Conclusion
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:
But is actually this:
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:
or
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.
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.
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?
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:
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…
and this…
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:
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:
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
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.
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:
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:
That will return 0, but it does demonstrate line breaking after each operation. As another example:
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.
Whereas this will have an error:
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
+= is pretty handy as well
This is also helpful when you have a deep property or long variable name to update with a long command as well:
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.
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:
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
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.
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:
Compare that to this:
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.
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.
This would be better if the split delimiter, maximum substrings, and split option were stored in variables and called like this:
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.
Type Operators: -is -isnot -as
Type Operators evaluate and transform object types.
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.
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.
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.
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.
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:
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:
Method Arguments:
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
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).
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:
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.
It is a better practice store the calculations in a variable and use the variable as the index:
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.
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:
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.
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.
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:
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.
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.
As a fun example of a deep object structure:
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:
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.
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.
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:
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:
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:
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.