Idera nSoftware Compellent

Chapter 9. Functions

PowerShell has the purpose of solving problems, and the smallest tool it comes equipped with for this is commands. By now you should be able to appreciate the great diversity of the PowerShell command repertoire: in the first two chapters, you already learned how to use the built-in PowerShell commands called cmdlets, as well as innumerable external commands, such as ping or ipconfig. In Chapter 6, the objects of the .NET framework, and COM objects were added, providing you with a powerful arsenal of commands.

In Chapters 3, 4, and 5, command chains forged out of these countless single commands combined statements either by using variables or the PowerShell pipeline.

The next highest level of automation is functions, which are self-defined commands that internally use all of the PowerShell mechanisms you already know, including the loops and conditions covered in the last two chapters.

Topics Covered:

Creating New Functions

Functions are self-defined new commands consisting of general PowerShell building blocks. They have in principle three tasks:

  • Shorthand: very simple shorthand for commands and immediately give the commands arguments to take along
  • Combining: functions can make your work easier by combining several steps
  • Encapsulating and extending: small but highly complex programs consisting of many hundreds of statements and providing entirely new functionalities

The basic structure of a function is the same in all three instances: after the Function statement follows the name of the function, and after that the PowerShell code in braces. Let's take a look at couple of examples:

First Example: Shorthand Functions

Perhaps you'd simply like to create comfortable shorthand for the customary console commands you already know. If PowerShell doesn't accept the "Cd.." entry because the mandatory blank character isn't interposed between command and argument, then create the appropriate shorthand function on the spot—and the problem will be solved right away:

Function Cd.. { Cd .. }
Cd..

Whenever you enter the Cd.. command afterwards, you won't get any error messages because PowerShell will invoke your function.

When you find yourself still repeatedly entering the same lengthy commands, functions may be the remedy. For example, if you're frequently using ping.exe with certain parameters, like ping.exe -w 100 -n 1 10.10.10.10, then this function will save you time:

Function myPing { ping.exe -w 100 -n 1 10.10.10.10 }
myPing
Pinging 10.10.10.10 with 32 bytes of data:
Reply from 88.70.64.1: destination host unreachable.

However, this function would be inflexible in practice; it would ping the same network address again and again. That's why most functions use arguments. Everything the caller specifies after the function name is in the $args variable. Let's modify our myPing function to ping any address.

Function myPing { ping.exe -w 100 -n 1 $args }
myPing www.microsoft.com
Pinging lb1.www.ms.akadns.net [207.46.193.254] with 32 bytes of data:
Request timed out.
Ping statistics for 207.46.193.254:
Packets: Sent = 1, Received = 0, Lost = 1 (100% Loss),

As you see, you only need to type the function again in order to overwrite the old version.

Second Example: Combining Several Steps

You might often need the nearest unallocated drive letter. The NextFreeDrive function can locate it. But before you try out the function and think about how to make it work, you should first answer the question of how to enter such a lengthy function.

Function NextFreeDrive
{
For ($x=67; $x -le 90; $x++)
{
$driveletter = [char]$x + ":"
If (!(Test-Path $driveletter))
{
$driveletter
break
}
}
}

Comfortably Entering Functions of Several Lines

Typing short functions is no problem but when a function consists of more than one line, PowerShell immediately activates its multiline mode, alerting you by the prompt symbol ">>":

