20 Improving your parameterized script

In the previous chapter, we left you with a pretty cool script that had been parameterized. The idea of a parameterized script is that someone else can run the script without having to worry about or mess with its contents. Script users provide input through a designated interface—parameters—and that’s all they can change. In this chapter, we’re going to take things a bit further.

Heads UP Just a reminder that this chapter is very Windows focused as far as the examples are concerned.

20.1 Starting point

Just to make sure we’re on the same page, let’s agree to use listing 20.1 as a starting point. This script features comment-based help, two input parameters, and a command that uses those input parameters. We’ve made one minor change since the previous chapter: we changed the output to be selected objects, rather than a formatted table that we used in section 19.4.

Listing 20.1 Starting point: Get-DiskInventory.ps1

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or
more computers.
.DESCRIPTION
Get-DiskInventory uses CIM to retrieve the Win32_LogicalDisk
instances from one or more computers. It displays each disk's
drive letter, free space, total size, and percentage of free
space.
.PARAMETER computername
The computer name, or names, to query. Default: Localhost.
.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation
for values. 3 is a fixed disk, and is the default.
.EXAMPLE
Get-DiskInventory -ComputerName SRV02 -drivetype 3
#>
param (
  $computername = 'localhost',
  $drivetype = 3
)
Get-CimInstance -class Win32_LogicalDisk -ComputerName $computername `
 -filter "drivetype=$drivetype" |
 Sort-Object -property DeviceID |
 Select-Object -property DeviceID,                                       
     @{label='FreeSpace(MB)';expression={$_.FreeSpace / 1MB -as [int]}},
     @{label='Size(GB)';expression={$_.Size / 1GB -as [int]}},
     @{label='%Free';expression={$_.FreeSpace / $_.Size * 100 -as [int]}}

Notice the Select-Object as opposed to Format-Table we used in chapter 19.

Why did we switch to Select-Object instead of Format-Table? We generally feel it’s a bad idea to write a script that produces preformatted output. After all, if someone needed this data in a CSV file, and the script was outputting formatted tables, that person would be out of luck. With this revision, we can run our script this way to get a formatted table:

PS C:> .Get-DiskInventory | Format-Table

Or we could run it this way to get that CSV file:

PS C:> .Get-DiskInventory | Export-CSV disks.csv

The point is that outputting objects (which Select-Object does), as opposed to formatting displays, makes our script more flexible in the long run.

20.2 Getting PowerShell to do the hard work

We’re going to turn on some PowerShell magic by adding just one line to our script. This technically turns our script into an advanced script, which enables a whole slew of useful PowerShell capabilities. The following listing shows the revision.

Listing 20.2 Making Get-DiskInventory.ps1 an advanced script

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or
more computers.
.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk
instances from one or more computers. It displays each disk's
drive letter, free space, total size, and percentage of free
space.
.PARAMETER computername
The computer name, or names, to query. Default: Localhost.
.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation
for values. 3 is a fixed disk, and is the default.
.EXAMPLE
Get-DiskInventory -ComputerName SRV02 -drivetype 3
#>
[CmdletBinding()]        
param (
  $computername = 'localhost',
  $drivetype = 3
)
Get-CimInstance -class Win32_LogicalDisk -ComputerName $computername `
 -filter "drivetype=$drivetype" |
 Sort-Object -property DeviceID |
 Select-Object -property DeviceID,
     @{name='FreeSpace(MB)';expression={$_.FreeSpace / 1MB -as [int]}},
     @{name='Size(GB)';expression={$_.Size / 1GB -as [int]}},
     @{name='%Free';expression={$_.FreeSpace / $_.Size * 100 -as [int]}}

The [CmdletBinding()] must be the first line after the comment-based help; PowerShell knows to look for it here.

As noted, it’s important that the [CmdletBinding()] directive be the first line in the script after the comment-based help. PowerShell knows to look for it only there. With this one change, the script will continue to run normally, but we’ve enabled several neat features that we’ll explore next.

20.3 Making parameters mandatory

From here, we could say we are all done, but that wouldn’t be much fun, now, would it? Our script is in its existing form because it provides a default value for the -ComputerName parameter—and we’re not sure one is really needed. We’d rather prompt for that value than rely on a hardcoded default. Fortunately, PowerShell makes it easy—again, adding just one line will do the trick, as shown in the next listing.

