A common point of confusion for IT Pros learning PowerShell
is “when can I press the Enter Key?”
Line continuation allows us to prevent our code for moving horizontally
off the screen. When someone is reading
your code and they must slide the horizontal scroll bar, well that is just
annoying. It makes your code less
readable. Let’s look at various
opportunities that you have to write less annoying code.
Let me start out with rule #1 when it comes to line
continuation:
Get your code to work first!!!
What this means is that you write it on one line before
trying to break it up. If you start
experimenting with how to break the line up without evening knowing if it
works, you will be generating a lot of very difficult to resolve errors. Get
your code to work first.
The Backtick
Let’s tackle the big one, the backtick. It is also known as the grave accent. Depending
on who you ask, they either love it or hate it. I personally love it, but only when
you use it consistently. Take a look at
this code.
New-ADFineGrainedPasswordPolicy -Name "Group1PSO" -Precedence 10 -ComplexityEnabled $True -Description "PSO for Group 1" -DisplayName"Group1PSO" -LockoutDuration "0.12:00:00" -LockoutObservationWindow "0.00:15:00" -LockoutThreshold 3 -MaxPasswordAge "10.00:00:00" -MinPasswordAge "1.00:00:00" -MinPasswordLength 8 -PasswordHistoryCount 10 -ReversibleEncryptionEnabled $False
This example code is 1 PowerShell cmdlet with its
parameters. Just in case you are interested:
PS C:\Users\JASON> 'New-ADFineGrainedPasswordPolicy -Name
"Group1PSO" -Precedence 10 -ComplexityEnabled $True -Description
"PSO for Group 1" -DisplayName"Group1PSO" -LockoutDuration
"0.12:00:00" -LockoutObservationWindow "0.00:15:00"
-LockoutThreshold 3 -MaxPasswordAge "10.00:00:00" -MinPasswordAge
"1.00:00:00" -MinPasswordLength 8 -PasswordHistoryCount 10
-ReversibleEncryptionEnabled $False'|
Measure-Object -Character -Word -IgnoreWhiteSpace
Lines Words Characters Property
----- ----- ---------- --------
29 346
346 characters. I’m
confident that this command will require horizontal scrolling if we do not
address the issue. The Backtick ( ` ) is
the PowerShell escape character. It is
not the single quote. On the US
keyboard, it is on the left hand side.
Do not confuse it with the single quote. The 2 draw backs that the
backtick has is that it is both very hard to see, and is easily confused with
the single quote. When I introduce its
usage in class, I am very clear that no one is to press anything on the right
hand side of the keyboard as we perform this exercise together. Unfortunately 9 times or of 10, someone does.
The backtick is acceptable to use if you use it in a consistent
fashion so your colleagues will clearly understand when and why you used
it. Here is an example of what the above
sample code looks like when the backtick is used properly:
New-ADFineGrainedPasswordPolicy -Name "Group1PSO" `
-Precedence 10 `
-ComplexityEnabled $True `
-Description "PSO for Group 1" `
-DisplayName "Group1PSO" `
-LockoutDuration "0.12:00:00" `
-LockoutObservationWindow "0.00:15:00" `
-LockoutThreshold 3 `
-MaxPasswordAge "10.00:00:00" `
-MinPasswordAge "1.00:00:00" `
-MinPasswordLength 8 `
-PasswordHistoryCount 10 `
-ReversibleEncryptionEnabled $False
Notice the nice clear alignment of the parameters. They are slightly indented, but left
aligned. With consistent use, I have no
issue with the backtick. Just remember these 4 rules.
1.
Use it in a consistent manor.
2.
Always proceed it with a space.
3.
Always press ENTER right after using it.
4.
Never add a backtick to the last line of a
command.
Splatting
A very common way to avoid the backtick is to use splatting. Splatting involves the usage of a hash
table. The hash table has two elements. The first is the name of the parameter and
the second is its value. Take a look:
$Params = @{
Name = "Group1PSO"
Precedence = 10
ComplexityEnabled = $True
Description = "PSO for Group
1"
DisplayName = "Group1PSO"
LockoutDuration = "0.12:00:00"
LockoutObservationWindow = "0.00:15:00"
LockoutThreshold = 3
MaxPasswordAge = "10.00:00:00"
MinPasswordAge = "1.00:00:00"
MinPasswordLength = 8
PasswordHistoryCount = 10
ReversibleEncryptionEnabled = $False}
New-ADFineGrainedPasswordPolicy @Params
We create a variable with a name of our choice. I called this one $Params. We set it equal to a hash table. In PowerShell, we start a hash table with @{ and
end it with } . Each
line contains a key/value pair. We need
to use the parameters of the cmdlet that we will be splatting to as our keys or
we will have an error. The first key/value pair in the example sets the value of
the “Name” parameter to “Group1PSO”. The
second Key/Value pair sets the “Precedence” parameter to a value of “10”
To use the splat, we call the cmdlet New-ADFineGrainPasswordPolicy and then provide the splat. Notice that we do not use the “$” when
providing the splat, only when creating it. We use the “@” and then the
variable name.
New-ADFineGrainedPasswordPolicy @Params
The Splat method has the advantage of no backtick!!! You will not be working with a hard to see
character.
The disadvantage, you need to know the parameters of the
cmdlet. When using the backtick, you
could still use TAB completion and the intellisense of the ISE. With splatting, you often need to write the
command out normally, and then cut and past the parameter names and values into
the hash table and then properly format them.
Using the Pipe Character
A key feature of PowerShell is its ability to chain together
a series of cmdlets into a working set of code.
We do this through piping. With
piping, we can significantly reduce our coding requirements.
Take a look at this code:
Get-EventLog -LogName
Security -InstanceId
4624 | Select-Object
-Property TimeWritten,
InstanceID, Message
| ConvertoTo-HTML | Out-File
-FilePath c:\ps\SecLog.html
This code is utilizing 4 commands. The problem is that it is requiring me to
horizontal scroll in the ISE. Here is
the same code where I pressed the ENTER key after each pipe.
Get-EventLog -LogName
Security -InstanceId
4624 |
Select-Object
-Property TimeWritten,
InstanceID, Message
|
ConvertoTo-HTML
|
Out-File -FilePath c:\ps\SecLog.html
The code is still considered a single line, just more
readable. I also add a TAB to each line
under the start of the first cmdlet in the piped statement. This is a visual cue to me that these lines
of code are part of the Get-EventLog piped
statement. That way I do not need to
look at the end of the line to see if they are piped.
Using Commas
Many parameters can have multiple values provided to
them. To discover which parameters can
accept multiple values, you need to look at the help file. Here is the syntax block from the help file
of Get-Service.
Get-Service [[-Name] []]
[-ComputerName []] [-DependentServices]
[-Exclude
[]] [-Include []] [-InformationAction {SilentlyContinue
| Stop | Continue |
Inquire | Ignore | Suspend}] [-InformationVariable
[]] [-RequiredServices] []
Take notice of the –Name parameter. Its data type is written as [String[]] The key element to look for is the “[]” That extra set of characters of open and
close braces right after the data type tells you that this parameter can receive
multiple values. Here is the command used without line continuation.
PS C:\> Get-Service -name Bits, WinRM, XboxNetApiSvc,
Vmms, Spooler
Status Name DisplayName
------ ---- -----------
Running Bits Background Intelligent Transfer
Ser...
Running Spooler Print Spooler
Running Vmms Hyper-V Virtual Machine
Management
Running WinRM Windows Remote Management
(WS-Manag...
Stopped
XboxNetApiSvc Xbox Live
Networking Service
And here it is with line continuation.
Get-Service -name Bits,
WinRM,
XboxNetApiSvc,
Vmms,
Spooler
A better example may be made when using Select-Object, which generally produces some very long commands.
Get-ADComputer –Filter
* |
Select-Object -Property
DistinguishedName, SID, SamAccountName,
Enable, @{n='ComputerName';e={$PSItem.Name}}
This one is getting out of hand. We can press ENTER after any of the commas
from the argument list of Select-Object’s –Property parameter
Get-ADComputer –Filter
* |
Select-Object -Property
DistinguishedName,
SID,
SamAccountName,
Enable,
@{n='ComputerName';e={$PSItem.Name}}
Using Periods
When we are using methods, we can actually separate them on
multiple lines.
First off, let’s get some data to work with.
$Data = Get-EventLog
-LogName Security
-Newest 1 -InstanceId 4634 |
Select-Object
-ExpandProperty Message
This will store the message component of an event in the
variable $Data. Go ahead and look at the
contents of this variable. It is 1
continuous string. This is actually one
of the exercises that I do in my PowerShell class on the final day. The objective is to get the Account Name
listed on the 5th line. Here
is the code written on one line using the methods of the System.String object.
($Data.Split("`n"))[4].Remove(0,(($Data.Split("`n"))[4].LastIndexOf(":")+1)).Trim().Replace("$",$null)
There are much simpler ways to solve this problem. I choose
this way because it is long. Here is the same code with the methods separated on
different line.
($Data.Split("`n"))[4].
Remove(0,(($Data.Split("`n"))[4].
LastIndexOf(":")+1)).
Trim().
Replace("$",$null)
If this is more readable or not is a subject of debate, but
it does show you another way to prevent horizontal scrolling. If you really wanted to push it a bit too
far:
($Data.
Split("`n"))[4].
Remove(0,
(($Data.
Split("`n"))[4].
LastIndexOf(":")+1)).
Trim().
Replace("$",
$null)
No, I would not actually write my code to look like this.
Hash Tables with
Select-Object and Format-Table
We can also provide for line continuation when creating
custom properties with Select-Object
and custom columns with Format-Table.
Take a look at this code:
Get-Process | Format-Table
-Property Name,
CPU, @{Name = "VMWS";
Expression = {$_.VM + $_.WS}; align="Left"}
You can inject some carriage returns after each element of
the hash table.
Get-Process | Format-Table
-Property Name,
CPU,
@{
Name = "VMWS"
Expression = {$_.VM
+ $_.WS}
align="Left"
}
You can also remove the semi-colon from the hash table when
the elements are on different lines, or leave them in.
Comparison Operators
Comparison operators gives us another opportunity to break
our long lines of code into multiple lines.
Consider this code:
Get-Volume |
Where-Object
-filter {$_.SizeRemaining
-gt 0 -and $_.SizeRemaining
/ $_.Size -lt .99
-and $_.FileSystemLabel -ne ""}
Add a carriage return after each logical operator.
Get-Volume |
Where-Object
-filter {$_.SizeRemaining
-gt 0 -and
$_.SizeRemaining / $_.Size
-lt .99 -and
$_.FileSystemLabel -ne ""}
It also works after the comparison operators, but it may be
a bit unreadable if you do.
Get-Volume |
Where-Object
-filter {$_.SizeRemaining
-gt
0 -and
$_.SizeRemaining / $_.Size
-lt
.99 -and
$_.FileSystemLabel -ne
""}
As the debate rages on, there is a request on Microsoft
Connect to allow for a syntax change to help remove the backtick (https://connect.microsoft.com/PowerShell/feedback/details/786717/reduce-the-need-for-line-continuation-use-in-powershell-commands-that-span-multiple-lines)
and provide for a different syntax for line continuation using the pipe
character. Below will not work, but it
is what is proposed.
Original
Get-EventLog -LogName
Security -InstanceId
4624 |
Select-Object
-Property TimeWritten,
InstanceID, Message
|
ConvertoTo-HTML
|
Out-File -FilePath c:\ps\SecLog.html
Proposed
Get-EventLog -LogName
Security -InstanceId
4624
| Select-Object
-Property TimeWritten,
InstanceID, Message
| ConvertoTo-HTML
| Out-File -FilePath c:\ps\SecLog.html
The proposed syntax change would allow for the pipe to be
placed at the beginning of the next line, as opposed to the end.
Choose which method you will be using, but make it a team
decision. In the end, your goal is to make your code readable.
Comments