Function NextFreeDrive
>> {
>> For ($x=67; $x -le 90; $x++)
(...)

Once the multiline mode is turned on, you have to type the entire function to the end. The prompt symbol ">>" will appear a last time, but when you press (Enter), the function will be operational. This kind of typing is not very user friendly, and when you make a typing mistake somewhere and forget a brace or quotation mark, you won't even be able to exit the multiline mode. Then it's time to cancel the multiline mode by hitting (Ctrl)+(C) and to begin all over again or to think about other options.

Reducing a Function to a Single Line

You could enter the function in just a single line, but it's not necessarily wise because then the function will hardly be understandable. If you want to reduce functions to a single line, then add a semi-colon after every command:

Function NextFreeDrive{For($x=67;$x -le 90;$x++){$driveletter=[char]$x+":";
If(!(Test-Path $driveletter)){$driveletter;break}}}

Using Text Editors

Functions can be written more easily in text editors. Even the Notepad is adequate. Start the Notepad with the Notepad command, type the function and when it's done, mark the entire text and copy it to the Clipboard. Afterwards, switch to the PowerShell console and right-click in it. If QuickEdit mode is active (see Chapter 1), the function code will be immediately inserted; if not, select Paste from the context menu. Special PowerShell editors like PowerShellPlus by Idera offer even more help.

Understanding NextFreeDrive

NextFreeDrive is an example of a function that doesn't require any arguments but supplies a return value:

NextFreeDrive
D:
$lw = NextFreeDrive
$lw
D:

So, let's take a look at how NextFreeDrive finds the next free drive letter and then reports back on it with a return value. The core of the function is a For loop (see Chapter 8) that counts from 67 to 90:

For ($x=67; $x -le 90; $x++)
{ $x }
67
68
69
(...)
89
90

The function needs drive letters and it makes use of the fact that every letter is layered over ANSI code and the letters from "C" to "Z" have the ANSI codes from 67 to 90. To turn these numbers into letters, the function uses the type conversion we saw in Chapter 6 and converts the number into a character:

For ($x=67; $x -le 90; $x++)
{ [char]$x }
C
D
E
F
(...)
X
Y
Z

So, the loop returns letters, and the function in $driveletter changes them to drive letters by appending a colon. Test-Path cmdlet can verify whether this path already exists. If yes, the letter is already allocated. The function must return the first letter that is not allocated, so the result of that test has to be inverted by "!".

So, if Test-Path returns False, the drive letter is still unallocated. By using "!", If gets True, the condition is met, and the code in the braces after If is executed. It defines the return value of the function by outputting the contents of $driveletter. Because the drive letter has been located, the For loop can now be interrupted by break.

Processing and Modifying Functions

If you'd like to make a change to an existing function, the usual advice is to just enter the function again. New version automatically overwrites old version. If you haven't stored the code of your function in an external editor, it's no fun to type it all over again, especially for really long functions.

You can also convert functions into a script, an external file that has the ".psl" file extension.

# The next two commands both store the content of the tabexpansion function in a file:
$function:tabexpansion | Out-File myscript.ps1
$function:tabexpansion > myscript.ps1

# Notepad opens the file:
notepad $$

The last line used to open the file in the Notepad is a little unusual. You can specify the name of the file after notepad, but $$ is shorter and easier. This special variable always contains the last token of the last pipeline. In this case, the last token was the name of the file.

Does it really matter whether you use Out-File or the redirection character to write the code of the function to a file? If you have to take care of encoding, use the Out-File cmdlet. It allows you to use the -encoding parameter to define encoding yourself.

Do you still remember how you write-protected variables in Chapter 3 or declared them as constants? This always works for functions as you can create write-protected functions that can't be modified:

Set-Item function:test {
"This function can neither be deleted nor modified."} -option constant
test

Try to use Del function:test to delete the function or function test { "Hello" } to overwrite it—both will fail. The function will not be deleted until PowerShell exits. If you create the function right away when PowerShell starts as part of a self-starting profile script (see the next chapter), nobody will be able to make any more changes to the function.

Removing Functions

Normally, you don't need to remove functions yourself. That's taken care of when you exit PowerShell. However, if you'd like to delete a function immediately, here is how you'd accomplish it:

# Remove the function called "test":
Del function:test

# The "test" function is deleted and can no longer be found:
test
The term "test" is not recognized as a cmdlet, function, operable
program, or script file. Verify the term and try again.
At line:1 char:4
+ test <<<<

Passing Arguments to Functions

It's true that functions can work completely autonomously, executing exactly the same commands whenever they're called. But usually that doesn't make sense. It's more often the case that you want functions to process given data or to control them to a certain extent. You can accomplish that by using arguments, which is additional information that you pass when calling a function. How you pass these arguments to your function is a matter of personal preference:

  • Arbitrary arguments: the $args variable contains all the arguments that are passed to a function. This is a good solution to implementing optional (voluntary) arguments.
  • Named arguments: a function can also assign a fixed name to arguments, ensuring that these arguments are mandatory. In addition, users can use parameters to name named arguments so that they won't have to be specified in a set sequence.
  • Predefined arguments: arguments may include default values. If the caller doesn't specify any of his own arguments to the function, the appropriate default value will be used.
  • Typed arguments: arguments can be defined for particular data types to make sure that the arguments correspond to a certain data type. This works in principle exactly like the typed variables covered in Chapter 3.
  • Special argument types: aside from conventional data types, arguments can also act like a switch: if a switch (i.e., the name of the argument) is specified, the argument has the $true value.

$args: Arbitrary Arguments

The simplest way to pass arguments to a function is to use the $args variable. It contains the arguments specified when a function is called, and then it is entirely up to the function itself to decide what to do with the contents of $args.

Because a function has no mandatory requirement for arguments, none have to be given. Arguments are voluntary or optional. Moreover, because $args can hold any number of arguments, a function isn't restricted to a limited number of arguments either. Here's a test function that will give you your first overview of how $args works:

function Howdy {
If ($args -ne $null) {
"You specified: $args"
"Argument number: $($args.count)"
$args | ForEach-Object { $i++; "$i. Argument: $_" }
} Else {
"You haven't specified any arguments!"
}
}

When you call this function without any arguments, it will detect that $args is empty and will output the relevant text. Now, try how the function behaves with various arguments:

# The function notices when you haven't specified any arguments:
Howdy
You haven't specified any arguments!
# Arguments are specified directly after the function name:
Howdy Tobias
You specified: Tobias
Argument number: 1
1. Argument: Tobias
# Several arguments are separated by blank characters:
Howdy Tobias Weltner
You specified: Tobias Weltner
Argument number: 2
1. Argument: Tobias
2. Argument: Weltner
# Text in quotation marks is evaluated as a single argument:
Howdy "Tobias Weltner"
You specified: Tobias Weltner
Argument number: 1
1. Argument: Tobias Weltner
# When used in PowerShell, the comma generally creates an array
Howdy Tobias, Weltner
You specified: System.Object[]
Argument number: 1
1. Argument: Tobias Weltner

The most important insight is that arguments are separated by blank characters, and if there's any white space in a text, the text has to be placed within quotation marks. This isn't a new rule. It applies to everything in PowerShell, including to cmdlets and their parameters. You'll find individual arguments as elements in the $args array. The first argument will be in $args[0], the second in $args[1], and so on.

In contrast, if you use commas to separate arguments, you'll be generating an array (see Chapter 4). The entire array can be found as a single argument in $args. Just take a look:

function test {
Foreach ($element in $args) {
$i++
If ($element -is [array]) {
"$i. Argument is an array: $element"
} Else {
"$i. Argument is not an array: $element"
}
}
}

test Hello test
1. Argument is not an array: Hello
2. Argument is not an array: test
test Hello,test value1 value2
1. Argument is an array: Hello test
2. Argument is not an array: value1
3. Argument is not an array: value2

It's important to realize that if you'd like to assign more than one value to an argument, then you should make a list of comma-separated values and pass an array to this argument. This works the same way for cmdlets and is a very important basic PowerShell principle. For example, the following line would list the directory contents of C:\ and C:\Users as well as all DLL files beginning with "p" in the Windows system directory:

Dir c:\, c:\users, $env:windir\system32\p*.dll

This is possible because Dir in this case contains only one single argument of yours, but it is an array that includes three elements. If you wanted to enable your own functions to accept arrays as arguments as well, you could try this:

function SaySomething {
# No argument was given:
If ($args -eq $null)
{
"No arguments"
# An array was specified as the first argument,
# so the function calls itself again
# for every argument in the array:
}
ElseIf ($args[0] -is [array])
{
Foreach ($element in $args[0])
{
SaySomething $element
}
# The first argument is not an array; the actual task was not completed:
}
Else
{
"Howdy, $args"
}
}

If you pass a comma-separated list to the function, it will recognize that the first argument ($args[0]) is an array. The function then picks out the array elements separately in a Foreach loop and invokes itself again with each separate element. Now your function is just as flexible as most cmdlets and can process an individual argument as well as a comma-separated list:

SaySomething Tobias
Howdy, Tobias
SaySomething Tobias, Martina, Cof
Howdy, Tobias
Howdy, Martina
Howdy, Cof

If you'd like to refer to certain arguments, just remember again that $args is an array. That means you can refer to each argument as you would with an array, by using an index, beginning at position 0. So, you'll find the first argument in $args[0]. Knowing this, you can make your own little function that adds two numbers together:

Add function{ $args[0] + $args[1]}
Add 1 2
3

Because $args is an array and you can find out at any time which elements are in this array, you could reformulate the function so that it would add as many numbers as you wish:

Add function
{
Foreach ($number in $args)
{
$result += $number
}
"Total: $result"
}
Add 1 4 5 12 436
Total: 458

Setting Parameters

While $args contains all the arguments that you pass to a function, that really isn't so useful. Because $args is an array, you're continually forced to access unreadable array elements. It would be easier if the passed arguments were available with their own names in separate variables. That is possible without too much effort by using a trick. In Chapter 3, you learned that you can assign variables not only separate values but also fill several variables with different values in one fell swoop.

The trick here is arrays. If you specify on the left side of an assignment operator a comma-separated variable list, then the contents of an array on the right side will be assigned to it. $args is an array, and that's why you could use this method of assigning to sub-divide the contents of $args into separate variables that are easier to handle:

function Add {
$Value1, $Value2 = $args
$Value1 + $Value2
}
Add 1 6
7

You no longer need to access the elements in $args within the function, but can use the named variables into which you sub-divided the contents of $args. However, the arguments used in this approach are still always optional. You can also specify fewer or more than two arguments, which becomes a problem as soon as you specify more than two arguments:

Add 1 2 3
"System.Object[]" cannot be converted to "System.Int32".
At line:3 char:9
+ $value1 + <<<< $value2

To understand why, take a look at the following example. The user had specified three arguments as $args contained three elements. The function distributed these three elements between two variables. The first variable got the first element and the second variable got all the others:

$value1, $value2 = 1,2,3
$value1
1

$value2
2
3

Despite all of PowerShell's capabilities, it cannot add a number with an array so you get an error. The reason is that the function accepts any number of arguments. If you want to specify a fixed number of arguments instead of any number of arguments, then lock in the expected arguments in the function description by defining the parameters:

Function subtract($Value1, $Value2) {
$value1 - $value2
}
Subtract 5 2
3

Subtract 5
5

By the way, arguments and parameters, while not the same, at least have a friendly relationship. What the user passes to a function in the way of additional information are arguments. They originate from whatever invokes the function. The function itself can define parameters. The user's arguments are then assigned to the parameters.

However, both parameters are not really mandatory. They only make sure that the user's arguments won't end up any more in the $args general container, but are clearly assigned to particular parameters: parameter binding. If the user doesn't specify an argument that you required, the parameter will automatically be assigned an empty value ($null) instead of generating an error. You'll read a little later about how you can ensure that the arguments you require really do get specified.

A great advantage of parameters is that arguments no longer need to be given in a fixed order. If you want to specify the argument for the Value2 parameter first and only afterwards the argument for the Value1 parameter, then type the parameter name before every argument for which it is meant. Writing it this way is nothing new: all cmdlets also work according to this principle:

# Named arguments can be assigned using parameters;
# a fixed sequence isn't necessary:
Subtract -Value1 12 2
10

Subtract -Value2 12 2
-10

At first, PowerShell "binds" the arguments that you have locked in to a parameter in the above way. Subsequently, all the other arguments not yet assigned to a parameter will be bound in the specified order to the parameters yet to be taken care of. So, if you bind the first argument to the Value2 parameter, the second argument, the number 2, remains. It can now be assigned to the first parameter that hasn't been looked after yet, Value1.

A second advantage is that your function will now be immune to additionally specified arguments. If the user gives more arguments than you asked for, nothing bad will happen. His additional statement will simply be ignored.

# Unnecessary arguments will be ignored:
Subtract 5 2 3
3

However, they won't really be ignored. All the arguments that you didn't assign to unnamed arguments will end up in $args again. As a result, you can check or query any number of additional voluntary statements to see whether additional arguments have been specified, and then use Throw to generate an appropriate error message:

# This function won't accept any optional arguments:
Subtract function($Value1, $Value2)
{
# Verify whether there are additional inputs;
# if yes, generate an error message:
If ($args.Count -ne 0) {
Throw "I don't need any more than just two arguments." }

$value1 - $value2
}
Subtract 1 2
-1

# If there are more than the two required arguments,
# the function will generate an error message:
Subtract 1 2 3
I don't need any more than just two arguments.
At line:2 char:31
+ If ($args.Count -ne 0) { Throw <<<<
"I don't need any more than just two arguments."}

Arguments Having Predefined Default Values

You have just seen that functions with fixed parameters will not output errors if fewer arguments are given than what are asked for. Instead, parameters that haven't been taken care of yet will just simply be left empty. However, they don't have to remain empty, because by using default values you can determine which value a parameter is supposed to have if a user omits the argument.

# This function uses fixed default values for its parameters:
function subtract($Value1=10, $Value2=20)
{
$value1 - $value2
}

# If no argument is given, the function
# will use the supplied default value:
Subtract
-10

# If arguments are incomplete, the function will use the
# following defaults for the missing arguments:
Subtract -Value1 30
10
Subtract -Value2 100
-90

Can you require that the user must give the requested arguments? That will also work because fixed values are not only allowed as default values: sub-expressions are, too. Do you remember? Sub-expressions are always enclosed in parentheses and evaluated separately. The result of the sub-expression will be reported subsequently if you put a "$" before it. Take advantage of that if you want to make it mandatory for an argument to be specified for a parameter: as default value, assign to the parameter for which you make an argument obligatory a sub-expression that output an error message.

If the function is invoked without the mandatory argument, the function will try to use the default value and evaluate the sub-expression. The error message it contains will be output. This sounds much more complicated than it really is, as the next example shows:

# This function will report an error if the
# argument for "Value1" is not specified:
Subtract function($Value1=$(Throw "Value1 wasn't specified!"), $Value2=20)
{
$value1 - $value2
}

# The second argument may be omitted; the default value will be used:
Subtract 10
-10

# The first argument may not be omitted because
# the default value is an error message:
Subtract
Value1 wasn't specified!
At line:1 char:36
+ Subtract function($Value1=$(Throw <<<< "Value1
wasn't specified!"), $Value2=20) {

The fact that you may use complete sub-expressions as default values for parameters is useful in other situations as well. You can adjust parameters to current daily requirements, such as date, logon names, or any other information that has default value at the moment in which the function is invoked:

function Weekday ($date=$(Get-Date))
{
$date.DayOfWeek
}

If you invoke your Weekday function without an argument, it will output the current day of the week. The standard value of the $date parameter is reset by the sub-expression, with the help of Get-Date, every time the function is invoked. If you specify another date after your function, you will, theoretically, find out on what day of the week the date falls. In practice, nothing at all happens. To find out why the function (still) doesn't work using its own arguments, read the next section.

Using Strongly Typed Arguments

You've just seen that things can get really muddled when the user's arguments are assigned to function parameters. The culprit is the argument parser, which takes the user's unprocessed arguments and distributes them among the function parameters.

The argument parser of functions is usually an arrogant guy who is completely indifferent to what information you give as argument. The argument parser is only interested in neatly splitting the raw data specified after the function into separate arguments and then passing these to the function parameters.

Of course, the function "sees" that quite differently because it must solve a very specific problem with the passed arguments and it doesn't care at all about what sort of arguments they are. That leads to trouble when the function expects arguments of a particular data type. This problem was already evident in the preceding examples, and you'll learn how to solve it in this section.

Only Numbers Allowed

Subtract function instantly throws an error if you pass string to it instead of numbers because it's impossible to "count" string:

Subtract Hello world
Method invocation failed because [System.String]
doesn't contain a method named "op_Subtraction".
At line:3 char:9
+ $value1 - <<<< $value2

Errors caused by mismatching data types are difficult to locate and remove because the resulting error message sounds confusing and the error is reported in a completely wrong location where the process is trying to get something done with an inappropriate data type.

It would make much more sense if the argument parser of the function hadn't accepted the mismatching arguments in the first place, which brings us to the solution: you can tell the argument parser which data types the parameters of your function can use. If the user specifies the wrong data types as an argument anyway, the argument parser will refuse to accept it and report the mistake with a much more explicit error message:

# This function accepts nothing but numbers as an argument:
function Subtract([int]$Value1, [int]$Value2)
{ $value1 - $value2 }

Subtract 5 2
3

# As long as the argument can be converted
# to a number, the function is satisfied:
Subtract "5" 2
3

# The function will accept no inputs that cannot
# be converted to numbers
Subtract Hello world
subtract : cannot convert value "Hello" to type "System.Int32".
Error: "input string was not in a correct format."
At line:1 char:12
+ Subtract <<<< Hello world

To get the argument parser to accept nothing but very specific data types, you should use the strong type specification that you learned about in Chapter 3. For that purpose, jot down the desired data type in brackets in front of the parameter. Effective immediately, the parameter will accept only numbers or information that can be changed to numbers. If the user specifies the wrong data type, an error will be generated that will now describe the cause with much greater clarity.

But watch out when you choose the data type for your arguments. Do you have any idea why your function returns the following results?

Subtract 8.2 0.2
8

Subtract 8.2 1.4
7

Subtract 8.2 1.9
6

Because your function expects the Integer data type for arguments, the specified floating point numbers are changed automatically to whole numbers and rounded off. To a certain extent, the same thing happens here:

[int]1.4
1

[int]1.9
2

So, if you'd like your function to handle floating point numbers properly, then you may not set the data type of the argument to Integer([int]). Instead, use the ([double]) floating point data type and the computational results will be correct:

function Subtract ([double]$Value1, [double]$Value2)
{
$value1 - $value2
}

Subtract 8.2 0.2
8

Subtract 8.2 1.4
6.8

Subtract 8.2 1.9
6.3

You can find an overview of the most used data types in Chapter 3.

Date Required

However, strong type specification is not only useful for rejecting mismatching data types. It can also be put to work to convert data types into a better format. You must surely remember the mysterious Weekday function, which would output the day of the week for the current date but not for the date that you specified. Without strong type specification, PowerShell automatically transformed your argument into the presumably matching data type, namely a string.

The function uses the DayOfWeek method to determine the weekday and because the String data type doesn't contain this method, the function consequently didn't return a result. You should know the solution by now: require the argument to be of the DateTime type. Then, the argument parser will, if possible, convert the input automatically into this type. If it isn't possible for it to convert the argument, an error will be generated:

function Weekday([datetime]$date=$(Get-Date))
{ $date.DayOfWeek }
Weekday 1.1.1980
Tuesday

Weekday 1.2.1980
Friday

Weekday sometime
Weekday : Cannot convert value "sometime" to type
"System.DateTime". Error: "The string was not
recognized as a valid DateTime. There is an unknown
word starting at index 0."
At line:1 char:10
+ Weekday <<<< sometime

"Switch" Parameter Is Like a Switch

The simplest conceivable parameter of a function contains just a binary yes/no value. This simple feature can (as an exception) even be represented entirely without an argument: if the parameter exists, then it will contain $true(for "yes"), otherwise $false(for "no"). If your function can utilize such yes/no decisions, then it can employ the simplified "switch" parameters, which are used most frequently to select special options. The following function, WriteText, writes text in the console. If you specify the -inverse switch, then the text will be output inversely:

Function WriteText([Switch]$inverse, $text)
{
If ($inverse) {
Write-Host -ForegroundColor "Black" `
-BackgroundColor "White" $text
} Else {
$text
}
}

If -inverse is specified as an argument, nothing new will happen: the argument parser will recognize that it is a parameter name. Normally, the parser would now assign the argument after -inverse to the parameter $inverse. However, because this parameter is of the [Switch] type, the argument parser "knows" that it is a simple parameter that can contain only $true or $false, and assigns it the $true value. On the other hand, if you don't specify -inverse, then the automatic default value of the parameter is $false.

Specifying Return Values of a Function

A function should ultimately return a result to whatever invoked the function. The examples of functions in this chapter have done their best to show that already. But exactly how functions return results is a highly interesting matter because functions work completely differently in PowerShell than in all other programming languages. That's reason enough to take a closer look at this aspect of functions.

One or More Return Values?

In fact, PowerShell functions don't return a single particular value. They simply return everything that they output at one juncture or another, while a function does its work. In the simplest case, that's just a single value, like the one in the following example. If you invoke the function interactively, the result will be output in the console. But you could just as well store the result of the function in a variable and process it further:

Function VAT([double]$amount=0)
{
$amount * 0.19
}

# An interactively invoked function
# output results in the console:
VAT 130.67
24.8273

# But the result of the function can
# also be assign to a variable:
$result = VAT 130.67
$result
24.8273

# The result is a single number value
# of the "double" type:
$result.GetType().Name
Double

In this example, the function returned a single number value. But what would happen if the function returned more than one result? To test that, we only need a function that output more than one result:

Function VAT([double]$amount=0)
{
$factor = 0.19
$total = $amount * $factor
"Value added tax {0:C}" -f $total
"Value added tax rate: {0:P}" -f $factor
}

# The function returns two results:
VAT 130.67
Value added tax $24.83
Value added tax rate: 19.00%

# All results are stored in a single variable:
$result = VAT 130.67
$result
Value added tax $24.83
Value added tax rate: 19.00%

# Several results are automatically stored in an array:
$result.GetType().Name
Object[]

# You can get each separate result of the
# function by using the index number:
$result[0]
Value added tax $24.83

# The data type of the respective array element
# corresponds to the included data:
$result[0].GetType().Name
String

To summarize, if a function outputs only one value, then this value will be returned immediately. In this respect, functions behave very much like functions in other programming languages. On the other hand, if a function returns more than one value, then all the values will be wrapped in an array. However, often you won't even notice because PowerShell cleverly converted each of the elements in the array into string and output them one below the other when you output the array. As a result, at first sight it looks like all the output of the function had merged together to form a joint text.

That's not the case, as shown by the preceding example. Each result of the function is neatly segregated as a separate result so that you could pick a specific result out of the array and output it.

The Return Statement

You now know that functions basically return as a result everything that you output somewhere in the function. How to understand the special return statement that you see in the next example? Might it influence what a function returns as a result or does it even determine the result?

Function Add([double]$Value1, [double]$Value2)
{
return $Value1 + $Value2
}

# The function returns the value that comes after "return":
Add 1 6
7

It seems so—at least at first. In fact, return works quite differently. The function still returns as a result everything that is output in the function. But in addition (and not instead of this), a result is returned of what follows return. So, you could just as well have omitted return. That raises the question of why return was even invented.

  • First, return exists for stylistic reasons because in many other programming languages it is customary to expressly specify values a function returns by using a statement like return. Unfortunately, return causes more confusion than it helps since it conceals the fact that all the other output of the function was returned, as well and not just what follows return.
  • Second, to some extent return also acts like a break statement. All further statements after return are ignored. Therefore, you could immediately leave the function in a loop or a condition, provided that some particular interrupt criterion is met.
Function Add([double]$value1, [double]$value2)
{
# This time the function returns a whole
# series of oddly assorted results:
"Here the result follows:"
1
2
3

# Return also returns a further result:
return $value1 + $value2

# This statement will no longer be executed
# because the function will exit when return is used:
"Another text"
}


Add 1 6
Here the result follows:
1
2
3
7

$result = Add 1 6
$result
Here the result follows:
1
2
3
7

Accessing Return Values

Whether a function returns one or several results is something you can verify with the returned data type. If it is an array, then several results were returned, otherwise only one. To examine this more closely, we will need a function that returns, depending on the situation, either one or several results. We could use the lottery number generator in the following example, which outputs random lottery numbers from 1 to 49. You can use the $number parameter to determine how many lottery numbers are returned. If you don't specify anything, exactly one lottery number will be generated. Your task now is to find out whether a function will retrieve exactly one or several results.

Function lottery([int]$number=1)
{
$rand = New-Object system.random
For ($i=1; $i -le $number; $i++) {
$rand.next(1,50)
}
}

# If a lottery number is queried, the result is not an array:
$result = lottery
$result -is [array]
False

# If there are several lottery numbers, the result is an array:
$result = lottery 10
$result -is [array]
True

Why should you be interested in whether the outcome of a function is one or several results? Among other things, you would like to find out exactly how many elements a result returns. In the case of our lottery number generator, of course, it makes less sense, because you can specify how many numbers it's supposed to generate. However, for other functions and cmdlets, the question can be decisive. Take the example of the Dir command, which lists the contents of a directory. If you want to know how many files are actually in a directory, then it's inevitable that you concern yourself with the question of whether Dir found none, exactly one, or many files.

If the directory holds more than one file, Dir will return an array, and you can ascertain the number of the elements (files) in the array by using Count:

(Dir c:\).Count
25

Here, in the parentheses again is a sub-expression which must be evaluated first. You could just as well have written:

$list = Dir c:\
$list.count
25

Things get thorny when Dir finds only one or no file at all, because then it doesn't return anymore arrays, and you can't use Count. An error will not be reported, just nothing at all:

(Dir *.MacGuffin).Count

You could now, as shown above, verify whether Dir returns an array or not, and if what it returns is not an array, see whether one file or none at all was found. There's a far more elegant method for doing this, though. The result of a function (or of a cmdlet) can always be wrapped in an array, even if there is only one result or none. Use the @(...) construction that you already know from Chapter 4. You can usually use this construction to create new array. The result is wrapped in an array. If the result is an array anyway, naturally it will remain an array, but if the result is not an array, after this it will be one.

@(Dir c:\).count
25

@(Dir *.MacGuffin).count
0

Excluding Output from the Function Result

Now the result that the function is supposed to return just needs to be output somewhere in the function, which is convenient. But you'll still have to watch out that you don't mistakenly output anything that isn't part of the function result.

Excluding Text Output from the Result

During development, many script authors insert a little text output in the code so that they have a better grasp of whether a function is really doing what it should be doing. But you know now that this text output also ends up in the function result, causing considerable confusion. Take a look:

Function Test
{
"Calculation will be performed"
$a = 12 * 10
"Result will be emitted"
"Result is: $a"
"Done"
}


Test
Calculation will be performed
Result will be emitted
Result is: 120
Done

At first everything looks absolutely impeccable: the function documents its internal sequence of operations by additional text output. But as soon as you fail to use the function interactively, storing the result in a variable instead, things change quite suddenly because your text comments will now no longer be output when the function runs but end up in the result:

# Your debugging report will not be emitted:
$result = Test

# In fact some debugging reports as well as all other output are in the result:
$result
Calculation will be performed
Result will be emitted
Result is: 120
Done

So, if you want to output text that is supposed to appear immediately and not flow into the function result, then this output must be sent directly to the console, which can be accomplished by using the Write-Host cmdlet:

Function Test
{
Write-Host "Calculation will be performed"
$a = 12 * 10
Write-Host "Result will be emitted"
"Result is: $a"
Write-Host "Done"
}

# This time your debugging reports will already
# be output when the function is executed:
$result = test
Calculation will be performed
Result will be emitted
Done

# The result will no long include your debugging reports:
$result
Result is: 120

Using Debugging Reports

You would have to go to some trouble to remove these temporary messages when the function is ready and can be used in a production environment. You can save yourself the trouble by using the Write-Debug cmdlet for temporary text output.

Function Test
{
Write-Debug "Calculation will be performed"
$a = 12 * 10
Write-Debug "Result will be emitted"
"Result is: $a"
Write-Debug "Done"
}

# Debugging reports will remain completely
# invisible in the production environment:
$result = Test

# If you would like to debug your function,
# turn on reporting:
$DebugPreference = "Continue"

# Your debugging reports will now be output
# with the "DEBUG:" prefix and output in yellow:
$result = Test
DEBUG: Calculation will be performed
DEBUG: Result will be emitted
DEBUG: Done

# They are not contained in the result:
$result
Result is: 120

# Everything is running the way you wish;
# turn off debugging:
$DebugPreference = "SilentlyContinue"
$result = Test

Write-Debug has a number of advantages. First, your debugging reports will be clearly marked and output in another color. Second, these reports will appear only if you expressly turn on the debugging mode. If your function is being used in a normal production environment, PowerShell will simply ignore the Write-Debug instruction. As a result, you won't have to take the trouble to remove your debugging output when the script is done.

Suppressing Error Messages

Errors cropping up inside your function normally cause error messages, and these error message will always be output immediately. So unlike normal output, error messages will not become part of the result of your function.

Function Test
{
Stop-Process -name "Unavailableprocess"
}

# Normally error messages are always output immediately:
$result = Test
Stop-Process : Cannot find a process with the name
"Unavailableprocess". Verify the process name and call
the cmdlet again.
At line:2 char:13
+ Stop-Process <<<< -name "Unavailableprocess"

Obviously, this is very sensible because errors are not supposed to crop up, and if they do nevertheless, you should be alerted to their presence immediately. However, if you want to expressly make the error message "vanish" because you find it unimportant, turn off the error message output inside your function. But remember that from then on all error messages inside the function will no longer be generated:

Function Test
{
# Suppress all error messages from now on:
$ErrorActionPreference = "SilentlyContinue"
Stop-Process -name "Unavailableprocess"
}

# All error messages inside the function are suppressed:
$result = Test

Of course, this is only wise if you're absolutely sure that you can afford to ignore the error. Even then, you shouldn't in general suppress errors inside your function, but only where it is really necessary so that you won't overlook other (and perhaps completely unexpected) errors:

Function Test
{
# Suppress all error messages from now on:
$ErrorActionPreference = "SilentlyContinue"
Stop-Process -name "Unavailableprocess"
# Immediately begin outputting all error messages again:
$ErrorActionPreference = "Continue"
1/$null
}

# Error messages will be suppressed in certain
# areas but not in others:
$result = Test
Attempted to divide by zero.
At line:5 char:3
+ 1/$ <<<< zero

It would be far better for you not to ignore errors in general. Instead, you should take note and respond to them. You'll learn more about this in Chapter 11.

Inspecting Available Functions

PowerShell already contains some predefined functions that you can access through function: PSDrive. If you would like to see all available functions use Dir:

Dir function:
CommandType Name Definition
----------- ---- ----------
Function prompt 'PS ' + $(Get-Location) + $(If ($nested...
Function TabExpansion param($line, $lastWord) &amp;{...
Function Clear-Host $spaceType = [System.Management.Automat...
Function more param([string[]]$paths); If(($paths -n...
Function help param([string]$Name,[string[]]$Category...
Function man param([string]$Name,[string[]]$Category...
Function mkdir param([string[]]$paths); New-Item -type...
Function md param([string[]]$paths); New-Item -type...
Function A: Set-Location A:
Function B: Set-Location B:
(...)

The result will tell you not only the names of functions but also their contents, which will be in the Definition column. If you would like to examine the definition of a particular function more closely, then directly access the function:

$function:prompt
'PS ' + $(Get-Location) + $(If ($nestedpromptlevel -ge 1) { '>>' }) + '> '

Many of the predefined functions already perform important tasks in PowerShell. Let's now look a little more closely at a few examples:

Function Description
Clear-Host Deletes the screen buffer
help, man Retrieves get-help internally and outputs help text one page at a time if you use the -detailed or -full switches
mkdir, md Creates a new subdirectory using New-Item
more Outputs either pipeline contents one page at a time or—if you specify one or more path names after more—the contents of specified files one page at a time
prompt Returns prompt text
TabExpansion This function is called when you press (Tab) so that AutoComplete is activated. This tab completion mechanism uses the two variables $line and $lastword, in which you can find the line and the word requested for AutoComplete. Apart from the original Microsoft function TabExpansion supplied along with PowerShell, dedicated PowerShell users have developed numerous improved alternatives to enhance AutoComplete with many new options and functions.
X: Invokes Set-Location for the specified drive letter. A pure alias could not accomplish this; functions can invoke cmdlets combined with arguments, while alias names cannot.

Table 9.1: Predefined PowerShell functions

If you'd like to know how many functions are currently defined in your PowerShell environment, type:

(Dir Function:).Count

Prompt: A Better Prompt

Every time a command is successfully executed and the blinking cursor reappears, PowerShell invokes the Prompt function to receive new commands. In default setting, the prompt displays "PS", the path name of the current directory (retrieved by get-location), and after that one ">" or " >>" signs depending on whether the console is in normal mode or in a nested prompt. Because a publicly accessible function retrieves the prompt, you can make any changes you wish to the prompt. All you have to do is to reset the Prompt function. The relevant changes to the prompt will be made immediately:

Function Prompt { "Type something. >" }
Type something. >

How do you get the old prompt back? When you change a function, the old function will be overwritten. You can't just get the old function back. However, all functions will be deleted the moment you end PowerShell. So, when you end and restart PowerShell, you'll get back your familiar prompt.

Of course, that raises the question of how you can permanently modify the prompt, as well as from where PowerShell actually got the original Prompt function. You can specify permanent changes to the functions in one of your PowerShell profiles, which contain scripts that are automatically executed after PowerShell starts. PowerShell also defines its own functions in profiles so that's where you can change them permanently. You'll find out more about profiles in Chapter 10.

Outputting Information Text at Any Location

Because the console contents are actually in the screen buffer, which you can access one line or character at a time, you also have the option of displaying additional information at any position in the screen buffer. The next function shows you how to do that. Access the screen buffer by using $host.ui.rawui. The CursorPosition function will furnish the current position of the blinking cursor as X (column) and Y (line). The function will note the current position in $curPos and specify the new position as an additional 60 characters to the right. Then it relocates the cursor at the new position and outputs the time and date. Finally, the old cursor position is restored so that the prompt reappears at its usual location:

function prompt
{
$curPos = $host.ui.rawui.CursorPosition
$newPos = $curPos
$newPos.X+=60
$host.ui.rawui.CursorPosition = $newPos
Write-Host ("{0:D} {0:T}" -f (Get-Date)) `
-foregroundcolor Yellow
$host.ui.rawui.CursorPosition = $curPos
Write-Host ("PS " + $(get-location) +">") `
-nonewline -foregroundcolor Green
" "
}

Using the Windows Title Bar

There is also room for information in the Windows console title bar where your prompt function could put useful information, such as the name of the user who is currently logged on or the current line. Use $host.ui.rawui.WindowTitle to set the text of the Windows title bar.

The next example specifies the name of the user who is currently logged on. We'll use the GetCurrent .NET static method to get the name from the WindowsIdentity object. Because invoking this function can consume several seconds of computing time and should not be executed again every time a new prompt is displayed, the user name is specified outside the function as a global variable:

$global:CurrentUser = `
[System.Security.Principal.WindowsIdentity]::GetCurrent()
function prompt
{
$host.ui.rawui.WindowTitle = "Line: " `
+ $host.UI.RawUI.CursorPosition.Y + " " `
+ $CurrentUser.Name + " " + $Host.Name `
+ " " + $Host.Version
Write-Host ("PS " + $(get-location) +">") `
-nonewline -foregroundcolor Green
return " "
}

The example incidentally shows how long lines in particular can be split up into several shorter lines. If you type a backtick character ("`") at the end of a line, the line will be continued in the next line.

Administrator Warning

The Prompt function can also warn you if you're using PowerShell with elevated privileges. Use WindowsPrincipal to find out your current user identity to determine whether or not you currently have administrator privileges. You don't need to understand the .NET code. It will return a global variable in $Admin to you that contains $true if you have administrator rights.

This variable evaluates the Prompt function. If you're working with elevated privileges, the word "Administrator:" will appear in the Windows title bar and the ">" sign of the prompt will be displayed in red:

$CurrentUser = `
[System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = new-object `
System.Security.principal.windowsprincipal($CurrentUser)
$global:Admin = `
$principal.IsInRole( `
[System.Security.Principal.WindowsBuiltInRole]::Administrator)

Function prompt
{
# Output standard prompt:
Write-Host ("PS " + $(get-location)) -nonewline

# The rest depends on whether you have admin rights or not:
If ($admin) {
$oldtitle = $host.ui.rawui.WindowTitle
# "Administrator: " displayed in title bar
# if its not already included:
If (!$oldtitle.StartsWith("Administrator: ")) {
$host.ui.rawui.WindowTitle =
"Administrator: " + $oldtitle
}

# End prompt in red:
Write-Host ">" -nonewline -foregroundcolor Red
} Else {
Write-Host ">" -nonewline
}
return " "
}

Clear-Host: Deleting the Screen Buffer

No doubt, you've already noticed that the cls command deletes the screen buffer. In fact, cls is actually only an alias for the Clear-Host function. At first, though, you can't see the contents of this function:

$function:Clear-Host
You must provide a value expression on
the right-hand side of the "-" operator.
At line:1 char:17
+ $function:clear-h <<<< ost

The "-" sign is a special character for PowerShell and, as always, if names contain special characters, put the entire expression in braces (remove line breaks to collapse to one line):

${function:Clear-Host}
$spaceType = [System.Management.Automation.Host.BufferCell];
$space = [System.Activator]::CreateInstance($spaceType);
$space.Character = ' ';
$space.ForegroundColor = $host.ui.rawui.ForegroundColor;
$space.BackgroundColor = $host.ui.rawui.BackgroundColor;
$rectType = [System.Management.Automation.Host.Rectangle];
$rect = [System.Activator]::CreateInstance($rectType);
$rect.Top = $rect.Bottom = $rect.Right = $rect.Left = -1;
$Host.UI.RawUI.SetBufferContents($rect, $space);
$coordType = [System.Management.Automation.Host.Coordinates];
$origin = [System.Activator]::CreateInstance($coordType);
$Host.UI.RawUI.CursorPosition = $origin;

This function is very hard to read because it has been written as one long one-liner. The function could be read better in this way:

$spaceType = [System.Management.Automation.Host.BufferCell]
$space = [System.Activator]::CreateInstance($spaceType)
$space.Character = ' '
$space.ForegroundColor = $host.ui.rawui.ForegroundColor
$space.BackgroundColor = $host.ui.rawui.BackgroundColor
$rectType = [System.Management.Automation.Host.Rectangle]
$rect = [System.Activator]::CreateInstance($rectType)
$rect.Top = $rect.Bottom = $rect.Right = $rect.Left = -1
$Host.UI.RawUI.SetBufferContents($rect, $space)
$coordType = [System.Management.Automation.Host.Coordinates]
$origin = [System.Activator]::CreateInstance($coordType)
$Host.UI.RawUI.CursorPosition = $origin

It doesn't make much sense to customize the Clear-Host function because what it is supposed to do—clearing the console contents—it does well. Nevertheless, you could easily override the function:

function Clear-Host { }
cls

Because Clear-Host is now "empty" and isn't doing anything, you won't be able to delete the screen contents any more—not even by using cls, because this alias will internally invoke the same function. To get back Clear-Host, restart PowerShell.

Predefined Functions Once Again: A:, B:, C:

The list of functions shows that even drive letters are independent functions. How could that be? And, above all: when can that be? Let's first look at what the function D: is actually doing:

${function:D:}
Set-Location D:

The function does nothing other than switch over to the "D:" drive, much like Cd d:. But now the question arises: just when is that doing? Or, in other words: when does PowerShell actually invoke the D: function? If you type "D:", for example, after a statement, this entry will be interpreted as normal text. That means the built-in D: function will not be invoked:

Write-Host D:
D:
Dir D:

Only when "D:" itself becomes a statement, for example, because the term is the first in a line, or because the term is a sub-expression in parentheses, the D: function will be executed:

PS C:\> Dir (D:)
Directory: Microsoft.PowerShell.Core\FileSystem::D:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 08.03.2007 16:17 M
d---- 07.26.2007 10:29 n1
d---- 07.26.2007 09:16 nst
PS D:\>

The astonishing result: the D: function is invoked and switches over first to the D: drive. Dir then lists the current drive, that is, D:. In conclusion, D: remains the current drive. That means that within a line you have not only changed the current drive, but also then listed this drive. But that's not really so spectacular because usually what is enclosed in parentheses is executed first, and you could also have typed the following in them:

Dir (Cd e:)

X: functions are interesting more for the reason that they show how you can access statements along with arguments under new and concise names. Aliases like Cd (for Set-Location) can abbreviate unwieldy command names, but they can't predefine additional arguments. In contrast, functions can invoke other commands as well with predefined arguments. They are designed to ensure that by simply typing a drive name you can switch to that drive just like you would when using the older console:

# Actually, a function is being invoked here:
e:
Dir

Functions, Filters and the Pipeline

Can functions actually read and further process the results of other commands? They can, namely by the pipeline, which PowerShell uses to connect more than one command to each other (see Chapter 5). In Chapter 5, you learned that the pipeline can command two modes: a slow sequential mode and a rapid streaming mode. In which of the two modes the pipeline can operate really depends on the statements used in the pipeline, and how you define your functions.

The Slow Sequential Mode: $input

In the simplest case, your function doesn't really support the pipeline. Your function is limited merely to processing the results of the preceding pipeline command if the command has completed its work. The results of the preceding command are always in the $input automatic variable. $input is an array: depending on the circumstances, it can contain many elements, exactly one element, or no element at all.

In the simplest case, a function will merely output the contents of $input again:

Function output
{
$input
}

# The function, when invoked alone,
# will return nothing because no pipeline
# results are available:
output

# If you create an array in the pipeline,
# the function will output the array:
1,2,3 | output
1
2
3

# The function is completely indifferent to
# which type of data is in the pipeline:
Dir | output
Directory: Microsoft.PowerShell.Core\FileSystem::
C:\Users\Tobias Weltner
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 07.20.2007 11:37 Application Data
d---- 07.26.2007 11:03 Backup
d-r-- 04.13.2007 15:05 Contacts
(...)

Up to now, the function has merely output the pipeline results, and the result wasn't exactly spectacular. In the next step, the function should process each pipeline result separately. We want to create a function called MarkEXE, which will inspect the result of Dir and highlight executable programs having the ".exe" file extension in a red color:

Function MarkEXE
{
# Note old foreground color
$oldcolor = $host.ui.rawui.ForegroundColor

# Inspect each pipeline element separately in a loop
Foreach ($element in $input) {

# If the name ends in ".exe", change the foreground color to red:
If ($element.name.toLower().endsWith(".exe")) {
$host.ui.Rawui.ForegroundColor = "red"
} Else {
# Otherwise, use the normal foreground color:
$host.ui.Rawui.ForegroundColor = $oldcolor
}
# Output element
$element
}

# Finally, restore the old foreground color:
$host.ui.Rawui.ForegroundColor = $oldcolor
}

When you pass on the result of Dir to this function, you will immediately receive directory listings in which executable programs are listed in red:

Dir $env:windir | MarkEXE

Filter: Rapid Streaming Mode

The slow sequential mode of the pipeline that you became accustomed to can be a problem when you have to process large quantities of data, resulting in enormous memory consumption and waiting periods. If your function supports the rapid streaming mode of the pipeline, in which the results of preceding commands are processed in real time while using minimal memory, then all you need is a little trick: use the Filter keyword instead of the Function keyword.

You would actually just need to replace the first "function" keyword by "filter" in your MarkEXE function, and it would begin to use the rapid streaming mode right away. Now, without having to endure long waits and the risk of a crash, you could use your filter to process recursive directory listings, even extremely lengthy ones:

Dir c:\ -recurse | MarkEXE

While your MarkEXE would be invoked only a single time, after Dir has done its work, the MarkEXE filter would be invoked again and again for every single element. For filters, $input always contains only a single result. That's why $input in filters is not useful at all. It's better for you to use the $_ variable in filters because it contains the current result of the preceding command immediately. That simplifies code because from then on you no longer need any more loops:

Filter MarkEXE {
# Note old foreground color
$oldcolor = $host.ui.rawui.ForegroundColor

# The current pipeline element is in $_
# If the name ends in ".exe", change
# the foreground color to red:
If ($_.name.toLower().endsWith(".exe")) {
$host.ui.Rawui.ForegroundColor = "red"
} Else {
# Otherwise, use the normal foreground color:
$host.ui.Rawui.ForegroundColor = $oldcolor
}
# Output element
$_

# Finally, restore the old foreground color:
$host.ui.Rawui.ForegroundColor = $oldcolor
}

Developing Genuine Pipeline Functions

Filters are superior to normal functions in pipelines because they immediately process every single result of the preceding command and don't have to wait until the preceding command has completed all its tasks. However, filters must be invoked repeatedly for every single result of the preceding command. That's difficult because certain tasks, like initialization or tidying, have to be carried out again every single time the filter is invoked. The MarkEXE function, for example, notes the current console foreground color when it begins and restores it when it finishes. The filter would then have to perform this task repeatedly for every single result of the preceding command. That costs time and resources.

In reality, filters are nothing more than special functions. If a function is used inside a pipeline, then you can define three fundamentally different task areas: the first initialization in which the function completes preparatory steps; the processing of each single result that traverses the pipeline from the preceding command; and the tidying chores at the end. These three task areas can be defined in functions by using begin, process and end blocks.

It turns into a filter as soon as a function defines at least the process block. A filter is nothing more than a function with a process block. Unlike a filter, a function, can also define begin and end block. That's why the following MarkEXE function is, of all our examples, the most efficient approach because the initialization and cleanup tasks need to be performed only once:

Function MarkEXE {
begin {
# Note old foreground color
$oldcolor = $host.ui.rawui.ForegroundColor
}

process {
# The current pipeline element is in $_
# If the name ends in ".exe", change
# the foreground color to red:
If ($_.name.toLower().endsWith(".exe")) {
$host.ui.Rawui.ForegroundColor = "red"
} Else {
# Otherwise, use the normal foreground color:
$host.ui.Rawui.ForegroundColor = $oldcolor
}
# Output element
$_
}

end {
# Finally, restore the old foreground color:
$host.ui.Rawui.ForegroundColor = $oldcolor
}
}

The next example will show that a filter is actually only a normal function that has a process block. First, it defines a filter:

filter Test { "Output: " + $_ }

Let's look now at the definition of the filter:

$function:Test
process {
"Output: " + $_
}

PowerShell has translated its filter instruction into a normal function and set the code in a process block. Therefore, filters are functions that have a process block; nothing more.

Summary

Functions bring together one or more PowerShell commands under one name. If a function is invoked, it will execute the commands defined in it one after the other.

PowerShell uses this concept for internal purposes too; that's why it comes equipped with a number of predefined functions (see Table 9.1). You may modify these predefined functions if you'd like to change how PowerShell behaves.

You have the freedom of creating your own additional functions. For example, you can invent your own convenient shorthand for tasks that would otherwise require the execution of several steps or statements. In the simplest case, specify the name of your new function after the Function keyword and append the commands in braces that are supposed to carry out the function.

Functions will be more flexible if you pass them arguments that include additional information telling the function precisely what it is to do. The function can either access these arguments through the $args variable or define its own parameters. The arguments will then be automatically assigned to these parameters ("parameter binding") and all the arguments that might be left over will turn up again in $args. The parameters of a function can also be typed (in which case they will accept only a particular data type), and they may contain default values. Default values can also consist of PowerShell commands.

The result of a function includes everything that the function has output anywhere within its code. Therefore, a function can return zero results, exactly one result, or very many results. As soon as the result consists of more than one value, the function wraps it automatically in an array. It remains unaltered by the optional return statement, which merely has the purpose of exiting a function ahead of time.

In the PowerShell pipeline, functions also play a role in that they have the option of reading the results of the preceding command and processing them further. The results of the preceding command are in the $input variable. A function can implement the process block so that functions will not have to wait until the preceding command has completely carried out its work. This block will immediately step through every single result of the preceding command, and the respective result of the preceding command will then be provided in the $_ variable. Filter functions exactly like functions with a process block. In addition, functions can implement a begin and end block, which runs just once respectively and serves the purpose of executing preparatory and follow-up tasks.


Posted Mar 30 2009, 08:00 AM by ps1

Comments

Chapter 9. Functions - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 9. Functions - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 04-01-2009 2:31 PM

Pingback from  Chapter 9. Functions - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Chapter 11. Finding and Avoiding Errors - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 11. Finding and Avoiding Errors - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 04-01-2009 2:32 PM

Pingback from  Chapter 11. Finding and Avoiding Errors - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 12. Command Discovery and Scriptblocks
on 04-01-2009 2:33 PM

In previous chapters you learned step by step how to use various PowerShell command types and mechanisms. After 11 chapters, we have reached the end of the list. You'll now put together everything you've seen. All of it can actually be reduced

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 15. The File System
on 04-01-2009 2:34 PM

The file system has special importance within the PowerShell console. One obvious reason is that administrators perform many tasks that involve the file system. Another is that the file system is the prototype of a hierarchically structured information

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 16. The Registry
on 04-01-2009 2:35 PM

You can navigate the Windows registry just as you would the file system because PowerShell treats the file system concept discussed in Chapter 15 as a prototype for all hierarchical information systems.

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 20. Your Own Cmdlets and Extensions
on 04-10-2009 5:41 PM

Since PowerShell is layered on the .NET framework, you already know from Chapter 6 how you can use .NET code in PowerShell to make up for missing functions. In this chapter, we'll take up this idea once again. You'll learn about the options PowerShell

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 1. The PowerShell Console
on 04-30-2009 12:50 AM

Welcome to PowerShell! This chapter will teach you about all aspects of the PowerShell console from A to Z. You'll also learn how to configure the console to suit your personal preferences including font colors and sizes, editing and display options

Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 04-30-2009 12:54 AM

Pingback from  Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Chapter 10. Scripts - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 10. Scripts - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 04-30-2009 1:03 AM

Pingback from  Chapter 10. Scripts - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

KodefuGuru wrote Free PowerShell EBook
on 07-24-2009 3:24 PM

Free PowerShell EBook

Copyright 2010 PowerShell.com. All rights reserved.