Listing 20.3 Giving Get-DiskInventory.ps1 a mandatory parameter

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or
more computers.
.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk
instances from one or more computers. It displays each disk's
drive letter, free space, total size, and percentage of free
space.
.PARAMETER computername
The computer name, or names, to query. Default: Localhost.
.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation
for values. 3 is a fixed disk, and is the default.
.EXAMPLE
Get-DiskInventory -ComputerName SRV02 -drivetype 3
#>
[CmdletBinding()]
param (
  [Parameter(Mandatory=$True)]   
  [string]$computername,        
  [int]$drivetype = 3
)
Get-CimInstance -class Win32_LogicalDisk -ComputerName $computername `
 -filter "drivetype=$drivetype" |
 Sort-Object -property DeviceID |
 Select-Object -property DeviceID,
     @{name='FreeSpace(MB)';expression={$_.FreeSpace / 1MB -as [int]}},
     @{name='Size(GB)';expression={$_.Size / 1GB -as [int]}},
     @{name='%Free';expression={$_.FreeSpace / $_.Size * 100 -as [int]}}

The [Parameter(Mandatory=$True)] decorator will make PowerShell prompt for a computer name if whoever runs this script forgets to provide one.

Above and beyond

When someone runs your script but doesn’t provide a mandatory parameter, PowerShell will prompt them for it. There are two ways to make PowerShell’s prompt more meaningful to that user.

First, use a good parameter name. Prompting someone to fill in comp isn’t as helpful as prompting them to provide a computerName, so try to use parameter names that are descriptive and consistent with what other PowerShell commands use.

You can also add a help message:

[Parameter(Mandatory=$True,HelpMessage="Enter a computer name to query")

Some PowerShell hosts will display that help message as part of the prompt, making it even clearer to the user, but not every host application will use this attribute, so don’t be dismayed if you don’t see it all the time as you’re testing. We like including it anyway, when we’re writing something intended to be used by other people. It never hurts. But for brevity, we’ll omit HelpMessage from our running example in this chapter.

Just that one decorator, [Parameter(Mandatory=$True)], will make PowerShell prompt for a computer name if whoever runs this script forgets to provide one. To help PowerShell even further, we’ve given both of our parameters a data type: [string] for -ComputerName, and [int] (which means integer) for -drivetype.

Adding these kinds of attributes to parameters can become confusing, so let’s examine the Param() block syntax more closely—look at figure 20.1.

Figure 20.1 Breaking down the Param() block syntax

Here are the important things to notice:

  • All of the parameters are enclosed within the Param() block’s parentheses.

  • A single parameter can include multiple decorators, which can be either strung out on one line or placed on separate lines, as we’ve done in figure 20.1. We think multiple lines are more readable—but the important bit is that they all go together. Here, the Mandatory attribute modifies only -ComputerName; it has no effect at all on -drivetype.

  • Each parameter name except the last one is followed by a comma.

  • For better readability, we also like to put a blank line between parameters. We think it helps to visually separate them better, making the Param() block less confusing.

  • We define each parameter as if it were a variable $computername and $drivetype, but someone who runs this script will treat them as normal PowerShell command-line parameters, such as -ComputerName and -drivetype.

Try it Now Try saving the script in listing 20.3 and running it in the shell. Don’t specify a -ComputerName parameter, and see how PowerShell prompts you for that information.

20.4 Adding parameter aliases

Is computername the first thing that comes to mind when you think about computer names? Possibly not. We used -ComputerName as our parameter name because it’s consistent with the way other PowerShell commands are written. Look at Get-Service, Get-CimInstance, Get-Process, and others, and you’ll see a -ComputerName parameter on them all. So we went with that.

But if something like -host comes more easily to your mind, you can add that as an alternative name, or alias, for the parameter. It’s just another decorator, as shown in the following listing. Don’t use -hostname, however, as that indicates an SSH connection when using PowerShell Remoting.

Listing 20.4 Adding a parameter alias to Get-DiskInventory.ps1

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or
more computers.
.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk
instances from one or more computers. It displays each disk's
drive letter, free space, total size, and percentage of free
space.
.PARAMETER computername
The computer name, or names, to query. Default: Localhost.
.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation
for values. 3 is a fixed disk, and is the default.
.EXAMPLE
Get-DiskInventory -ComputerName SRV02 -drivetype 3
#>
[CmdletBinding()]
param (
  [Parameter(Mandatory=$True)]
  [Alias('host')]               
  [string]$computername,
  [int]$drivetype = 3
)
Get-CimInstance -class Win32_LogicalDisk -ComputerName $computername `
 -filter "drivetype=$drivetype" |
 Sort-Object -property DeviceID |
 Select-Object -property DeviceID,
     @{name='FreeSpace(MB)';expression={$_.FreeSpace / 1MB -as [int]}},
     @{name='Size(GB)';expression={$_.Size / 1GB -as [int]}},
     @{name='%Free';expression={$_.FreeSpace / $_.Size * 100 -as [int]}}

