Don't Write Scripts, Write PowerShell Functions

Don't Write Scripts, Write PowerShell Functions

  • Comments 3
  • Likes

Summary: There are times when piling Windows PowerShell cmdlets together becomes a bit cumbersome, so instead of writing a script, consider functions.

Weekend Scripter

Microsoft Scripting Guy, Ed Wilson, is here. There are times, when I want to do things that are a bit more complicated than piecing together cmdlets in a straightforward manner. In yesterday’s Hey! Scripting Guy blog, I talked about not needing to write Windows PowerShell scripts. I illustrated piping cmdlets together, and working in an interactive fashion.

I actually had the idea for this series while I was speaking at the TechStravaganza in Atlanta, Georgia a month or so ago. I told them, that I was making an announcement to ITPros that it was time to quit writing scripts. Here is a picture taken of me while I was speaking on that occasion. In the picture, you can see Mark Schill, president of the Atlanta PowerShell User Group on the left, and Mark Minassi sitting in the front row center.

Photo of Ed speaking

In the old days, I might very well whip out a script editor and bang out a simple Windows PowerShell script. This was especially true in the Windows PowerShell 1.0 days. For example, consider a common need: to view information about disk drives on a local or remote computer. The information I normally need is things like the capacity of the drive and the amount of free space. In addition, I like to know the percentage of free space on the drive because it gives me a “feel” for how rapidly my drive space is diminishing over time. Anyway, such a script is the DiskDriveScript.ps1 Windows PowerShell script. It is shown here.

DiskDriveScript.ps1

$drive = "c:"

