Tip

Tip: Focus Your Scripts and Functions

The PowerShell paradigm is the task based cmdlet.  With cmdlets that surface a single function that handle a multitude of inputs, a PowerShell session or script can read back like a sentence. 

Get-Process –Name iexplore | Where-Object {$_.WorkingSet –gt 50000000 } | Stop-Process

In the above (often overused, but illustrates the point well) example, Get-Process, Where-Object, and Stop-Process all accurately describe the task that they perform.

We can take this same approach with our scripts and functions.  By keeping your scripts and functions focused on performing discreet tasks, you keep with the composable nature of PowerShell and provide yourself the most flexibility in reusing the functions you create.

One of the most common “errors” I see in PowerShell scripts that are shared is the embedding of formatting in the output of the script.  If your script generates output, it should be in the form of objects (custom or otherwise).  (The only caveat here is if your script’s functionality is to handle the output of data, in which case it’s sole focus should be the handling that output.)

The PowerShell ecosystem provides a number of tools for formatting the output of your scripts, whether you want to display your scripts in the console window (Format-List, Format-Wide, Format-Custom, Format-Table), as a web page (ConvertTo-HTML), sent to a CSV file (Export-CSV) or XML (Export-CLIXML), in Version 2 to a sortable, groupable grid (Out-GridView), as well as a number of other options (Visifire graphs via PowerBoots, Excel, PDF, or Image via the Reporting Services Redistributable, or as a netmap using NodeXL).

Take advantage of these, or write your own.  If you write your own, it should work with custom objects, to keep with the composable nature of PowerShell.

Tip: Keeping Your Scripts DRY.

DRY?  Does this mean I can’t use PowerShell on a water-cooled PC?

DRY is a principle that can be very familiar to the PowerShell aficionado with a development background.  DRY means Don’t Repeat Yourself.  Keeping your scripts DRY means that our scripts don’t contain repeated code.  Copy/Paste is not your friend!

Why should PowerShell scripters care about keeping their PowerShell DRY?  One major reason – script maintainability.

PowerShell has a huge advantage over scripting environments/shells in that the noun/verb structure lends itself to very readable scripts.  If there is duplication in your code, that readability can give a PowerShellers a bit of overconfidence when reading/modifying scripts that are new to them or that have not been looked at in a while.

If you find yourself copying and pasting lines of PowerShell between different sections of your script, you could be setting yourself up for some interesting times when you need to make a change.

Tip: Calculated Properties

I really enjoy Jeffery Hicks’s Prof. PowerShell column.  In two of his recent columns (Perfect Properties and Label Me Perfect), Jeffery goes in to “calculated properties”.  

A calculated property is a hashtable passed to the Select-Object or Format-* cmdlets as one of the properties to return.

Here is an example from Label Me Perfect:

PS C:\> get-wmiobject Win32_logicaldisk -filter “drivetype=3″ `
-computer “mycompany-dc01″ | Format-table DeviceID,VolumeName,`
@{label=”Size(MB)”;Expression={$_.Size/1mb -as [int]}},`
@{label=”Free(MB)”;Expression={$_.FreeSpace/1MB -as [int]}},`
@{label=”PercentFree”;Expression={
“{0:P2}” -f (($_.FreeSpace/1MB)/($_.Size/1MB))}} -autosize

That is a whopper to type at the command line or to try to read in a script.

In order to increase readability and resuse, you can assign those hash tables to a variable and use that variable as one of the parameters to Select-Object or one of the Format-* cmdlets.

PS C:\> $Size = @{label=”Size(MB)”;Expression={$_.Size/1mb -as [int]}}
PS C:\> $FreeMB = @{label=”Free(MB)”;Expression={$_.FreeSpace/1MB -as [int]}}
PS C:\> $PercentFree = @{label=”PercentFree”;Expression={“{0:P2}” -f (($_.FreeSpace/1MB)/($_.Size/1MB))}}

PS C:\> get-wmiobject Win32_logicaldisk -filter “drivetype=3″ `
-computer “mycompany-dc01″ | Format-table DeviceID,VolumeName, `
$Size, $FreeMB, $PercentFree -autosize

This makes it a bit easier to read, and also makes those hash tables available for further use in additional statements without needing to retype them.

 

PSMDTAG: FAQ: Calculated Properties

Tip: Free Up Some Memory

I’m often running too many applications on my workstation, and at least one session of  PowerShell is almost always open.  I’m regularly jumping back to a PowerShell prompt to try something out, query something from my database, or complete a task. 

If I don’t clear out the variables that reference objects I’m done with, the amount of memory used by my PowerShell session increases dramatically, which impacts some other applications that I run.  (I know, memory is cheap.. buy more memory.  I’ve got a limited budget and I have to prioritize.)

So I’ve written a couple of functions that can help me clear out the extraneous variables and free up some of that memory being used by PowerShell.

The first function (and I’m open to suggestions on the naming here..), Set-SessionVariableList, reads in all the variable names in the global scope and saves them to the AppDomain (so I don’t lose them when I clear out variables).  I recommend running this shortly after starting PowerShell or running it from your profile.