This addition is part of the -ComputerName parameter; it has no effect on -drivetype.

With this minor change, we can now run this:

PS C:> .Get-DiskInventory -host SRV02

Note Remember, you have to type only enough of a parameter name for PowerShell to understand which parameter you mean. In this case, -host was enough for PowerShell to identify -hostname. We could also have typed the full thing.

Again, this new addition is part of the -ComputerName parameter; it has no effect on -drivetype. The -ComputerName parameter’s definition now occupies three lines of text, although we could also have strung everything together on one line:

[Parameter(Mandatory=$True)][Alias('hostname')][string]$computername,

We just think that’s a lot harder to read.

20.5 Validating parameter input

Let’s play with the -drivetype parameter a little bit. According to the MSDN documentation for the Win32_LogicalDisk WMI class (do a search for the class name, and one of the top results will be the documentation), drive type 3 is a local hard disk. Type 2 is a removable disk, which should also have a size and free space measurement. Drive types 1, 4, 5, and 6 are less interesting (does anyone use RAM drives, type 6, anymore?), and in some cases they might not have an amount of free space (type 5, for optical disks). So we’d like to prevent anyone from using those types when they run our script. This listing shows the minor change we need to make.

Listing 20.5 Adding parameter validation to Get-DiskInventory.ps1

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or
more computers.
.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk
instances from one or more computers. It displays each disk's
drive letter, free space, total size, and percentage of free
space.
.PARAMETER computername
The computer name, or names, to query. Default: Localhost.
.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation
for values. 3 is a fixed disk, and is the default.
.EXAMPLE
Get-DiskInventory -ComputerName SRV02 -drivetype 3
#>
[CmdletBinding()]
param (
  [Parameter(Mandatory=$True)]
  [Alias('hostname')]   
  [string]$computername,
  [ValidateSet(2,3)]       
  [int]$drivetype = 3
)
Get-CimInstance -class Win32_LogicalDisk -ComputerName $computername `
 -filter "drivetype=$drivetype" |
 Sort-Object -property DeviceID |
 Select-Object -property DeviceID,
     @{name='FreeSpace(MB)';expression={$_.FreeSpace / 1MB -as [int]}},
     @{name='Size(GB)';expression={$_.Size / 1GB -as [int]}},
     @{name='%Free';expression={$_.FreeSpace / $_.Size * 100 -as [int]}}

We add [ValidateSet(2,3)] to the script to tell PowerShell that only two values, 2 and 3, are accepted by our -drivetype parameter and that 3 is the default.

There are a bunch of other validation techniques you can add to a parameter, and when it makes sense to do so, you can add more than one to the same parameter. Run help about_functions_advanced_parameters for a full list. We’ll stick with ValidateSet() for now.

Try it Now Save this script and run it again. Try specifying -drivetype 5 and see what PowerShell does.

20.6 Adding the warm and fuzzies with verbose output

In chapter 17, we mentioned how we prefer to use Write-Verbose over Write-Host for producing the step-by-step progress information that some folks like to see their scripts produce. Now’s the time for a real example. We’ve added a few verbose output messages in the following listing.

Listing 20.6 Adding verbose output to Get-DiskInventory.ps1

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or
more computers.
.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk
instances from one or more computers. It displays each disk's
drive letter, free space, total size, and percentage of free
space.
.PARAMETER computername
The computer name, or names, to query. Default: Localhost.
.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation
for values. 3 is a fixed disk, and is the default.
.EXAMPLE
Get-DiskInventory -ComputerName SRV02 -drivetype 3
#>
[CmdletBinding()]
param (
  [Parameter(Mandatory=$True)]
  [Alias('hostname')]   
  [string]$computername,
  [ValidateSet(2,3)]
  [int]$drivetype = 3
)
Write-Verbose "Connecting to $computername"              
Write-Verbose "Looking for drive type $drivetype"        
Get-CimInstance -class Win32_LogicalDisk -ComputerName $computername `
 -filter "drivetype=$drivetype" |
 Sort-Object -property DeviceID |
 Select-Object -property DeviceID,
     @{name='FreeSpace(MB)';expression={$_.FreeSpace / 1MB -as [int]}},
     @{name='Size(GB)';expression={$_.Size / 1GB -as [int]}},
     @{name='%Free';expression={$_.FreeSpace / $_.Size * 100 -as [int]}}
