Functions are a construct common to most programming languages that provide the basic modularization programmers have used for decades. PowerShell allows you to create a function by declaring it as follows:
Use the Function keyword.
Provide the name of your function.
Enclose the function's code within {curly braces}.
A very basic function might look like this:
function myFunction { write-host "Hello" }
Functions are nearly identical to script blocks except for two differences:1) functions are explicitly declared using the Function keyword; and 2) functions have a name. Otherwise, functions are practically the same as a script block.
This particular function doesn't accept any input arguments, nor does it really return any kind of value. It simply displays "Hello" on the screen. In languages like VBScript this function might have been written as a Sub since functions typically return some value in VBScript. However, in PowerShell there is not a separate construct if a value isn't being returned. Instead, you simply have the function not return anything if you don't need it to.
Note that you can interactively declare functions without writing a script. Try typing the following into PowerShell at the prompt:
function myFunction { write-host "Hello" }
This is the same function as the first example, but it is declared all on one line. Because it was entered into the command prompt within PowerShell, this function becomes available globally. In other words, it lives within the global scope. We talked a good deal about scope in Chapter 5 and elsewhere. You may recall that scope applies to the availability of functions just as it applies to the availability of variables. With myFunction declared globally, any child scopes such as scripts will be able to call the function. However, if you declare a function within a script, then only that script and its child scopes will be able to "see" the function and use it.
Functions can also be nested:
Function Outer1 { Function Inner1 { #code A here } Function Inner2 { #code B here } #code C here } Function Outer2 { Function Inner3 { #code D here } Function Inner4 { #code E here } #code F here }
If this were included in a script, then the entire script would be able to call either Outer1 or Outer2. However, the script would not be able to directly call any of the Inner functions since those exist within the Outer functions' private scopes. Any code within Outer1 (code C) is be able to call Inner1 and Inner2. However, the code within Outer2 (code F) is not be able to access Inner1 and Inner2 because those two functions are contained within the private scope of Outer1.
Functions have two ways of accepting input arguments. Here's the first:
Function add2 { [int]$args[0] + [int]$args[1] }
This could be called like this:
PS> Add2 10 20 30
This output shows that the two input arguments, 10 and 20, were successfully added. Inside the function these arguments were accessed by using the special $args variable. The $args variable is an array in which each element in the array represents one argument passed to the function. Even though this is a fairly informal technique for accepting input arguments, it may be difficult to follow when you read the script months later. A more formal, easier-to-maintain way to work with input arguments looks like this:
Function add2 ([int]$x, [int]$y) { $x + $y }
You would call this in exactly the same way. Up front it defines that two input arguments of the Integer type are required.
A third way to declare this function is as follows:
Function add2 { Param ([int]$x, [int]$y) $x + $y }
This is the same idea, however the parameters are defined in a special Param section that must be the first line of code in the function, instead of defining the arguments as a part of the function declaration itself.
Once again, you can call the function as follows:
PS> Add2 10 20 30
However, when you specifically define and name arguments using either of the above techniques, you can also call the function by naming the arguments as you pass them in:
PS> Add2 -x 10 -y 20 30
This passes the value 10 specifically to the $x argument, and 20 to the $y arguments. It does not make a difference in the math, but for more complex functions this technique provides more control and allows you to pass in arguments out of order, if necessary.
You can also declare a default value for an argument. In this case, if the function is called without a value for the argument, the function may be able to proceed using a default value:
Function add2 { Param ([int]$x = 10, [int]$y = 10) $x + $y }
Calling the function with no input arguments results in a value:
PS> Add2 20
Returning a value from a function is fairly easy - whatever the function outputs is also its return. So really, all of the sample functions we've looked at so far have returned a value. For example:
PS> $result = add2 10 10 PS> $result 20
Here, our Add2 function was called with 10 and 10 as input arguments. The result of the function was stored in $result. Anything output from the function becomes part of its result, not just the last thing it outputs. For example:
Functiontest.ps1
$a = "Hello" function foo ($b) { $b.ToUpper() $b.ToLower() } $x = foo $a $x
The function foo outputs both the uppercase and lowercase versions of the input argument. $x is set equal to foo's output. The result of this is:
PS C:\> test\functiontest HELLO Hello
This shows that $x contains both pieces of information output by the function.
You can also use the Return keyword to explicitly return a value:
$a = "Hello" function foo ($b) { return $b.ToUpper() $b.ToLower() } $x = foo $a $x
There's a caveat, though: Once you use Return, the function exits immediately. So, in the above example, $b.ToLower() would never execute, because it comes after the Return.
If a function produces output and uses Return, the value on the Return line is simply appended to any other output. Consider this function:
$a = "Hello" function foo ($b) { $b.ToUpper() Return $b.ToLower() } $x = foo $a $x
The result is identical to the first version of this function:
PS C:\> test\functiontest HELLO Hello
The idea of having a function return anything output from that function is neat, but it can also be confusing. We recommend that you concatenate all of your intended output into a variable, and use the Return keyword to return that value from the function. That way your function is only returning data through one technique, and it's a technique that's easy to spot when you're reviewing the code later.
The examples you've seen so far in this chapter have demonstrated that you can call functions outright. However, you can also pipe the output of other commands into a function. When you do this, the piped-in data is stored in the special $input variable. If multiple objects are piped to a function, then the function is called only once. In this case all of the objects are placed into $input at the same time. Here's a very simple example of a function that simply outputs whatever's piped in:
function foo { $input }
Piping this function generates same output for the Get-Process cmdlet as the Get-Process generates by itself:
PS C:\> get-process | foo Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 37 3 1080 3624 31 0.05 2208 acrotray 104 5 1144 3384 32 0.02 492 alg 61 2 548 2136 19 0.52 1076 ati2evxx 90 3 1152 4664 31 0.72 3960 ati2evxx 228 7 6316 8688 65 0.64 3216 BTSTAC~1 182 5 4004 7420 56 98.94 2216 BTTray 55 3 2060 3044 31 0.16 1508 btwdins 1093 9 2020 3964 31 51.44 816 csrss 218 5 2896 7716 45 0.30 5836 dllhost 697 15 18696 7036 108 160.30 604 explorer
However, consider this revised function:
function foo { $input | get-member }
The function is now piping its input to the Get-Member cmdlet, which changes the output as follows:
PS C:\> get-process | foo TypeName: System.Diagnostics.Process Name MemberType Definition ---- ---------- ---------- Handles AliasProperty Handles = Handlecount Name AliasProperty Name = ProcessName
Even though this output is truncated, it shows that $input was recognized as an object of the System.Diagnostics.Process type.
Here's another example:
function foo { foreach ($i in $input) { $i.ProcessName } }
Below is the partial output from this function when Get-Process is piped to it:
PS C:\> get-process | foo acrotray alg ati2evxx ati2evxx BTSTAC~1 BTTray btwdins csrss dllhost
The function takes the output of Get-Process into the $input variable. It then goes through each object in $input and displays just the ProcessName property of each.
Functions can include up to four special script blocks that execute during different phases of execution. When filters are discussed in the next section, you'll see this applies to filters also. These script blocks use special names to identify themselves:
Begin: This script block is executed only once when the function or filter is first called.
Process: If multiple objects are passed into the function or filter through the pipeline, this script block is executed once for each object.
End: This script block is executed after all pipeline objects have been dealt with by the Process script block.
Understanding how these script blocks work might take a little time. With a function, everything in the pipeline is normally lumped together into the $input variable. However, if the function contains a Process block, then $input is null. Instead, the Process block uses the special $_ variable to access the current pipeline object. Here's an example:
function foo { Begin { "Running processes:" } Process { $_.ProcessName } End { "Complete" } }
This produces the following output when Get-Process is piped to it:
PS C:\> get-process | foo Running processes: acrotray alg ati2evxx ati2evxx BTSTAC~1 BTTray btwdins csrss dllhost explorer firefox Groove hpqgalry hpqtra08 hpwuSchd2 HPZipm12 Complete
The Begin block runs first. For each object in the pipeline, Process is executed once, with the current object being put into the $_ variable. End is executed when all objects have been processed. Note that when using any of these blocks no code can appear outside a block. Any code within the function that's not within a block will result in an error. For example:
function foo { "Starting function foo" Begin { "Running processes:" } Process { $_.ProcessName } End { "Complete" } }
Results in this:
PS C:\> get-process | foo Starting function foo 'begin' is not recognized as a cmdlet, function, operable program, or script file. At C:\test\blocktest.ps1:3 char:8 + Begin <<<< { Get-Process : Cannot evaluate parameter 'Name' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input. At C:\test\blocktest.ps1:6 char:10 + Process <<<< { 'end' is not recognized as a cmdlet, function, operable program, or script file. At C:\test\blocktest.ps1:9 char:6 + End <<<< { PS C:\>
This occurs because code exists outside a script block when script blocks are in use.