Later, when I’m feeling some memory pressure, I can run the second function Remove-NewVariable, which calls Remove-Variable for all variables whose names I did not save in the AppDomain.  This removes the references to the objects they represented. 

Info Overload: Notice that I said that it removed the references to those objects.  Those objects still exist in memory and remain there until the CLR “garbage collects” them, releasing that memory.  The CLR has a process to reclaim that “used” memory when there is a need to reuse it or when it is impacting other performance.

Even if you skipped the previous bold section (which is perfectly acceptable), you might have noticed that when you clear out a variable, your memory usage for PowerShell might remain high for a while.  My function speeds up the natural “garbage collection” process by calling [System.GC]::Collect(), forcing the CLR to reclaim that memory.

There are a couple of “helper” functions included, Get-SessionVariableList, which will list out the variable names saved, in case you wanted to see which variables were going to be left and Add-SessionVariable, which adds more values to the current list. 

 

Feedback and improvements are always welcome!

PSMDTAG:FAQ Memory

Tip: Passing Parameters Revisited

In Jeff Hicks’s Prof. PowerShell series, he continues with his tips on working with functions.  He uses the Check-Service function that was in the previous article and that I provided another option in this post.  Jeff describes how you can make a function act more like a cmdlet.

The problem that you run into in V1 of PoweShell is that it is easy to make your functions work with the pipeline

Function Check-Service {
  Param([string]$service=”spooler” )
  PROCESS
  {
    $wmi=get-wmiobject win32_service -filter “name=’$service’” -computername $_ 
    if ($wmi.state -eq “running”)  {
      write $True
    }
    else {
      write $False
    }
  }
}
 
Get-Content servers.txt | Check-Service

OR work well with parameters being passed in.

Function Check-Service {
  Param([string[]]$server=$env:computername, [string]$service=”spooler” ) 
    $wmi=get-wmiobject win32_service -filter “name=’$service’” -computername $server 
    if ($wmi.state -eq “running”)  {
      write $True
    }
    else {
      write $False
    }

  
Check-Service –Server Server01, Server02, Server03

Advanced functions in V2 of PowerShell can alleviate this problem (a topic for a whole new post), but a workaround I’ve found for V1 is to use the Begin block to take certain command line parameters and pass them back into the function via the pipeline.

Function Check-Service {
Param([string[]]$server=$null,[string]$service=”spooler” )
  BEGIN
  {
    if ($server -ne $null)
    {
      $server | Check-Service –Service $service
    }
  }
  PROCESS
  {
    if ($_ -ne $null)
    {
      $wmi=get-wmiobject win32_service -filter “name=’$service’” -computername $_
      if ($wmi.state -eq “running”) {
        write $True
      }
      else {  write $False  }
    }
  }
}

This enables both of the above scenarios:

Get-Content servers.txt | Check-Service

Check-Service –Server Server01, Server02, Server03

PSMDTAG:FAQ param

PSMDTAG:FAQ pipeline

Updated: (Missed a little recursion bug.. thanks Aleksandar!)

Tip: Passing Parameters

Jeffery Hicks recently talked about the Param statement in his Prof. Powershell column for MCP Magazine (Keeping Your Options Open).  If you are new to PowerShell, I would definitely recommend reading the article, as provides a nice introduction to the Param statement. 

Jeffrey recommended to cast your parameter as the type of object that you are expecting, as way to catch errors.  I wholeheartedly agree.  That can cause trouble though, if you want to allow consumers of your script to pass in one or more values for that parameter.  In the spirit of the article’s title, I thought I would share a method I’ve come across to handle that situation.

Jeffrey gave is the below function.

Function Check-Service {
  Param([string]$computername=$env:computername,
    [string]$service=”spooler” )
  $wmi=get-wmiobject win32_service -filter “name=’$service’” -computername $computername
  if ($wmi.state -eq “running”)  {
    write $True
  }
  else {
    write $False
  }
}

Now, you can easily see where you might want to check for a service on multiple computers, but as this function stands, we would have to call it one time for each computer we want to check.

We can modify this script to take one or more computer names simply by changing the cast for the $computername variable from [string] to [string[]].  That changes makes our variable hold an array of strings, rather than just one. 

We don’t have to change the default value, as PowerShell handles the details of converting that value to an array. 

Since the –ComputerName parameter for Get-WMIObject can take an array of computer names, we don’t have to change much else in the function. 

I do add a foreach loop for the reporting end of the script, so we can see which machines have the service running.

And I end up with this (my changes in bold):

Function Check-Service

{

  Param( [string[]]$computername=$env:computername, [string]$service=”spooler” )

  $wmi=get-wmiobject win32_service -filter “name=’$service’” -computername $computername

  foreach ($one in $wmi)

  {

    if ($one.state -eq “running”)

    {

      write $one.SystemName, $true

    }

    else

    {

      write $one.SystemName, $False

    }

  }

}

 

I almost forgot

PSMDTAG:FAQ param

Get Adobe Flash playerPlugin by wpburn.com wordpress themes