Advanced Windows PowerShell Scripting Video Training

Advanced Windows PowerShell Scripting Video Training
Advanced Windows PowerShell Scripting Video Training

Thursday, May 5, 2016

Line Continuation in PowerShell – The Big Debate

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.


No comments: