Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am confused about using functions in Windows PowerShell 2.0. I was also confused about using functions in Windows PowerShell 1.0. Are they like functions in VBScript, or are they something different? In addition, I have tried to create a subroutine in Windows PowerShell, and it does not seem to work. What am I doing wrong?

-- EB

 

Hey, Scripting Guy! AnswerHello EB,

Microsoft Scripting Guy Ed Wilson here. The cool thing about coming home after having been on holiday is that, well, you are home. I have stayed in all kinds of hotels over the years in 37 different countries, and though some have been extremely nice hotels, I have yet to find one that was as nice as my own home. There is just something comforting about being home. Perhaps it is familiarity with the known that provides the comfort. EB, Because you are familiar with VBScript, I will use that language as a basis for answering your question today.  

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.

In Windows PowerShell 2.0, functions have moved to the forefront as the primary programming element. This is not because of improvements in functions, but rather a combination of factors including the maturity of Windows PowerShell script writers. In Windows PowerShell 1.0, functions were not well understood, perhaps due to the lack of clear documentation about their use, purpose, and application.

In VBScript you had both subroutines and functions. According to the classic definitions, a subroutine was used to encapsulate code that would do things such as writing to a database and creating a Word document. The function was used to return a value. An example of the classic VBScript function is one that converts a temperature from Fahrenheit to Celsius. The function receives the value in Fahrenheit and returns the value in Celsius. The classic function always returns a value; if it does not, the subroutine should be used.

Needless to say, the concept of function and subroutine was a bit confusing for many VBScript writers. A common question I used to receive when teaching VBScript classes was, "When do I use a subroutine and when do I use a function?" After sharing the classic definitions, I would then show them that you could actually write a subroutine that would behave like a function. Next, I would write a function that acted like a subroutine. It was great fun, and the class loved it. The Windows PowerShell team has essentially done the same thing. There is no confusion over when to use a subroutine and when to use a function, because there are no subroutines in Windows PowerShell—only functions.

To create a function, you begin with the Function keyword followed by the name of the function. As a best practice, use the Windows PowerShell Verb-Noun combination when creating functions (such as Get-Help, in which Get is the verb and Help is the noun). Pick the verb from the standard list of Windows PowerShell verbs to make your functions easier to remember. It is a best practice to avoid creating new verbs when there is an existing verb that can easily do the job.

In most cases, you won’t need to create a new verb. I have written more than 3,000 Windows PowerShell scripts in the last three years, and I have never needed to create a new verb. The verbs are well named, and easily cover most situations an IT pro would need to script.

An idea of the verb coverage can be obtained by using the Get-Command cmdlet and piping the results to the Group-Object cmdlet. This is seen here:

Get-Command -CommandType cmdlet | Group-Object -Property Verb |
Sort-Object -Property count –Descending

The above command was run on Windows Vista and includes only the default cmdlets. No modules were loaded. As seen in the following list, Get is used the most by the default cmdelts, followed distantly by Set, New, and Remove.

