Hey, Scripting Guy! Question

Hey, Scripting Guy! I have been having problems with trying to understand functions in Windows PowerShell. I get that you use the Function keyword to create the function, but I do not know how to get information from the function back to the script. I have been able to make the functions work by using Write-Host from inside the function, but when I want to use the function to change a value (so that it actually works like a function), I run into all kinds of problems. Can you explain this to me?

-- BJ

Hey, Scripting Guy! AnswerHello BJ,

There are lots of things that are hard for me to explain. For example, how does the frogfish I saw while scuba diving in Maui (seen in the following picture) ever make any friends? He sits around all day looking like a really dirty orange sponge, and you never see one swimming, frolicking, and having fun. They just sit there. They have really good camouflage and are really hard to find when they are hiding on the coral. So when the mommy frogfish tells the baby frogfish to go outside and play, how does the baby frogfish ever find its way back home? These are the kinds of things that this scripting guy worries about.

Image of a frog fish 


BJ, luckily, you did not ask about frogfish. Instead you are worrying about getting data out of your function. Functions are not nearly as difficult to spot as are frogfish, so this will not be too difficult. When a function is called, it returns data to the calling code. This behavior is often not understood really well by people who are coming to Windows PowerShell from other scripting languages. When you run the AddOne.ps1 script, the number 6 is displayed on the console. The confusing part is that the data is returned from the line of code that calls the function, not from within the function itself. This is different behavior than might be expected. Most of the time, when two numbers are added together, the data is returned from the line that performs the work. This is seen here:

PS C:\> $int = 5
PS C:\> $int + 1
6
PS C:\>

It is therefore reasonable to expect that the number 6 is coming from inside the AddOne function, and not from outside the function. The AddOne.ps1 script with the AddOne function is seen here.

AddOne.ps1

Function AddOne($int)
{
 $int + 1
}

AddOne(5)

To illustrate from whence the data comes, you can modify the script to store the result of calling the function into a variable. You can then use the Get-Member cmdlet to display the information that is returned. This is seen in AddOne1.ps1.

AddOne1.ps1

Function AddOne($int)
{
 $int + 1
}

$number = AddOne(5)
$number | get-member
'Display the value of $number: ' + $number

When the AddOne1.ps1 script is run, you will see that the information is returned to the code that calls the function. In the first line after the function call, the object that is stored in the $number variable is shown to be a System.Int32 object. Following the Get-Member command, the value that is stored in the $number variable is shown to be equal to 6. The value 5 is not displayed from within the AddOne function. This is seen here:

TypeName: System.Int32

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     System.Int32 CompareTo(Object value), System.Int32 Co...
Equals      Method     System.Boolean Equals(Object obj), System.Boolean Equ...
GetHashCode Method     System.Int32 GetHashCode()
GetType     Method     System.Type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode()
ToString    Method     System.String ToString(), System.String ToString(Stri...
Display the value of $number: 6

When you use a cmdlet like Write-Host from inside the function, you then circumvent the return process that is inherent in the design of the function. The use of Write-Host from within a function is illustrated in AddOne2.ps1.

AddOne2.ps1

Function AddOne($int)
{
 Write-Host $int + 1
}

$number = AddOne(5)
$number | get-member
'Display the value of $number: ' + $number

When the script is run, you will notice that nothing is returned from inside the function. The $number variable no longer contains an object. This is seen here:

5 + 1
Get-Member : No object has been specified to get-member.
At C:\Documents and Settings\ed\Local Settings\Temp\tmp6.tmp.ps1:9 char:21
+ $number | get-member <<<<
Display the value of $number:

In addition to using cmdlets such as Write-Host from within a function to circumvent the output from a function, it is also possible to store the results of a function to a variable. The problem with storing results from the function to a variable within the function is that when a variable is created within a function, it is not available outside of the function. This is seen here:

AddOne3.ps1

Function AddOne($int)
{
 $number =  $int + 1
}

$number = AddOne(5)
$number | get-member
'Display the value of $number: ' + $number

When the AddOne3.ps1 script is run, there is no object in the $number variable because it is not available outside of the addone function. This is seen here:

Get-Member : No object has been specified to get-member.
At C:\Documents and Settings\ed\Local Settings\Temp\tmp9.tmp.ps1:9 char:21
+ $number | get-member <<<<
Display the value of $number:

One technique that is sometimes used to get the value from within the function to the calling script is to add a scope to the variable. This is shown in AddOne4.ps1.

AddOne4.ps1

Function AddOne($int)
{
 $global:number =  $int + 1
}

AddOne(5)
$global:number | get-member
'Display the value of $global:number: ' + $global:number

There is a potential problem with adding a variable to the global scope—the variable will continue to exist after the script has exited. As long as the Windows PowerShell console is open and until you explicitly remove the global variable, it will continue to be available. This means it will be available in other scripts and will always be available within the console. This may not be a problem, but it could cause scripts that use the same variable names to operate in an erratic fashion. One way to determine if the variable persists is to check the variable drive. This is shown here:

   TypeName: System.Int32

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     System.Int32 CompareTo(Object value), System.Int32 Co...
Equals      Method     System.Boolean Equals(Object obj), System.Boolean Equ...
GetHashCode Method     System.Int32 GetHashCode()
GetType     Method     System.Type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode()
ToString    Method     System.String ToString(), System.String ToString(Stri...
Display the value of $global:number: 6


PS C:\data\PowerShellBestPractices\Scripts\Chapter12> Get-Item Variable:\number

Name                           Value
----                           -----
number                         6

It is possible to remove the global variable in the last line of the script, by using the Remove-Variable cmdlet, but a better approach is to use the script-level scope instead of the global-level scope. The script-level variable will be available inside and outside the function while the script is running. After the script has run, the variable is removed. The use of the script-level scope is seen in the AddOne5.ps1 script.

AddOne5.ps1

Function AddOne($int)
{
 $script:number =  $int + 1
}

AddOne(5)
$script:number | get-member
'Display the value of $script:number: ' + $script:number

When the AddOne5.ps1 script runs, the value of the $number variable is available outside of the function. When the script has run, an error is returned when the Get-Item cmdlet is used to attempt to retrieve the value of the variable. This is seen here:

   TypeName: System.Int32

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     System.Int32 CompareTo(Object value), System.Int32 Co...
Equals      Method     System.Boolean Equals(Object obj), System.Boolean Equ...
GetHashCode Method     System.Int32 GetHashCode()
GetType     Method     System.Type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode()
ToString    Method     System.String ToString(), System.String ToString(Stri...
Display the value of $script:number: 6


PS C:\data\PowerShellBestPractices\Scripts\Chapter12> Get-Item variable:number
Get-Item : Cannot find path 'number' because it does not exist.
At line:1 char:9
+ Get-Item  <<<< variable:number

BJ, that is about all there is to getting information out of your function. Next time you are in the water, and you see what looks like a big colorful sponge yawning, it just might be a frogfish that is getting bored not having any friends. Fortunately, you do not have that problem around here. There are lots of scripting friends. You can follow us on Twitter, join our group on Facebook, and hang out in the Scripting Guys Forum. Join us tomorrow as we conclude our discussion of output from scripts. Until then, take care.

Ed Wilson and Craig Liebendorfer, Scripting Guys