Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! You have been talking about writing functions and using functions all week. To me it seems like a lot of extra work to create a function. Do I have to write functions? Or can I just write my code in a script like I used to do with VBScript?

-- GB

 

Hey, Scripting Guy! AnswerHello GB,

Microsoft Scripting Guy Ed Wilson here. The other day we had some friends over to the Scripting House to watch the The Godfather (vanilla infused black tea with a dash of milk seems to go well with Godfather movies). One of my favorite expressions came from that movie when someone wondered how the Godfather was able to accomplish a particular thing. The answer was, “He made him an offer he could not refuse.” If you want to make using Windows PowerShell easier, write a function you can reuse (rim shot).  

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.

When I teach a Windows PowerShell class, often students come up with ideas for scripts that I have not previously written. It is not uncommon for me to whip out a script on the fly while teaching. One thing that makes this possible is the fact that I write many functions. When scripts are written using well-designed functions, it makes it easier to reuse them in other scripts and to provide access to these functions from within the Windows PowerShell console. To get access to these functions, you will need to dot source the containing script. An issue with dot sourcing scripts to bring in functions is that often the script may contain global variables or other items you do not want to bring into your current environment.

An example of a good function is the ConvertToMeters.ps1 script. There are no variables defined outside the function, and the function itself does not use the Write-Host cmdlet to break up the pipelilne. The results of the conversion will be returned directly to the calling code. The only problem with the ConvertToMeters.ps1 script is that when it is dot sourced into the Windows PowerShell console, it runs, and returns the data because all executable code in the script is executed. The ConvertToMeters.ps1 script is seen here.

ConvertToMeters.ps1

Function Script:ConvertToMeters($feet)
{
  "$feet feet equals $($feet*.31) meters"
} #end ConvertToMeters
$feet = 5
ConvertToMeters -Feet $feet

With a well-written function, it is trivial to collect them into a single script—you just cut and paste. When you are finished, you have created a function library. With a function library, you can avoid the problem of polluting your scripting environment with inadvertently imported variables.

When pasting your functions into the function library script, pay attention to the comments at the end of the function. The comments at the closing curly bracket for each function not only point to the closing curly bracket, but also provide a nice visual indicator for the end of each function. This can be helpful when you need to troubleshoot a script. An example of such a function library is the ConversionFunctions.ps1 script, which is seen here.

ConversionFunctions.ps1

Function Script:ConvertToMeters($feet)
{
  "$feet feet equals $($feet*.31) meters"
} #end ConvertToMeters

Function Script:ConvertToFeet($meters)
{
 "$meters meters equals $($meters * 3.28) feet"
} #end ConvertToFeet

Function Script:ConvertToFahrenheit($celsius)
{
 "$celsius celsius equals $((1.8 * $celsius) + 32 ) fahrenheit"
} #end ConvertToFahrenheit

Function Script:ConvertTocelsius($fahrenheit)
{
 "$fahrenheit fahrenheit equals $( (($fahrenheit - 32)/9)*5 ) celsius"
} #end ConvertTocelsius

Function Script:ConvertToMiles($kilometer)
{
  "$kilometer kilometers equals $( ($kilometer *.6211) ) miles"
} #end convertToMiles

Function Script:ConvertToKilometers($miles)
{
  "$miles miles equals $( ($miles * 1.61) ) kilometers"
} #end convertToKilometers

One way to use the functions from the ConversionFunctions.ps1 script is to use the dot sourcing operator to run the script so that the functions from the script are part of the calling scope. To dot source the script, you use the dot source operator (a period) followed by the path to the script containing the functions you wish to include in your current scope. After you do this, you can call the function directly, as seen here:

PS C:\> . C:\scripts\ConversionFunctions.ps1
PS C:\> convertToMiles 6
6 kilometers equals 3.7266 miles

All of the functions from the dot sourced script are available to the current session. If you would like the functions to always be available, you can add dot source to the function library script via your Windows PowerShell profile.

We talked about Windows PowerShell profiles on the Hey, Scripting Guy! Blog during the week of November 23, 2009.

After you have dot sourced the function library into the Windows PowerShell console, you can check that the functions are available by examining the function drive. This is seen here, where the Get-ChildItem cmdlet is used to provide a list of the functions that begin with the letters “co”:

PS C:\> Get-ChildItem function: | Where { $_.name -like 'co*'} | Format-Table -Property name, definition -AutoSize

Name                Definition
----                ----------
ConvertToMeters     param($feet) "$feet feet equals $($feet*.31) meters"...
ConvertToFeet       param($meters) "$meters meters equals $($meters * 3.28) feet"...
ConvertToFahrenheit param($celsius) "$celsius celsius equals $((1.8 * $celsius) + 32 ) fahrenheit"...
ConvertTocelsius    param($fahrenheit) "$fahrenheit fahrenheit equals $( (($fahrenheit - 32)/9)*5 ) celsius...
ConvertToMiles      param($kilometer) "$kilometer kilometers equals $( ($kilometer *.6211) ) miles"...
ConvertToKilometers param($miles) "$miles miles equals $( ($miles * 1.61) ) kilometers"...

You can use multiple functions from multiple scripts if you wish to do so. Suppose you have a function that gets weather from a weather service, and you would like to display the temperature in Celsius. The Get-Weather function is shown here.

The Get-Weather function was discussed in the How Can I Use Web Services? Hey, Scripting Guy! Blog post.

Function Get-Weather
{
 Param(
  [Parameter(Mandatory=$true)]
  [string]$city,
  [Parameter(Mandatory=$true)]
  [string]$country
 )#end param
 $URI = "http://www.webservicex.net/globalweather.asmx?wsdl"
 $Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy
 $Proxy.GetWeather($city,$country)
} #end Get-Weather

You can use a function from the ConversionFunctions.ps1 script and a function from the Get-InternationalWeather.ps1 script in the same script. This is illustrated in the UseDotSourceScripts.ps1 script, seen here.

UseDotSourceScripts.ps1

. C:\data\ScriptingGuys\2009\HSG_12_21_09\ConversionFunctions.ps1
. C:\data\ScriptingGuys\2009\HSG_12_21_09\Get-InternationalWeather.ps1

[xml]$xml = Get-Weather -city "Rock Hill" -country "United States"
$F = ($xml.CurrentWeather.Temperature.split("F"))[0].trim()
ConvertTocelsius($f)

When the UseDotSourceScripts.ps1 script runs, the following output is displayed:

Image of output of script

 

Of course, the best way to reuse functions is to place them in modules because it gives you much more control over the export process. We’ll discuss modules in January 2010. GB, that is all there is to using functions from other scripts. 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