Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I enjoyed reading yesterday’s blog post about working with functions. I was really interested when you were talking about using “real names” for my functions. You did not say too much about why I should do that, but I guess it makes sense. I am thinking about writing a function that reads a text file, and I am considering using the Read verb for that. Therefore, the function would be named Read-TextFile. What do you think?

-- DS

 

Hey, Scripting Guy! AnswerHello DS,

Microsoft Scripting Guy Ed Wilson here, I am listening to The Go-Go’s on my Zune HD and sipping a cup of English Breakfast tea from my new tea pot. I had not heard The Go-Go’s for a while, and the shuffle feature just brought up the song. Nice compliment to my tea.  

Image of Windows PowerShell 2.0 Best Practices book

Note: Portions of today's Hey, Scripting Guy! article are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. This book is now available.

DS, in addition to using “approved verbs,” it is a good idea to use the verb in the same manner in which the Windows PowerShell team used it. The easy way to verify this is to use the Get-Command cmdlet with the verb parameter. To see cmdlets that use the Read verb, you would use the command shown here:

PS C:\> Get-Command -Verb read

 

CommandType     Name                             Definition

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

Cmdlet          Read-Host                        Read-Host [[-Prompt] <Object>] [-AsSecureString]...

 

 

PS C:\>

In reviewing the list of cmdlets that use the verb Read, you can see there is only one cmdlet that uses the verb Read. It is the Read-Host cmdlet, which is used to obtain information from the command line. This would indicate that the verb Read is not used to describe reading a file. There is no verb called Display, and the Write verb is used in cmdlet names such as Write-Error and Write-Debug, both of which do not really seem to have the concept of displaying information. If you were writing a function that would read the content of a text file and display statistics about that file, you might call the function Get-TextStatistics.

By using a name such as Get-TextStatistics, we are following the Windows PowerShell team’s examples of cmdlet names such as Get-Process and Get-Service. The Get verb includes the concept of emitting retrieved content as essential functionality. The Get-TextStatistics function accepts a single parameter called path. The interesting thing about parameters for functions is that when you pass a value to the parameter, you use a dash. When you refer to the value inside the function, it is a variable such as $path. To call the Get-TextStatistics function, you have a couple of options. The first is to use the name of the function and put the value in parenthesis. This is seen here:

Get-TextStatistics("C:\fso\mytext.txt")

This is a natural way to call the function, and it works when there is a single parameter. It does not work when there are two or more parameters. Another way to pass a value to the function is to use the dash and the parameter name. This is seen here:

Get-TextStatistics –path "C:\fso\mytext.txt"

You will note from the previous example that no parentheses are required. You can also use positional arguments when passing a value. In this usage, you omit the name of the parameter entirely, and simply place the value for the parameter following the call to the function. This is illustrated here:

Get-TextStatistics "C:\fso\mytext.txt"

The use of positional arguments works well when one is working from the command line and wishes to speed things along by reducing the typing load. It can be a bit confusing, and in general I tend to avoid it, even when working at the command line. This is because I often copy my working code from the console directly into a script; as a result, I would need to retype the command a second time to get rid of aliases and unnamed arguments. With the improvements in tab expansion, I feel that the time saved by using positional arguments or partial arguments does not sufficiently warrant the time involved in retyping commands when they need to be transferred to scripts. The other reason for always using named arguments is that it helps me to be aware of the exact command syntax.

One additional way to pass a value to a function is to use partial parameter names. All that is required is enough of the parameter name to disambiguate it from other parameters. This means if you have two parameters that both begin with the letter “p,” you would need to supply enough letters of the parameter name that will separate it from the other parameter. This is illustrated here:

Get-TextStatistics –p "C:\fso\mytext.txt"

The complete text of the Get-TextStatistics function is seen here:

Function Get-TextStatistics($path)
{
 Get-Content -path $path |
 Measure-Object -line -character -word
}

Between Windows PowerShell 1.0 and Windows PowerShell 2.0, the number of verbs grew from 40 to 60. It is anticipated that the list of standard verbs should cover 80-85 percent of administrative tasks. The new verbs are listed here:

Checkpoint
Complete
Connect
Debug
Disable
Disconnect
Enable
Enter
Exit
Limit
Receive
Register
Reset
Restore
Send
Show
Undo
Unregister
Use
Wait

After the function has been named, you will create any parameters the function may require. The parameters are contained within parentheses. In the Get-TextStatistics function, the function accepts a single parameter, path. When you have a function that accepts a single parameter, you can pass the value to the function by placing the value for the parameter inside parentheses. This command is seen here:

Get-TextLength("C:\fso\test.txt")

The path "C:\fso\test.txt" is passed to the Get-TextStatistics function via the path parameter. Inside the function, the string "C:\fso\text.txt" is contained in the $path variable. The $path variable only lives within the confines of the Get-TextStatistics function. It is not available outside the scope of the function. It is available from within child scopes of the Get-TextStatistics function. A child scope of the Get-TextStatistics is one that is created from within the Get-TextStatistics function.

In the Get-TextStatisticsCallChildFunction.ps1 script, the Write-Path function is called from within the Get-TextStatistics function. This means the Write-Path function will have access to variables that are created within the Get-TextStatistics function. This is the concept of variable scope and is an extremely important concept when working with functions. As you use functions to separate the creation of objects, you must always be aware of where the object gets created and where you intend to use that object. In the Get-TextStatisticsCallChildFunction the $path variable does not obtain its value until it is passed to the function. Therefore, it lives within the Get-TextStatistics function. But because the Write-Path function is called from within the Get-TextStatistics function, it inherits the variables from that scope. When you call a function from within another function, variables created within the parent function are available to the child function. This is seen in the Get-TextStatisticsCallChildFunction.ps1 script.

Get-TextStatisticsCallChildFunction.ps1

Function Get-TextStatistics($path)
{
 Get-Content -path $path |
 Measure-Object -line -character -word
 Write-Path
}

Function Write-Path()
{
 "Inside Write-Path the `$path variable is equal to $path"
}

Get-TextStatistics("C:\fso\test.txt")
"Outside the Get-TextStatistics function `$path is equal to $path"

Inside the Get-TextStatistics function, the $path variable is used to provide the path to the Get-Content cmdlet. When the Write-Path function is called, nothing is passed to it. But inside the Write-Path function, the value of $path is maintained. Outside both of the functions, however, $path does not have any value. The output from running the script is seen here:

Image of output from running script

 

You will then need to open and close a script block. The curly bracket is used to delimit the script block on a function. As a best practice, when writing a function I will always use the function keyword, and type the name, the input parameters, and the curly brackets for the script block at the same time. This is seen here:

Function My-Function()
{
 #insert code here
}

 

DS, that is all there is to working with function names and function parameters. Function Week will continue tomorrow.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys