Windows PowerShell: Whitespace, Please

Proper formatting, including a little whitespace here and there, can make your Windows PowerShell commands a heck of a lot easier to understand.

Don Jones

Take a look at the following function and tell me what you think it will do. This is a real, fully functional “advanced function.” Your goal is to simply tell me (without actually running it) what it’s supposed to do:

function Get-PCInfo { [CmdletBinding()] param( [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)][string[]]$computername ) PROCESS { Write-Verbose "Beginning PROCESS block" foreach ($computer in $computername) { Write-Verbose "Connecting to $computer" try {$continue = $true $cs = Get-WmiObject -EV mybad -EA Stop -Class Win32_computersystem -ComputerName $computer } catch {$continue = $false $computer | Out-File -FilePath oops.txt -append Write-Verbose "$computer failed" $mybad | ForEach-Object { Write-Verbose $_ } }if ($continue) { $proc = Get-WmiObject win32_processor -ComputerName $computer | select -first 1 $obj = new-object -TypeNamePSObject $obj | add-member NotePropertyComputerName $computer $obj | add-member NotePropertyProcArchitecture $proc.addresswidth $obj | add-member NoteProperty Domain $cs.domain $obj | add-memberNotePropertyPCModel $cs.model $obj.psobject.typenames.insert(0,'MyPCInfo') write-output $obj }}}}

It’s difficult to determine, isn’t it? That’s because this function displays an all-too-common fault of scripts and examples you’ll find on the Internet, and in many of your own folders. It doesn’t use whitespace. Now consider this revised example:

function Get-PCInfo { [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [string[]]$computername ) PROCESS { Write-Verbose "Beginning PROCESS block" foreach ($computer in $computername) { Write-Verbose "Connecting to $computer" try { $continue = $true $cs = Get-WmiObject -EV mybad -EA Stop ` -Class Win32_computersystem ` -ComputerName $computer } catch { $continue = $false $computer | Out-File -FilePath oops.txt -append Write-Verbose "$computer failed" $mybad | ForEach-Object { Write-Verbose $_ } } if ($continue) { $proc = Get-WmiObject win32_processor ` -ComputerName $computer | select -first 1 $obj = new-object -TypeNamePSObject $obj | add-member NotePropertyComputerName $computer $obj | add-member NotePropertyProcArchitecture $proc.addresswidth $obj | add-member NoteProperty Domain $cs.domain $obj | add-memberNotePropertyPCModel $cs.model $obj.psobject.typenames.insert(0,'MyPCInfo') write-output $obj } } } }

Ah, that’s much easier to follow. Commands within a {construct} are indented. Long commands are nicely wrapped. It’s much more readable.

It’s so much easier to help someone debug a script when it’s nicely formatted. Just try to find a single misplaced or missing curly bracket when there’s nothing intended. You could go blind. The trouble is that a lot of you don’t realize how to achieve this kind of formatting. Let’s solve that problem right now.

Indenting

Most script editors work great with the Tab key, including the free Windows PowerShell ISE included with the Windows PowerShell version 2 installer. Some may insert four or five spaces instead of an actual tab character, but that’s fine. Most let you highlight a block of commands and press Tab to indent them all, or Shift+Tab to unindent them.

That’s convenient, and it encourages you to keep your code looking clean by making it easy to do so. Most editors will also maintain your indent, meaning that when you finish typing a line and hit Enter, the cursor starts the next line at the same indent position.

Line Breaking

This is a much-less-obvious trick, especially in Windows PowerShell scripts. The idea is to try and keep lines from wrapping horizontally unless absolutely necessary. Windows PowerShell actually lets you break a line after any character that tells the shell “there’s more coming,” including:

  • Commas
  • Semicolons
  • Pipes

Look at the “improved” example to see what I mean.

You can also use the Windows PowerShell escape character: the backtick (`). If you immediately follow a backtick with a carriage return, it “escapes” that carriage return. This forces Windows PowerShell to treat the following physical line as part of the same logical line. In other words, it’s kind of a line-continuation character. I hate this one, though. It’s hard to see on-screen and in print, and it’s easy to confuse for a dead pixel on your monitor or a stray piece of toner on the page.

It’s also easy to misuse. If you follow the backtick with a space, tab or another hard-to-see whitespace character, then it won’t act as a line-continuation character. It won’t escape a carriage return. I’ve used it a couple of times in the “improved” example, just so you could see it. Feel free to not use that technique if there’s a better way.

Consistency

Consistent formatting is important. For example, I prefer to do constructs like this:

if ($continue) { # some code goes here }

While others like this formatting better:

if ($continue) { # some code goes here }

Use whichever format you like, but pick one and stick with it. The goal is to make your code consistent and easy to read.

Go Back and Reformat

Dig out some of those ugly old scripts you’ve been running and spend a few minutes reformatting them. You’ll be doing a great kindness for whatever poor person has to come along in six months and figure out what you were thinking. Besides, the odds are good that “poor person” will be you.

Don Jones

Don Jones is a Microsoft MVP Award recipient and author of “Learn Windows PowerShell in a Month of Lunches” (Manning Publications Co., 2010), a book designed to help any administrator become effective with Windows PowerShell. Jones also offers public and on-site Windows PowerShell training. Contact him through his Web site at ConcentratedTech.com.