Write PowerShell Scripts for YOU!

I’m often training administrators to use Windows PowerShell or offering guidance in a number of forums. As you might imagine I see a lot of code samples. Of course I help with the immediate challenge, but often I see opportunities where a few slight tweaks to the code layout can make a difference in readability and troubleshooting. Here’s what I’m talking about.

If you’ve been using PowerShell for a while or have heard me speak about it, you know there is no difference between running a set of PowerShell commands in the console or in a PowerShell script. The primary difference is that a script is “write once run many”. PowerShell offers all sorts of shortcuts and techniques to simplify the amount you have to type at a PowerShell prompt. That’s terrific and I encourage you to use them. However, in a script they don’t make sense.

First, aliases in the interactive shell make perfect sense. But in a script an alias doesn’t offer any benefits and actually can be counterproductive. If you are new to PowerShell or will be sharing your scripts with others, an expression like this can be rather cryptic.

gdr | ? {$_.used} | % {$_ | am Noteproperty Compressed ((gwmi win32_volume -f “driveletter=’$($_.Root.Substring(0,2))'”).Compressed) -pa} | Select Name,Compressed,Used,Free | ft -a

This is a one line PowerShell expression that may not make sense to you. Of course if you typed it out at a prompt as I did you’ll know what to expect. But not so in a script. The use of aliases makes this more complicated to understand and if you don’t understand, you can’t debug or troubleshoot. In fact, if you tried to copy and paste this snippet it will fail for you as well.  My expression is using a custom alias (am) for Add-Member. That’s one of the downsides to using aliases in a script, especially custom ones: it limits code sharing and reuse.

The better approach is to use full cmdlet and parameter names. There’s no performance penalty. PowerShell doesn’t care, but you should.

Get-PSDrive | Where-Object {$_.used} | ForEach-Object {$_ | Add-Member -MemberType -Name Noteproperty -Value Compressed ((Get-WmiObject win32_volume -f “driveletter=’$($_.Root.Substring(0,2))'”).Compressed) -passthru } | Select-Object Name,Compressed,Used,Free | Format-Table -autosize

Remember, the goal is to write scripts for you. If you had to troubleshoot, isn’t this line easier to understand than the previous incarnation?

I also see code samples like this:

$s=Get-Service
$a=@()
foreach ($t in $s) { if ($t.status -eq “running”) { Write-Host $t.displayname -fore Green ; $a+=$t} else { Write-Host $t.Displayname -fore Red } }

(my examples are meant to be illustrative not necessarily the best way to accomplish the task)

This code works and if I were to have to enter this at the console is reasonable. But in a script this again complicates things for you. This is a very brief example but I’ve seen scripts with 20 lines or more of this compact formatting. Again, PowerShell doesn’t care but this makes it harder for you.

First off, the variable names are essentially meaningless. In a short script that my not be too bad but in a longer script you might define $s at the beginning but when you are at the end of 200 line script, what was $s again?  Use more meaningful variable names.

The other issue is to layout constructs like Foreach and If so that they are easier to read. If they are easier to read, they will be easier to troubleshoot and debug. Where you place the curly braces is a matter of personal preference, but isn’t something like this easier to understand?

#get service information
$services=Get-Service
 
#initialize arrays
$running=@()
$stopped=@()
 
#enumerate the services
foreach ($service in $services) { 
  if ($service.status -eq "running") { 
    Write-Host $service.displayname -foregroundcolor Green
    
    #put each expression on a separate line
    #add the running service object to the array
    $running+=$service
    
    } #end If 
    
  else { 
      Write-Host $service.Displayname -foregroundcolor Red 
      
      #add the running service object to the array
      $stopped+=$service
      
    } #end Else
  } #end ForEach
  
Write-Host "$($running.count) running services" -foregroundcolor Green
Write-Host "$($stopped.count) stopped services" -foregroundcolor Red
 
#end of script

Yes, it might take a few extra minutes to type, but you only have to do it once and now you have a script written for you. Notice the more meaningful variable names, the use of comments, white space and indenting. (Please don’t ask me to help debug a 100 line script with no spacing and everything left justified.) I especially like adding comments to the end of script blocks like the ForEach construct. You’ll find this invaluable when you have a complex set of nested expressions and are trying to figure out which curly braces match.

I know we often approach scripting with an ad hoc mind set, but if you take the time to follow some of these suggestions, and that’s all they are, I think you’ll enjoy writing PowerShell scripts more and the scripts you produce will be easier to understand, troubleshoot and share.