$computername = "."

 

 Foreach($d in $drive)

 {

  Get-WmiObject -Class win32_Volume -ComputerName $computername `

      -Filter "DriveLetter = '$d'" |

  Format-table -autosize -property DriveLetter, Label,

  @{Name = "ComputerName"; Expression = {$_.__Server} },

  @{Name = "Capacity(GB)"; Expression = {$_.capacity / 1GB} },

  @{Name = "FreeSpace(GB)"; Expression = {$_.Freespace / 1GB} },

  @{Name = "PercentFree"; Expression = { ($_.FreeSpace / $_.Capacity)*100 } }

 } # end foreach

The DiskDriveScript.ps1 is not a bad script. It has default values for the disk drive and the computer name. It will accept an array of drive letters, and it creates a nice custom formatted table of output. In addition, it calculates the percent of free space, and it displays the disk space in gigabytes, which is a number that is more likely to be meaningful with today’s drive sizes.

However, there are a number of problems with the DiskDriveScript.ps1 script. The main problem is that it is pretty much single purpose. Although I can call the script and redirect the output to a text file, that is about as far as I can go with the script. One reason is that it outputs data in a table. When the WMI Win32_Volume object hits the Format-Table cmdlet it changes from a management object to a series of format-specific objects. Once a Format-Table, Format-List, or Format-Wide cmdlet enters the equation, it becomes the end of the pipeline—nothing else can follow. In creating the table from within the DiskDriveScript.ps1 script, any potential code reuse is no longer possible.

A more useful approach, and one that takes very little additional work, creates an advanced function instead of a single purpose script. An advanced function does require comment-based Help, but because comment-based Help is so easy to add (especially by using my Add-Help add on to the Windows PowerShell ISE), and because it adds so much value to your function (by explaining it and illustrating potential ways to use the function), I pretty much feel that it is nearly a requirement for an advanced function. This is why I emphasized comment-based Help during the 2011 Scripting Games.

The big change from the DiskDrive.ps1 script was real simple—I changed Format-Table to Select-Object. That is it. That simple change converts the script from a single purpose, limited-use script into a script that emits an object that is available for nearly an infinite amount of code reuse.

For example, suppose I am interested in the amount of free space I have on the various drives that are connected to my computer. I can use the Get-DiskDrive function to retrieve drive information, and then pipe the output to the Sort-Object cmdlet. This command is shown here.

Get-DiskDrive -d c:,e:,g: | Sort-Object -Property percentFree

The command and its associated output are shown in the following image.

Image of command output

Perhaps I am interested in seeing information about how my drives are formatted. I can group the drives, based on the FileSystem property. This command and its associated output are shown here.

PS C:\Users\ed> Get-DiskDrive -d c:,e:,g: | Group-Object -Property FileSystem -NoElement

 

Count Name                    

----- ----                    

    2 NTFS                    

    1 FAT32                   

If I want a bit more information, I remove the NoElement parameter. The command and output associated with the revised command are shown here.

PS C:\Users\ed> Get-DiskDrive -d c:,e:,g: | Group-Object -Property FileSystem

 

Count Name                      Group                                                      

----- ----                      -----                                                      

    2 NTFS                      {@{DriveLetter=C:; Label=; FileSystem=NTFS; PageFilePrese...

    1 FAT32                     {@{DriveLetter=G:; Label=TESTFORMAT; FileSystem=FAT32;

Maybe I need to know where the page file resides so I can optimize my system. Well, to do that, I add a Where-Object as shown here.

PS C:\Users\ed> Get-DiskDrive -d c:,e:,g: | where { $_.pagefilepresent }

 

DriveLetter     : C:

Label           :

FileSystem      : NTFS

PageFilePresent : True

ComputerName    : NEWMRED

Capacity(GB)    : 119.142574310303

FreeSpace(GB)   : 72.1981544494629

PercentFree     : 60.5981152139833

But, maybe I need to know the percentage of free space, the file system, and the drive letter. I add a Select-Object command as shown here.

PS C:\Users\ed> Get-DiskDrive -d c:,e:,g: | where { $_.pagefilepresent } | select DriveLetter, Filesystem, percentFree

 

DriveLetter                           FileSystem                                                      PercentFree

-----------                           ----------                                                      -----------

C:                                    NTFS                                                       60.5981152139833

If I want to do so, I can even use WMI to retrieve a list of all the drives on the computer that are fixed local hard disk drives, and pipe that information to the Get-DiskDrive function. This command is shown here (the % character is an alias for the ForEach-Object cmdlet).

gwmi win32_logicaldisk -filter "drivetype = '3'" | % {Get-DiskDrive -drive $_.deviceID }

The complete Get-DiskDrive advanced function is shown here.

Function Get-DiskDrive

{

  <#

   .Synopsis

    This function returns capacity and freespace in gigs, and percent free

   .Description

    This function returns capacity and freespace in gigs, and percent free. By

    default it returns the system drive (normally drive c:)

   .Example

    Get-DiskDrive

    Returns capacity and free space in gigabytes. It also returns percent free,

    and the drive letter and drive label of the system drive on the local machine.

   .Example

    Get-DiskDrive -drive e: -computer berlin

    Returns capacity and free space in gigabytes of the e: drive. It also returns

    percent free, and the drive letter and drive label of the system drive on the

    remote machine named berlin.

   .Example

    Get-DiskDrive -drive e: -computer berlin, munich

    Returns capacity and free space in gigabytes of the e: drive. It also returns

    percent free, and the drive letter and drive label of the system drive on two

    remote machines named berlin and munich.

   .Example

    Get-DiskDrive -drive c:, e: -computer berlin, munich

    Returns capacity and free space in gigabytes of the C: and e: drive. It also

    returns percent free, and the drive letter and drive label of the system drive

    on two remote machines named berlin and munich.

   .Example

    "c:","d:","f:" | % { Get-DiskDrive $_ }

    Returns information about c, d, and f drives on local computer.

   .Example

    Get-DiskDrive -d "c:","d:","f:"

    Returns information about c, d, and f drives on local computer. Same command

    as the previous example - but easier to read. But on my computer this is a

    bit slower than the previous command (40 milliseconds).

   .Parameter drive

    The drive letter to query.  Defaults to system drive (normally c:)

   .Parameter computername

    The name of the computer to query. Defaults to local machine.

   .Notes

    NAME:  Example-

    AUTHOR: ed wilson, msft

    LASTEDIT: 06/02/2011 16:12:08

    KEYWORDS:

    HSG: HSG-06-26-2011

   .Link

    Http://www.ScriptingGuys.com/blog

 #Requires -Version 2.0

 #>

 Param(

  [string[]]$drive = $env:SystemDrive,

  [string[]]$computername = $env:COMPUTERNAME

 ) #end param

 Foreach($d in $drive)

 {

  Get-WmiObject -Class win32_Volume -ComputerName $computername -Filter "DriveLetter = '$d'" |

  Select-object DriveLetter, Label, FileSystem, PageFilePresent,

  @{Name = "ComputerName"; Expression = {$_.__Server} },

  @{Name = "Capacity(GB)"; Expression = {$_.capacity / 1GB} },

  @{Name = "FreeSpace(GB)"; Expression = {$_.Freespace / 1GB} },

  @{Name = "PercentFree"; Expression = { ($_.FreeSpace / $_.Capacity)*100 } }

 } # end foreach

 

} #end function get-diskdrive

The complete Get-DiskDrive function is uploaded to the Scripting Guys Script Repository. This makes it easy to copy the function without worrying about getting transient HTML goop embedded in the code.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hey Ed,

    that's true and something we learned from SG2011:

    We are better off, if we return objects from (advanced) functions to keep all options open!

    Terminating the pipeline with a format cmdlet might be fine in special, rare occasions, but

    mostly this is something that results in highly specialized custom functions that maybe

    useless in other similar occasions and we end up writing dozens of alike functions for very

    special purposes!

    We can do better!

    Klaus

  • great post

  • Do not write scripts, write modules.

    Author of the psget.net