Write-Verbose "Finished running command"                 

Adds in three verbose output messages

Now try running this script in two ways. This first attempt shouldn’t display any of the verbose output:

PS C:> .Get-DiskInventory -ComputerName localhost

Now for a second attempt, where we want the verbose output displayed:

PS C:> .Get-DiskInventory -ComputerName localhost -verbose

Try it Now This is a lot cooler when you see it for yourself. Run the script as we’ve shown here, and see the differences for yourself.

How cool is that? When you want verbose output (pointed out in code listing 20.6), you can get it—and you don’t have to code the -Verbose parameter at all! It comes for free when you add [Cmdlet-Binding()]. And a really neat part is that it will also activate verbose output for every command that your script contains! So any commands you use that are designed to produce verbose output will do so “automagically.” This technique makes it easy to turn the verbose output on and off, making it a lot more flexible than Write-Host. And you don’t have to mess around with the $VerbosePreference variable to make the output show up onscreen.

Also, notice in the verbose output how we made use of PowerShell’s double quotation mark trick: by including a variable ($computername) within double quotes, the output is able to include the contents of the variable so we can see what PowerShell is up to.

20.7 Lab

This lab requires you to recall some of what you learned in chapter 19, because you’ll be taking the following command, parameterizing it, and turning it into a script—just as you did for the lab in chapter 19. But this time we also want you to make the -ComputerName parameter mandatory and give it a host alias. Have your script display verbose output before and after it runs this command too. Remember, you have to parameterize the computer name—but that’s the only thing you have to parameterize in this case.

Be sure to run the command as is before you start modifying it, to make sure it works on your system:

Get-CimInstance win32_networkadapter -ComputerName localhost |
 where { $_.PhysicalAdapter } |
 select MACAddress,AdapterType,DeviceID,Name,Speed

To reiterate, here’s your complete task list:

  • Make sure the command runs as is before modifying it.

  • Parameterize the computer name.

  • Make the computer name parameter mandatory.

  • Give the computer name parameter an alias, hostname.

  • Add comment-based help with at least one example of how to use the script.

  • Add verbose output before and after the modified command.

  • Save the script as Get-PhysicalAdapters.ps1.

20.8 Lab answer

<#
.Synopsis
Get physical network adapters
.Description
Display all physical adapters from the Win32_NetworkAdapter class.
.Parameter Computername
The name of the computer to check.
.Example
PS C:> c:scriptsGet-PhysicalAdapters -computer SERVER01
#>
[cmdletbinding()]
Param (
[Parameter(Mandatory=$True,HelpMessage="Enter a computername to query")]
[alias('host')]
[string]$Computername
)
Write-Verbose "Getting physical network adapters from $computername"
Get-CimInstance -class win32_networkadapter –computername $computername |
 where { $_.PhysicalAdapter } |
 select MACAddress,AdapterType,DeviceID,Name,Speed
Write-Verbose "Script finished."

Above and beyond

Take what you have learned so far and modify the script we made in chapter 19, Get-FilePath.ps1 (listing 19.2) and

  • Make it an advanced function.

  • Add mandatory parameters.

  • Add verbose output.

  • Adapt the formatting to make exporting to a CSV easier.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.156.212