Param statement, and new-object

Tue, Jul 12, 2005 4-minute read

Vivek recently posted a helpful script to get the contents of a web page.

As is common with .Net and Monad, there is more than one way to do it. In addition, there are also two Monad facilities we can take advantage of to make the script more readable: new-object, and the param statement.

First of all, the easy one. The new-object cmdlet creates a new object. In the current drop, it only creates .Net objects, but COM support is coming soon. This cmdlet replaces the calls to [Activator]::CreateInstance in Vivek’s script.

Next, the param statement. I used it in my new-calendarItem script, and it looks like this:

## test-params.msh
## Test the param facility

param(
   [string] $importantParameter = $(throw "Please specify the important parameter!"),
   [int] $countOfThings,
   [DateTime] $recordDate = [DateTime]::MaxValue
)

"ImportantParameter was $importantParameter"
"CountOfThings was $countOfThings"
"RecordDate was $recordDate"

It looks similar to a function definition in many languages, but offers more benefits. The param statement allows you to define the parameters to your script. In the simplest case, your user supplies values for all of your parameters. When they do this, Monad interprets them positionally for you. That is, each parameter gets assigned from the value passed at that position on the command line. For example:

MSH:130 C:\Temp >test-params First 123 "Tuesday, July 12, 2005"

ImportantParameter was First
CountOfThings was 123
RecordDate was 7/12/2005 12:00:00 AM

Notice how Monad coerces the data types, if it can – as with the DateTime value in the third position.

In your script, you simply refer to your parameters by the names you gave them: $importantParameter, $countOfThings, and $recordDate.

For some parameters, you might want to supply your user with sensible defaults. In that case, Monad allows you to specify a default value for your parameter, as with $recordDate above:

MSH:135 C:\Temp >test-params First 123

ImportantParameter was First
CountOfThings was 123
RecordDate was 12/31/9999 11:59:59 PM

However, your users don’t always know how to interact with your script. You might have parameters that should always be populated, such as $importantParameter in the example above. In that case, we specify a special default value for that parameter – an exception that tells them how to resolve the problem:

MSH:136 C:\Temp >test-params

 : Please specify the important parameter!
At d:\lee\tools\test-params.msh:5 char:42
+    [string] $importantParameter = $(throw  <<<< "Please specify the important parameter!"),

The param statement also gives you support of named parameters for free. Named parameters let your user specify the parameters by name, rather than position. Your parameter’s variable name becomes the parameter name:

MSH:141 C:\Temp >test-params -RecordDate:$([DateTime]::MinValue) `
>>    -ImportantParameter:"Important!" `
>>    -CountOfThings:123
>> 

ImportantParameter was Important!
CountOfThings was 123
RecordDate was 1/1/0001 12:00:00 AM

As with regular cmdlets, you need only to specify enough of the parameter to differentiate it from the others:

MSH:152 C:\Temp >test-params -r:$([DateTime]::MinValue) -i:"Important!" -c:10

ImportantParameter was Important!
CountOfThings was 10
RecordDate was 1/1/0001 12:00:00 AM

This also helps Monad disambiguate between multiple optional parameters:

MSH:153 C:\Temp >test-params "Important!" -r:$([DateTime]::MinValue)

ImportantParameter was Important!
>>>>> CountOfThings was 0
RecordDate was 1/1/0001 12:00:00 AM

MSH:154 C:\Temp >test-params "Important!" -c:123

ImportantParameter was Important!
CountOfThings was 123
>>>>> RecordDate was 12/31/9999 11:59:59 PM

So, given this new-found knowledge, this is how we might rewrite Vivek’s example. First, the System.Net.WebClient way, as newly implemented in V2 of the framework:

## get-uri.msh
## Get a web page

param(
   [string] $uri = $(throw "Please specify an URI to retieve"),
   [string] $userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"
)

$wc = new-object System.Net.WebClient
$wc.Headers.Add("user-agent", $userAgent)
$wc.DownloadString($uri)

and the System.Net.HttpWebRequest way:

## get-uri.msh
## Get a web page

param(
   [string] $uri = $(throw "Please specify an URI to retieve"),
   [string] $userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"
)

$request = [System.Net.WebRequest]::Create($uri)
$request.Headers.Add("user-agent", $userAgent)
$response = $request.GetResponse()
$requestStream = $response.GetResponseStream()
$readStream = new-object System.IO.StreamReader $requestStream
$readStream.ReadToEnd()
$readStream.Close()
$response.Close()

If we need the content split into an array (as with Vivek’s script,) we can write:

MSH:158 C:\Temp >$lines = (get-uri http://www.microsoft.com).Split("`n")
MSH:159 C:\Temp >$lines[0]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

For the purpose we need it, I much prefer the System.Net.WebClient way.

[Edit: Thanks, Toby, for pointing out the WebClient / HttpWebRequest switch]

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]