Count Name                      Group
----- ----                      -----
   46 Get                       {Get-Acl, Get-Alias, Get-AuthenticodeSignatu...
   19 Set                       {Set-Acl, Set-Alias, Set-AuthenticodeSignatu...
   16 New                       {New-Alias, New-Event, New-EventLog, New-Ite...
   14 Remove                    {Remove-Computer, Remove-Event, Remove-Event...
    8 Export                    {Export-Alias, Export-Clixml, Export-Console...
    8 Write                     {Write-Debug, Write-Error, Write-EventLog, W...
    7 Import                    {Import-Alias, Import-Clixml, Import-Counter...
    7 Out                       {Out-Default, Out-File, Out-GridView, Out-Ho...
    6 Add                       {Add-Computer, Add-Content, Add-History, Add...
    6 Start                     {Start-Job, Start-Process, Start-Service, St...
    6 Invoke                    {Invoke-Command, Invoke-Expression, Invoke-H...
    6 Clear                     {Clear-Content, Clear-EventLog, Clear-Histor...
    5 Test                      {Test-ComputerSecureChannel, Test-Connection...
    5 Stop                      {Stop-Computer, Stop-Job, Stop-Process, Stop...
    4 ConvertTo                 {ConvertTo-Csv, ConvertTo-Html, ConvertTo-Se...
    4 Register                  {Register-EngineEvent, Register-ObjectEvent,...
    4 Format                    {Format-Custom, Format-List, Format-Table, F...
    4 Disable                   {Disable-ComputerRestore, Disable-PSBreakpoi...
    4 Enable                    {Enable-ComputerRestore, Enable-PSBreakpoint...
    3 ConvertFrom               {ConvertFrom-Csv, ConvertFrom-SecureString, ...
    3 Wait                      {Wait-Event, Wait-Job, Wait-Process}
    3 Update                    {Update-FormatData, Update-List, Update-Type...
    3 Select                    {Select-Object, Select-String, Select-Xml}
    3 Rename                    {Rename-Computer, Rename-Item, Rename-ItemPr...
    2 Unregister                {Unregister-Event, Unregister-PSSessionConfi...
    2 Restart                   {Restart-Computer, Restart-Service}
    2 Move                      {Move-Item, Move-ItemProperty}
    2 Copy                      {Copy-Item, Copy-ItemProperty}
    2 Measure                   {Measure-Command, Measure-Object}
    1 Tee                       {Tee-Object}
    1 Suspend                   {Suspend-Service}
    1 Debug                     {Debug-Process}
    1 Sort                      {Sort-Object}
    1 Show                      {Show-EventLog}
    1 Checkpoint                {Checkpoint-Computer}
    1 Split                     {Split-Path}
    1 Disconnect                {Disconnect-WSMan}
    1 Use                       {Use-Transaction}
    1 Pop                       {Pop-Location}
    1 Where                     {Where-Object}
    1 Trace                     {Trace-Command}
    1 Exit                      {Exit-PSSession}
    1 Enter                     {Enter-PSSession}
    1 Undo                      {Undo-Transaction}
    1 Receive                   {Receive-Job}
    1 Read                      {Read-Host}
    1 Compare                   {Compare-Object}
    1 Convert                   {Convert-Path}
    1 Connect                   {Connect-WSMan}
    1 Join                      {Join-Path}
    1 Push                      {Push-Location}
    1 Complete                  {Complete-Transaction}
    1 Limit                     {Limit-EventLog}
    1 Resume                    {Resume-Service}
    1 ForEach                   {ForEach-Object}
    1 Send                      {Send-MailMessage}
    1 Reset                     {Reset-ComputerMachinePassword}
    1 Group                     {Group-Object}
    1 Restore                   {Restore-Computer}
    1 Resolve                   {Resolve-Path}

A function is not required to accept any parameters. In fact, many functions do not require input to perform their job in the script. Let us use an example to illustrate this point. A common task for network administrators is obtaining the operating system version. Script writers often need to do this to ensure their script uses the correct interface or exits gracefully. It is also quite common that one set of files would be copied to a desktop running one version of the operating system, and a different set of files for another version of the operating system.

The first step in creating a function is to come up with a name. Because the function is going to retrieve information, the best verb to use is Get. For the noun portion of the name, it is best to use something that describes the information that will be obtained. In this example, a noun of OperatingSystemVersion makes sense. An example of such a function is the function seen in the Get-OperatingSystemVersion.ps1 script. The Get-OperatingSystemVersion function uses WMI to get the version of the operating system. In this, the most basic form of the function, you have the function keyword followed by the name of the function, and a script block with code in it, which is delimited by curly brackets. This pattern is seen here:

Function Function-Name
{
 #insert code here
}

In the Get-OperatingSystemVersion.ps1 script, the Get-OperatingSystemVersion function is at the top of the script. It uses the Function keyword to define the function followed by the name, Get-OperatingSystemVersion. The curly brackets are opened, followed by the code. The code uses the Get-WmiObject cmdlet to retrieve an instance of the Win32_OperatingSystem WMI class. Because this WMI class returns only a single instance, the properties of the class are directly accessible. The version is the property in question, and so parentheses force the evaluation of the code inside. The returned management object is used to emit the version value. The curly brackets are used to close the function. The operating system version is returned to the code that calls the function. In this example, a string is used that writes, "This OS is version." A subexpression is used to force evaluation of the function. The version of the operating system is returned to the place from where the function was called. This is seen here:

Get-OperatingSystemVersion.ps1

Function Get-OperatingSystemVersion
{
 (Get-WmiObject -Class Win32_OperatingSystem).Version
} #end Get-OperatingSystemVersion

"This OS is version $(Get-OperatingSystemVersion)"

 

EB that is all there is to creating a basic function. 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