In this chapter, we will cover the following:
Using the help system
Understanding command syntax and parameters
Understanding the pipeline
Working with variables and objects
Formatting output
Working with arrays and hash tables
Looping through items
Creating and running scripts
Using flow control statements
Creating custom objects
Creating PowerShell functions
Setting up a profile
So, your organization has decided to move to Exchange Server 2013 to take advantage of the many exciting new features such as integrated e-mail archiving, discovery capabilities, and high availability functionality. Like it or not, you've realized that PowerShell is now an integral part of Exchange Server management and you need to learn the basics to have a point of reference for building your own scripts. That's what this book is all about. In this chapter, we'll cover some core PowerShell concepts that will provide you with a foundation of knowledge for using the remaining examples in this book. If you are already familiar with PowerShell, you may want to use this chapter as a review or as a reference for later on after you've started writing scripts.
If you're completely new to PowerShell, its concept may be familiar if you've worked with Unix command shells. Like Unix-based shells, PowerShell allows you to string multiple commands together on one line using a technique called pipelining. This means that the output of one command becomes the input for another. But, unlike Unix shells that pass text output from one command to another, PowerShell uses an object model based on the .NET Framework, and objects are passed between commands in a pipeline, as opposed to plain text. From an Exchange perspective, working with objects gives us the ability to access very detailed information about servers, mailboxes, databases, and more. For example, every mailbox you manage within the shell is an object with multiple properties, such as an e-mail address, database location, or send and receive limits. The ability to access this type of information through simple commands means that we can build powerful scripts that generate reports, make configuration changes, and perform maintenance tasks with ease.
To work with the code samples in this chapter, follow these steps to launch the Exchange Management Shell:
Log on to a workstation or server with the Exchange Management Tools installed.
You can connect using remote PowerShell if you for some reason don't have Exchange Management Tools installed. Use the following command:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange ` -ConnectionUri http://tlex01/PowerShell/ ` -Authentication Kerberos ` Import-PSSession $Session
Open the Exchange Management Shell by clicking on Start | All Programs | Microsoft Exchange Server 2013. Or if you're using Windows 2012 Server, it can be found by pressing the Windows key.
Click on the Exchange Management Shell shortcut.
Note
Remember to start the Exchange Management Shell using Run As Admin to avoid permission problems.
In the chapter, notice that in the examples of cmdlets, I have used the back tick (`
) character for breaking up long commands into multiple lines. The purpose with this is to make it easier to read. The back ticks are not required and should only be used if needed.
The Exchange Management Shell includes over 750 cmdlets (pronounced command-lets), each with a set of multiple parameters. For instance, the New-Mailbox
cmdlet accepts more than 60 parameters, and the Set-Mailbox
cmdlet has over 160 available parameters. It's safe to say that even the most experienced PowerShell expert would be at a disadvantage without a good help system. In this recipe, we'll take a look at how to get help in the Exchange Management Shell.
To get help information for a cmdlet, type Get-Help
, followed by the cmdlet name. For example, to get help information about the Get-Mailbox
cmdlet, run the following command:
Get-Help Get-Mailbox -full
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.
When running Get-Help
for a cmdlet, a synopsis and description for the cmdlet will be displayed in the shell. The Get-Help
cmdlet is one of the best discovery tools to use in PowerShell. You can use it when you're not quite sure how a cmdlet works or what parameters it provides.
You can use the following switch parameters to get specific information using the Get-Help
cmdlet:
Detailed: The detailed view provides parameter descriptions and examples, and uses the following syntax:
Get-Help<cmdletname>-Detailed
Examples: You can view multiple examples of how to use a cmdlet by running the following syntax:
Get-Help<cmdletname>-Examples
Full: Use the following syntax to view the complete contents of the help file for a cmdlet:
Get-Help<cmdletname>-Full
Some parameters accept simple strings as input, while others require an actual object. When creating a mailbox using the New-Mailbox
cmdlet, you'll need to provide a secure string object for the -Password
parameter. You can determine the data type required for a parameter using Get-Help
:

You can see from the command output that we get several pieces of key information about the -Password
parameter. In addition to the required data type of <SecureString>
, we can see that this is a named parameter. It is required when running the New-Mailbox
cmdlet and it does not accept wildcard characters. You can use Get-Help
when examining the parameters for any cmdlet to determine whether or not they support these settings.
You could run Get-HelpNew-MailboxExamples
to determine the syntax required to create a secure string password object and how to use it to create a mailbox. This is also covered in detail in the recipe entitled Adding, modifying, and removing mailboxes in Chapter 3, Managing Recipients.
There will be times when you'll need to search for a cmdlet without knowing its full name. In this case, there are a couple of commands you can use to find the cmdlets you are looking for.
To find all cmdlets that contain the word "mailbox", you can use a wildcard, as shown in the following command:
Get-Command *Mailbox*
You can use the -Verb
parameter to find all cmdlets starting with a particular verb:
Get-Command -Verb Set
To search for commands that use a particular noun, specify the name with the -Noun
parameter:
Get-Command -Noun Mailbox
The Get-Command
cmdlet is a built-in PowerShell core cmdlet, and it will return commands from both Windows PowerShell as well as the Exchange Management Shell. The Exchange Management Shell also adds a special function called Get-Ex
command that will return only Exchange-specific commands.
In addition to getting cmdlet help for cmdlets, you can use GetHelp
to view supplementary help files that explain general PowerShell concepts that focus primarily on scripting. To display the help file for a particular concept, type Get-Helpabout_
followed by the concept name. For example, to view the help for the core PowerShell commands, type the following:
Get-Help about_Core_Commands
You can view the entire list of conceptual help files using the following command:
Get-Help about_*
Don't worry about trying to memorize all the Exchange or PowerShell cmdlet names. As long as you can remember GetCommand
and Get-Help
, you can search for commands and figure out the syntax to do just about anything.
One of the things that can be confusing at first is the distinction between cmdlets and functions. When you launch the Exchange Management Shell, a remote PowerShell session is initiated to an Exchange server and specific commands, called proxy functions, are imported into your shell session. These proxy functions are essentially just blocks of code that have a name, such as GetMailbox
, and that correspond to the compiled cmdlets installed on the server. This is true even if you have a single server and when you are running the shell locally on a server.
When you run the Get-Mailbox
function from the shell, data is passed between your machine and the Exchange server through a remote PowerShell session. The Get-Mailbox
cmdlet is actually executing on the remote Exchange server, and the results are being passed back to your machine. One of the benefits of this is that it allows you to run the cmdlets remotely regardless of whether your servers are on-premise or in the cloud. Additionally, this core change in the tool set is what allows Exchange 2010 and 2013 to implement its new security model by allowing and restricting which cmdlets administrators and end users can actually use through the shell or the web-based control panel.
We'll get into the details of all this throughout the remaining chapters in the book. The bottom line is that, for now, you need to understand that, when you are working with the help system, the Exchange 2013 cmdlets will show up as functions and not as cmdlets.
Consider the following command and the output:

Here we are running GetCommand
against a PowerShell v3 core cmdlet. Notice that the CmdletType
shows that this is a Cmdlet
.
Now try the same thing for the Get-Mailbox
cmdlet:

As you can see, the CommandType
for the Get-Mailbox
cmdlet shows that it is actually a Function
. So, there are a couple of key points to take away from this. First, throughout the course of this book, we will refer to the Exchange 2013 cmdlets as cmdlets, even though they will show up as functions when running GetCommand
. Second, keep in mind that you can run Get-Help
against any function name, such as Get-Mailbox
, and you'll still get the help file for that cmdlet. But if you are unsure of the exact name of a cmdlet, use Get-Command
to perform a wildcard search as an aid in the discovery process. Once you've determined the name of the cmdlet you are looking for, you can run GetHelp
against that cmdlet for complete details on how to use it.
Try using the help system before going to the Internet to find answers. You'll find that the answers to most of your questions are already documented within the built-in cmdlet help.
The Understanding command syntax and parameters recipe
The Manually configuring remote PowerShell connections recipe in Chapter 2, Exchange Management Shell Common Tasks
The Working with Role Based Access Control (RBAC) recipe in Chapter 10, Exchange Security
Windows PowerShell provides a large number of built-in cmdlets that perform specific operations. The Exchange Management Shell adds an additional set of PowerShell cmdlets used specifically for managing Exchange. We can also run these cmdlets interactively in the shell, or through automated scripts. When executing a cmdlet, parameters can be used to provide information, such as which mailbox or server to work with, or which attribute of those objects should be modified. In this recipe, we'll take a look at basic PowerShell command syntax and how parameters are used with cmdlets.
When running a PowerShell command, you type the cmdlet name, followed by any parameters required. Parameter names are preceded by a hyphen (-
) followed by the value of the parameter. Let's start with a basic example. To get mailbox information for a user named testuser
, use the following command syntax:
Get-Mailbox –Identity testuser
Alternatively, the following syntax also works and provides the same output, because the –Identity
parameter is a positional parameter:
Get-Mailbox testuser
Most cmdlets support a number of parameters that can be used within a single command. We can use the following command to modify two separate settings on the testuser
mailbox:
Set-Mailbox testuser –MaxSendSize 5mb –MaxReceiveSize 5mb
All cmdlets follow a standard verb-noun naming convention. For example, to get a list of mailboxes you use the Get-Mailbox
cmdlet. You can change the configuration of a mailbox using the Set-Mailbox
cmdlet. In both examples, the verb (Get
or Set
) is the action you want to take on the noun (Mailbox
). The verb is always separated from the noun using the hyphen (-
) character. With the exception of a few Exchange Management Shell cmdlets, the noun is always singular.
Cmdlet names and parameters are not case sensitive. You can use a combination of upper and lowercase letters to improve the readability of your scripts, but it is not required.
Parameter input is either optional or required, depending on the parameter and cmdlet you are working with. You don't have to assign a value to the c
parameter since it is not required when running the Get-Mailbox
cmdlet. If you simply run Get-Mailbox
without any arguments, the first 1,000 mailboxes in the organization will be returned.
Tip
If you are working in a large environment with more than 1,000 mailboxes, you can run the Get-Mailbox
cmdlet setting the -ResultSize
parameter to Unlimited
to retrieve all of the mailboxes in your organization.
Notice that in the first two examples we ran Get-Mailbox
for a single user. In the first example, we used the -Identity
parameter, but in the second example we did not. The reason we don't need to explicitly use the -Identity
parameter in the second example is because it is a positional parameter. In this case, -Identity
is in position 1
, so the first argument received by the cmdlet is automatically bound to this parameter. There can be a number of positional parameters supported by a cmdlet, and they are numbered starting from one. Other parameters that are not positional are known as named parameters, meaning we need to use the parameter name to provide input for the value.
The -Identity
parameter is included with most of the Exchange Management Shell cmdlets, and it allows you to classify the object you want to take an action on.
Tip
The -Identity
parameter used with the Exchange Management Shell cmdlets can accept different value types. In addition to the alias, the following values can be used: ADObjectID, Distinguished name, Domain\Username, GUID, LegacyExchangeDN, SmtpAddress, and User principal name (UPN).
Unlike the Get-Mailbox
cmdlet, the -Identity
parameter is required when you are modifying objects, and we saw an example of this when running the Set-Mailbox
cmdlet. This is because the cmdlet needs to know which mailbox it should modify when the command is executed. When you run a cmdlet without providing input for a required parameter, you will be prompted to enter the information before execution.
Tip
In order to determine whether a parameter is required, named, or positional, supports wildcards, or accepts input from the pipeline, you can use the Get-Help
cmdlet which is covered in the next recipe in this chapter.
Multiple data types are used for input depending on the parameter you are working with. Some parameters accept string values, while others accept integers or Boolean values. Boolean parameters are used when you need to set a parameter value to either true or false. PowerShell provides built-in shell variables for each of these values using the $true
and $false
automatic variables.
Note
For a complete list of PowerShell v3 automatic variables, run Get-Help about_automatic_variables
. Also see Appendix A, Common Shell Information, for a list of automatic variables added by the Exchange Management Shell.
For example, you can enable or disable a send connector using the Set-SendConnector
cmdlet with the -Enabled
parameter:
Set-SendConnector Internet -Enabled $false
Switch parameters don't require a value. Instead they are used to turn something on or off, or to either enable or disable a feature or setting. One common example of when you might use a switch parameter is when creating an archive mailbox for a user:
Enable-Mailbox testuser -Archive
PowerShell also provides a set of common parameters that can be used with every cmdlet. Some of the common parameters, such as the risk mitigation parameters (-Confirm
and -Whatif
), only work with cmdlets that make changes.
Risk mitigation parameters allow you to preview a change or confirm a change that may be destructive. If you want to see what will happen when executing a command without actually executing it, use the -WhatIfparameter
:

When making a change, such as removing a mailbox, you'll be prompted for confirmation, as shown in the following screenshot:

To suppress this confirmation set the -Confirm
parameter to false:
Remove-Mailbox testuser -Confirm:$false
Notice here that when assigning the $false
variable to the -Confirm
parameter, we had to use a colon immediately after the parameter name and then the Boolean value. This is different to how we assigned this value earlier with the -Enabled
parameter when using the Set-SendConnector
cmdlet. Remember that the -Confirm
parameter always requires this special syntax, and while most parameters that accept a Boolean value generally do not require this, it depends on the cmdlet with which you are working. Fortunately, PowerShell has a great built-in help system that we can use when we run into these inconsistencies. When in doubt, use the help system, which is covered in detail in the next recipe.
Cmdlets and parameters support tab completion. You can start typing the first few characters of a cmdlet or a parameter name and hit the tab key to automatically complete the name or tab through a list of available names. This is very helpful in terms of discovery and can serve as a bit of a time saver.
In addition, you only need to type enough characters of a parameter name to differentiate it from another parameter name. The following command using a partial parameter name is completely valid:
Set-Mailbox -id testuser –Office Sales
Here we've used id
as a shortcut for the -Identity
parameter. The cmdlet does not provide any other parameters that start with id
, so it automatically assumes you want to use the -Identity
parameter.
Another helpful feature that some parameters support is the use of wildcards. When running the Get-Mailbox
cmdlet, the -Identity
parameter can be used with wildcards to return multiple mailboxes that match a certain pattern:
Get-Mailbox -id t*
In this example, all mailboxes starting with the letter "t" will be returned. Although this is fairly straightforward, you can refer to the help system for details on using wildcard characters in PowerShell by running Get-Help about_Wildcards
.
Parameter values containing a space need to be enclosed in either single or double quotation marks. The following command would retrieve all of the mailboxes in the Sales Users OU in Active Directory. Notice that since the OU name contains a space, it is enclosed in single quotes:
Get-Mailbox -OrganizationalUnit 'contoso.com/Sales Users/Phoenix'
Use double quotes when you need to expand a variable within a string:
$City = 'Phoenix' Get-Mailbox -OrganizationalUnit "contoso.com/Sales Users/$City"
You can see here that we first create a variable containing the name of the city, which represents a sub OU under Sales Users
. Next, we include the variable inside the string used for the organizational unit when running the Get-Mailbox
cmdlet. PowerShell automatically expands the variable name inside the double quoted string where the value should appear and all mailboxes inside the Phoenix
OU are returned by the command.
The single most important concept in PowerShell is the use of its flexible, object-based pipeline. You may have used pipelines in Unix-based shells, or when working with the cmd.exe
command prompt. The concept of pipelines is similar to that of sending the output from one command to another. But, instead of passing plain text, PowerShell works with objects, and we can accomplish some very complex tasks in just a single line of code. In this recipe, you'll learn how to use pipelines to string together multiple commands and build powerful one-liners.
The following pipeline command would set the office location for every mailbox in the DB1
database:
Get-Mailbox -Database DB1 | Set-Mailbox -Office Headquarters
In a pipeline, you separate a series of commands using the pipe (|
) character. In the previous example, the Get-Mailbox
cmdlet returns a collection of mailbox objects. Each mailbox object contains several properties that contain information such as the name of the mailbox, the location of the associated user account in Active Directory, and more. The Set-Mailbox
cmdlet is designed to accept input from the Get-Mailbox
cmdlet in a pipeline, and with one simple command we can pass along an entire collection of mailboxes that can be modified in one operation.
You can also pipe output to filtering commands, such as the Where-Object
cmdlet. In this example, the command retrieves only the mailboxes with a MaxSendSize
equal to 10
megabytes:
Get-Mailbox | Where-Object{$_.MaxSendSize -eq 10mb}
The code that the Where-Object
cmdlet uses to perform the filtering is enclosed in curly braces ({}
). This is called a script block, and the code within this script block is evaluated for each object that comes across the pipeline. If the result of the expression is evaluated as true, the object is returned; otherwise, it is ignored. In this example, we access the MaxSendSize
property of each mailbox using the $_
object, which is an automatic variable that refers to the current object in the pipeline. We use the equals (-eq
) comparison operator to check that the MaxSendSize
property of each mailbox is equal to 10 megabytes. If so, only those mailboxes are returned by the command.
Tip
Comparison operators allow you to compare results and find values that match a pattern. For a complete list of comparison operators, run Get-Helpabout_Comparison_Operators
.
When running this command, which can also be referred to as a one-liner, each mailbox object is processed one at a time using stream processing. This means that as soon as a match is found, the mailbox information is displayed on the screen. Without this behavior, you would have to wait for every mailbox to be found before seeing any results. This may not matter if you are working in a very small environment, but without this functionality in a large organization with tens of thousands of mailboxes, you would have to wait a long time for the entire result set to be collected and returned.
One other interesting thing to note about the comparison being done inside our Where-Object
filter is the use of the mb
multiplier suffix. PowerShell natively supports these multipliers and they make it a lot easier for us to work with large numbers. In this example, we've used 10mb
, which is the equivalent of entering the value in bytes because behind the scenes, PowerShell is doing the math for us by replacing this value with 1024
*1024
*10
. PowerShell provides support for the following multipliers: kb
, mb
, gb
, tb
, and pb
.
You can use advanced pipelining techniques to send objects across the pipeline to other cmdlets that do not support direct pipeline input. For example, the following one-liner adds a list of users to a group:
Get-User | Where-Object{$_.title -eq "Exchange Admin"} | Foreach-Object{ Add-RoleGroupMember -Identity "Organization Management" ` -Member $_.name }
This pipeline command starts off with a simple filter that returns only the users that have their title
set to "Exchange Admin"
. The output from that command is then piped to the ForEach-Object
cmdlet that processes each object in the collection. Similar to the Where-Object
cmdlet, the ForEach-Object
cmdlet processes each item from the pipeline using a script block. Instead of filtering, this time we are running a command for each user object returned in the collection and adding them to the "Organization Management"
role group.
Using aliases in pipelines can be helpful because it reduces the number of characters you need to type. Take a look at the following command where the previous command is modified to use aliases:
Get-User | ?{$_.title -eq "Exchange Admin"} | %{ Add-RoleGroupMember -Identity "Organization Management" ` -Member $_.name }
Notice the use of the question mark (?
) and the percent sign (%
) characters. The ?
character is an alias for the Where-Object
cmdlet, and the %
character is an alias for the ForEach-Object
cmdlet. These cmdlets are used heavily, and you'll often see them used with these aliases because it makes the commands easier to type.
Tip
You can use the Get-Alias
cmdlet to find all of the aliases currently defined in your shell session and the New-Alias
cmdlet to create custom aliases.
The Where-Object
and ForEach-Object
cmdlets have additional aliases. Here's another way you could run the previous command:
Get-User | where{$_.title -eq "Exchange Admin"} | foreach{ Add-RoleGroupMember -Identity "Organization Management" ` -Member $_.name }
Use aliases when you're working interactively in the shell to speed up your work and keep your commands concise. You may want to consider using the full cmdlet names in production scripts to avoid confusing others who may read your code.
The Looping through items recipe
The Creating custom objects recipe
The Dealing with concurrent pipelines in remote PowerShell recipe in Chapter 2, Exchange Management Shell Common Tasks
Every scripting language makes use of variables as placeholders for data, and PowerShell is no exception. You'll need to work with variables often to save temporary data to an object so you can work with it later. PowerShell is very different from other command shells in that everything you touch is, in fact, a rich object with properties and methods. In PowerShell, a variable is simply an instance of an object just like everything else. The properties of an object contain various bits of information depending on the type of object you're working with. In this recipe we'll learn to create user-defined variables and work with objects in the Exchange Management Shell.
To create a variable that stores an instance of the testuser
mailbox, use the following command:
$mailbox = Get-Mailbox testuser
To create a variable, or an instance of an object, you prefix the variable name with the dollar sign ($
). To the right of the variable name, use the equals (=
) assignment operator, followed by the value or object that should be assigned to the variable. Keep in mind that the variables you create are only available during your current shell session and will be destroyed when you close the shell.
Let's look at another example. To create a string variable that contains an e-mail address, use the following command:
$email = "testuser@contoso.com"
Tip
In addition to user-defined variables, PowerShell also includes automatic and preference variables. To learn more, run Get-Helpabout_Automatic_Variables
and Get-Helpabout_Preference_Variables
.
Even a simple string variable is an object with properties and methods. For instance, every string has a Length
property that will return the number of characters that are in the string:
[PS] C:\>$email.length 20
When accessing the properties of an object, you can use dot notation to reference the property with which you want to work. This is done by typing the object name, then a period, followed by the property name, as shown in the previous example. You access methods in the same way, except that the method names always end with parenthesis ().
The string data type supports several methods, such as Substring
, Replace
, and Split
. The following example shows how the Split
method can be used to split a string:
[PS] C:\>$email.Split("@") testuser contoso.com
You can see here that the Split
method uses the "@"
portion of the string as a delimiter and returns two substrings as a result.
At this point, you know how to access the properties and methods of an object, but you need to be able to discover and work with these members. To determine which properties and methods are accessible on a given object, you can use the Get-Member
cmdlet, which is one of the key discovery tools in PowerShell along with Get-Help
and Get-Command
.
To retrieve the members of an object, pipe the object to the Get-Member
cmdlet. The following command will retrieve all of the instance members of the $mailbox
object we created earlier:
$mailbox | Get-Member
Tip
To filter the results returned by Get-Member
, use the -MemberType
parameter to specify whether the type should be a Property
or a Method
.
Let's take a look at a practical example of how we could use Get-Member
to discover the methods of an object. Imagine that each mailbox in our environment has had a custom MaxSendSize
restriction set and we need to record the value for reporting purposes. When accessing the MaxSendSize
property, the following information is returned:
[PS] C:\>$mailbox.MaxSendSize IsUnlimited Value ----------- ----- False 10 MB (10,485,760 bytes)
We can see here that the MaxSendSize
property actually contains an object with two properties: IsUnlimited
and Value
. Based on what we've learned, we should be able to access the information for the Value
property using dot notation:
[PS] C:\>$mailbox.MaxSendSize.Value 10 MB (10,485,760 bytes)
That works, but the information returned contains not only the value in megabytes, but also the total bytes for the MaxSendSize
value. For the purpose of what we are trying to accomplish, we only need the total megabytes. Let's see if this object provides any methods that can help us out with this using Get-Member
:

From the output shown in the previous screenshot, we can see this object supports several methods that can be used convert the value. To obtain the MaxSendSize
value in megabytes, we can call the ToMB
method:
[PS] C:\>$mailbox.MaxSendSize.Value.ToMB() 10
In a traditional shell, you would have to perform complex string parsing to extract this type of information, but PowerShell and the .NET Framework make this much easier. As you'll see over time, this is one of the reasons why PowerShell's object-based nature really outshines a typical text-based command shell.
An important thing to point about this last example is that it would not work if the mailbox had not had a custom MaxSendSize
limitation configured. Nevertheless, this provides a good illustration of the process you'll want to use when you're trying to learn about an object's properties or methods.
As mentioned in the Understanding command syntax and parameters recipe in this chapter, PowerShell uses quoting rules to determine how variables should be handled inside a quoted string. When enclosing a simple variable inside a double-quoted string, PowerShell will expand that variable and replace the variable with the value of the string. Let's take a look at how this works by starting off with a simple example:
[PS] C:\>$name = "Bob" [PS] C:\> "The user name is $name" The user name is Bob
This is pretty straightforward. We stored the string value of "Bob"
inside the $name
variable. We then include the $name
variable inside a double-quoted string that contains a message. When we hit return, the $name
variable is expanded and we get back the message we expect to see on the screen.
Now let's try this with a more complex object. Let's say that we want to store an instance of a mailbox object in a variable and access the PrimarySmtpAddress
property inside the quoted string:
[PS] C:\>$mailbox = Get-Mailbox testuser [PS] C:\>"The email address is $mailbox.PrimarySmtpAddress" The email address is test user.PrimarySmtpAddress
Notice here that when we try to access the PrimarySmtpAddress
property of our mailbox object inside the double-quoted string, we're not getting back the information that we'd expect. This is a very common stumbling block when it comes to working with objects and properties inside strings. We can get around this using sub-expression notation. This requires that you enclose the entire object within $()
characters inside the string:
[PS] C:\>"The email address is $($mailbox.PrimarySmtpAddress)" The email address is testuser@contoso.com
Using this syntax, the PrimarySmtpAddress
property of the $mailbox
object is properly expanded and the correct information is returned. This technique will be useful later when extracting data from objects and generating reports or logfiles.
PowerShell will automatically try to select the correct data type for a variable based on the value being assigned to it. You don't have to worry about doing this yourself, but we do have the ability to explicitly assign a type to a variable if needed. This is done by specifying the data type in square brackets before the variable name:
[string]$a = 32
Here we've assigned the value of 32
to the $a
variable. Had we not strongly typed the variable using the [string]
type shortcut, $a
would have been created using the Int32
data type, since the value we assigned was a number that was not enclosed in single or double quotes. Take a look at the following screenshot:

As you can see here, the $var1
variable is initially created without any explicit typing. We use the GetType()
method, which can be used on any object in the shell, to determine the data type of $var1
. Since the value assigned was a number not enclosed in quotes, it was created using the Int32
data type. When using the [string]
type shortcut to create $var2
with the same value, you can see that it has now been created as a string.
It is good to have an understanding of data types because when building scripts that return objects, you may need to have some control over this. For example, you may want to report on the amount of free disk space on an Exchange server. If we store this value in the property of a custom object as a string, we lose the ability to sort on that value. There are several examples throughout the book that use this technique.
See Appendix A, Common Shell Information, for a listing of commonly-used type shortcuts.
One of the most common PowerShell questions is how to get information returned from commands in the desired output on the screen. In this recipe, we'll take a look at how you can output data from commands and format that information for viewing on the screen.
To change the default output and view the properties of an object in list format, pipe the command to the Format-List
cmdlet:
Get-Mailbox testuser | Format-List
To view specific properties in table format, supply a comma-separated list of property names as parameters, as shown next when using Format-Table
:
Get-Mailbox testuser | Format-Table name,alias
When you run the Get-Mailbox
cmdlet, you only see the Name
, Alias
, ServerName
, and ProhibitSendQuota
properties of each mailbox in a table format. This is because the Get-Mailbox
cmdlet receives its formatting instructions from the exchange.format.ps1xml
file located in the Exchange server bin directory.
PowerShell cmdlets use a variety of formatting files that usually include a default view with only a small subset of predefined properties. When you need to override the default view, you can use Format-List
and Format-Table
cmdlets.
You can also select specific properties with Format-List
, just as we saw when using the Format-Table
cmdlet. The difference is, of course, that the output will be displayed in list format.
Let's take a look at the output from the Format-Table
cmdlet, as shown previously:

As you can see here, we get both properties of the mailbox formatted as a table.
When using Format-Table
cmdlet, you may find it useful to use the -Autosize
parameter to organize the columns based on the width of the data:

This command selects the same properties as our previous example, but this time we are using the -Autosize
parameter and the columns are adjusted to use only as much space on the screen as is needed. Remember, you can use the ft
alias instead of typing the entire Format-Table
cmdlet name. You can also use the fl
alias for the Format-List
cmdlet. Both of these aliases can keep your commands concise and are very convenient when working interactively in the shell.
One thing to keep in mind is that you never want to use the Format-*
cmdlets in the middle of a pipeline since most other cmdlets will not understand what to do with the output. The Format-*
cmdlets should normally be the last thing you do in a command unless you are sending the output to a printer or a text file.
To send formatted output to a text file, you can use the Out-File
cmdlet. In the following command, the Format-List
cmdlet uses the asterisk (*
) character as a wildcard and exports all of the property values for the mailbox to a text file:
Get-Mailbox testuser | fl * | Out-File c:\mb.txt
To add data to the end of an existing file, use the -Append
parameter with the Out-File
cmdlet. Even though we're using the Out-File
cmdlet here, the traditional cmd
output redirection operators such as >
and >>
can still be used. The difference is that the cmdlet gives you a little more control over the output method and provides parameters for tasks, including setting the encoding of the file.
You can sort the output of a command using the Sort-Object
cmdlet. For example, this command will display all mailbox databases in alphabetical order:
Get-MailboxDatabase | sort name | ft name
We are using the sort
alias for the Sort-Object
cmdlet specifying name
as the property we want to sort. To reverse the sort order, use the descending switch parameter:
Get-MailboxDatabase | sort name -desc | ft name
The Understanding the pipeline recipe
The Exporting reports to text and CSV files recipe in Chapter 2, Exchange Management Shell Common Tasks
Like many other scripting and programming languages, Windows PowerShell allows you to work with arrays and hash tables. An array is a collection of values that can be stored in a single object. A hash table is also known as an associative array, and is a dictionary that stores a set of key-value pairs. You'll need to have a good grasp of arrays so that you can effectively manage objects in bulk and gain maximum efficiency in the shell. In this recipe, we'll take a look at how we can use both types of arrays to store and retrieve data.
You can initialize an array that stores a set of items by assigning multiple values to a variable. All you need to do is separate each value with a comma. The following command would create an array of server names:
$servers = "EX1","EX2","EX3"
To create an empty hash table, use the following syntax:
$hashtable = @{}
Now that we have an empty hash table, we can add key-value pairs:
$hashtable["server1"] = 1 $hashtable["server2"] = 2 $hashtable["server3"] = 3
Notice in this example that we can assign a value based on a key name, not using an index number as we saw with a regular array. Alternatively, we can create this same object using a single command using the following syntax:
$hashtable = @{server1 = 1; server2 = 2; server3 = 3}
You can see here that we used a semicolon (;
) to separate each key-value pair. This is only required if the entire hash table is created in one line.
You can break this up into multiple lines to make it easier to read:
$hashtable = @{ server1 = 1 server2 = 2 server3 = 3 }
Let's start off by looking at how arrays work in PowerShell. When working with arrays, you can access specific items and add or remove elements. In our first example, we assigned a list of server names to the $servers
array. To view all of the items in the array, simply type the variable name and hit return:
[PS] C:\>$servers EX1 EX2 EX3
Array indexing allows you to access a specific element of an array using its index number inside square brackets ([]
). PowerShell arrays are zero-based, meaning that the first item in the array starts at index zero. For example, use the second index to access the third element of the array, as shown next:
[PS] C:\>$servers[2] EX3
To assign a value to a specific element of the array, use the equals (=
) assignment operator. We can change the value from the last example using the following syntax:
[PS] C:\>$servers[2] = "EX4" [PS] C:\>$servers[2] EX4
Let's add another server to this array. To append a value, use the plus equals (+=
) assignment operator as shown here:
[PS] C:\>$servers += "EX5" [PS] C:\>$servers EX1 EX2 EX4 EX5
To determine how many items are in an array, we can access the Count
property to retrieve the total number of array elements:
[PS] C:\>$servers.Count 4
We can loop through each element in the array with the ForEach-Object
cmdlet and display the value in a string:
$servers | ForEach-Object {"Server Name: $_"}
We can also check for a value in an array using the -Contains
or -NotContains
conditional operators:
[PS] C:\>$servers -contains "EX1" True
In this example, we are working with a one-dimensional array, which is what you'll commonly be dealing with in the Exchange Management Shell. PowerShell supports more complex array types such as jagged and multidimensional arrays, but these are beyond the scope of what you'll need to know for the examples in this book.
Now that we've figured out how arrays work, let's take a closer look at hash tables. When viewing the output for a hash table, the items are returned in no particular order. You'll notice this when viewing the hash table we created earlier:
[PS] C:\>$hashtable Name Value ---- ----- server2 2 server1 1 server3 3
If you want to sort the hash table, you can call the GetEnumerator
method and sort by the Value
property:
[PS] C:\>$hashtable.GetEnumerator() | sort value Name Value ---- ----- server1 1 server2 2 server3 3
Hash tables can be used when creating custom objects, or to provide a set of parameter names and values using parameter splatting. Instead of specifying parameter names one by one with a cmdlet, you can use a hash table with keys that match the parameter's names and their associated values will automatically be used for input:
$parameters = @{ Title = "Manager" Department = "Sales" Office = "Headquarters" } Set-User testuser @parameters
This command automatically populates the parameter values for Title
, Department
, and Office
when running the Set-User
cmdlet for the testuser
mailbox.
For more details and examples for working with hash tables, run Get-Help about_Hash_Tables
.
You can think of a collection as an array created from the output of a command. For example, the Get-Mailbox
cmdlet can be used to create an object that stores a collection of mailboxes, and we can work with this object just as we would with any other array. You'll notice that, when working with collections, such as a set of mailboxes, you can access each mailbox instance as an array element. Consider the following example:

First, we retrieve a list of mailboxes that start with the letter t
and assign that to the $mailboxes
variable. From looking at the items in the $mailboxes
object, we can see that the testuser
mailbox is the second mailbox in the collection.
Since arrays are zero-based, we can access that item using the first index, as shown next:

If your command only returns one item, then the output can no longer be accessed using array notation. In the following example, the $mailboxes
object contains only one mailbox and will display an error when trying to access an item using array notation:

Even though it will only store one item, you can initialize this object as an array, using the following syntax:

You can see here that we've wrapped the command inside the @()
characters to ensure that PowerShell will always interpret the $mailboxes
object as an array. This can be useful when you're building a script that always needs to work with an object as an array, regardless of the number of items returned from the command that created the object. Since the $mailboxes
object has been initialized as an array, you can add and remove elements as needed.
We can also add and remove items to multi-valued properties, just as we would with a normal array. To add an e-mail address to the testuser
mailbox, we can use the following commands:
$mailbox = Get-Mailbox testuser $mailbox.EmailAddresses += "testuser@contoso.com" Set-Mailbox testuser -EmailAddresses $mailbox.EmailAddresses
In this example, we created an instance of the testuser
mailbox by assigning the command to the $mailbox
object. We can then work with the EmailAddresses
property to view, add, and remove e-mail addresses from this mailbox. You can see here that the plus equals (+=
) operator was used to append a value to the EmailAddresses
property.
We can also remove that value using the minus equals (-=
) operator:
$mailbox.EmailAddresses -= "testuser@contoso.com" Set-Mailbox testuser -EmailAddresses $mailbox.EmailAddresses
Tip
There is actually an easier way to add and remove e-mail addresses on recipient objects. See Adding and removing recipient e-mail addresses in Chapter 3, Managing Recipients for details.
We've covered the core concepts in this section that you'll need to know when working with arrays. For more details run Get-Helpabout_arrays
.
Loop processing is a concept that you will need to master in order to write scripts and one-liners with efficiency. You'll need to use loops to iterate over each item in an array or a collection of items, and then run one or more commands within a script block against each of those objects. In this recipe, we'll take a look at how you can use foreach
loops and the ForEach-Object
cmdlet to process items in a collection.
The foreach
statement is a language construct used to iterate through values in a collection of items. The following example shows the syntax used to loop through a collection of mailboxes, returning only the name of each mailbox:
foreach($mailbox in Get-Mailbox) {$mailbox.Name}
In addition, you can take advantage of the PowerShell pipeline and perform loop processing using the ForEach-Object
cmdlet. This example produces the same result as the one shown previously:
Get-Mailbox | ForEach-Object {$_.Name}
You will often see the given command written using an alias of the ForEach-Object
cmdlet, such as the percent sign (%
):
Get-Mailbox | %{$_.Name}
The first part of a foreach
statement is enclosed in parenthesis and represents a variable and a collection. In the previous example, the collection is the list of mailboxes returned from the Get-Mailbox
cmdlet. The script block contains the commands that will be run for every item in the collection of mailboxes. Inside the script block, the $mailbox
object is assigned the value of the current item being processed in the loop. This allows you to access each mailbox one at a time using the $mailbox
variable.
When you need to perform loop processing within a pipeline, you can use the ForEach-Object
cmdlet. The concept is similar, but the syntax is different because objects in the collection are coming across the pipeline.
The ForEach-Object
cmdlet allows you to process each item in a collection using the $_
automatic variable, which represents the current object in the pipeline. The ForEach-Object
cmdlet is probably one of the most commonly-used cmdlets in PowerShell, and we'll rely on it heavily in many examples throughout the book.
The code inside the script block used with both looping methods can be more complex than just a simple expression. The script block can contain a series of commands or an entire script. Consider the following code:
Get-MailboxDatabase -Status | %{ $DBName = $_.Name $whiteSpace = $_.AvailableNewMailboxSpace.ToMb() "The $DBName database has $whiteSpace MB of total white space" }
In this example, we're looping through each mailbox database in the organization using the ForEach-Object
cmdlet. Inside the script block, we've created multiple variables, calculated the total megabytes of whitespace in each database, and returned a custom message that includes the database name and corresponding whitespace value. This is a simple example, but keep in mind that inside the script block you can run other cmdlets, work with variables, create custom objects, and more.
PowerShell also supports other language constructs for processing items such as for
, while
, and do
loops. Although these can be useful in some cases, we won't rely on them much for the remaining examples in this book. You can read more about them and view examples using the get-help about_for
, get-help about_while
, and get-helpabout_do
commands in the shell.
There are some key differences about the foreach
statement and the ForEach-Object
cmdlet that you'll want to be aware of when you need to work with loops. First, the ForEach-Object
cmdlet can process one object at a time as it comes across the pipeline. When you process a collection using the foreach
statement, this is the exact opposite. The foreach
statement requires that all of the objects that need to be processed within a loop are collected and stored in memory before processing begins. We'll want to take advantage of the PowerShell pipeline and its streaming behavior whenever possible since it is much more efficient.
The other thing to take note of is that in PowerShell, foreach
is not only a keyword, but also an alias. This can be a little counterintuitive, especially when you are new to PowerShell and you run into a code sample that uses the following syntax:
Get-Mailbox | foreach {$_.Name}
At first glance, this might seem like we're using the foreach
keyword, but we're actually using an alias for the ForEach-Object
cmdlet. The easiest way to remember this distinction is that the foreach
language construct is always used before a pipeline. If you use foreach
after a pipeline, PowerShell will use the foreach
alias which corresponds to the ForEach-Object
cmdlet.
You can accomplish many tasks by executing individual cmdlets or running multiple commands in a pipeline, but there may be times where you want to create a script that performs a series of operations or that loads a library of functions and predefined variables and aliases into the shell. In this recipe, we'll take a look at how you can create and run scripts in the shell.
Let's start off by creating a basic script that automates a multi-step process. We'll start up a text editor, such as Notepad, and enter the following code:
param( $name, $maxsendsize, $maxreceivesize, $city, $state, $title, $department ) Set-Mailbox -Identity $name ` -MaxSendSize $maxsendsize ` -MaxReceiveSize $maxreceivesize Set-User -Identity $name ` -City $city ` -StateOrProvince $state ` -Title $title ` -Department $department Add-DistributionGroupMember -Identity DL_Sales ` -Member $name
Next, we'll save the file on the
C:\
drive using the nameUpdate-SalesMailbox.ps1
.We can then run this script and provide input using parameters that have been declared using the
param
keyword:C:\Update-SalesMailbox.ps1 -name testuser ` -maxsendsize 25mb ` -maxreceivesize 25mb ` -city Phoenix ` -state AZ ` -title Manager ` -department Sales
When the script runs, the specified mailbox will be updated with the settings provided.
The concept of a PowerShell script is similar to batch files used with cmd.exe
, but, instead of a .bat
extension, PowerShell scripts use a .ps1
extension. To create a script, you can use a basic text editor such as Notepad or you can use the Windows PowerShell Integrated Scripting Environment (ISE).
Just like a function, our script accepts a number of parameters. As you can see from the code, we're using this script to automate a task that modifies several properties of a mailbox and add it to a distribution group. Since this requires the use of three separate cmdlets, it makes sense to use a script to automate this task.
If we wanted to run this script against a collection of mailboxes, we could use a foreach
loop, as shown:
foreach($i in Get-Mailbox -OrganizationalUnit contoso.com/sales) { c:\Update-SalesMailbox.ps1 -name $i.name ` -maxsendsize 100mb ` -maxreceivesize 100mb ` -city Phoenix ` -state AZ ` -title 'Sales Rep' ` -department Sales }
Here you can see we're simply looping through each mailbox in the Sales
OU and running the script against each one. You can modify the script to run any number of cmdlets. Also, keep in mind that although we're using parameters with our script, they are not required.
Think of a script as the body of a function. We can use the same three phases of execution such as Begin
, Process
, and End
blocks, and add as many parameters as required. You may find it easier to create all of your code in the form of functions as opposed to scripts, although one of the nice things about scripts is that they can easily be scheduled to run as a task using the task scheduler.
Here's something that seems a little strange at first and might take a little getting used to. When you want to execute a PowerShell script in the current directory, you need to prefix the command with a dot slash (.\
) as shown:
[PS] C:\>.\New-SalesMailbox.ps1
We can use either the forward or backslash characters; it doesn't matter which. This is just a security mechanism which prevents you from executing a script in an unknown location. As you might expect, you can still run a script using its full path, just as you would with an executable or batch file.
Another thing to be aware of is the concept of dot-sourcing a script. This gives us the ability to execute commands in a script and also load any custom aliases, functions, or variables that are present within the script into your PowerShell session. To dot-source a script, use the dot operator: type a period, followed by a space, and then the path to the script as shown next:
[PS] C:\>. .\functions.ps1
This technique can be used to load functions, modules, variables, and aliases from within other scripts.
Windows PowerShell implements script security to keep unwanted scripts from running in your environment. You have the option of signing your scripts with a digital signature to ensure that scripts that are run are from a trusted source. In order to implement this functionality, PowerShell provides four script execution modes that can be enabled:
Restricted: In this mode the scripts will not run even if they are digitally signed
AllSigned: In this mode all scripts must be digitally signed
RemoteSigned: In this mode you can run local scripts, but scripts downloaded from the Internet will not run
Unrestricted: In this mode all scripts will run whether they are signed or not, or have been downloaded from an Internet site
The default execution policy on a machine is Restricted
. When you install Exchange 2013 on a server, or the Exchange Management Tools on a workstation, the execution policy is automatically set to RemoteSigned
. This is required by Exchange in order to implement the remote shell functionality.
It is possible to manage Exchange 2013 through PowerShell remoting on a workstation or server without Exchange Tools installed. In this case, you'll need to make sure your script execution policy is set to either RemoteSigned
or Unrestricted
. To set the execution policy, use the following command:
Set-ExecutionPolicy RemoteSigned
Make sure you do not change the execution policy to AllSigned
on machines where you'll be using the Exchange cmdlets. This will interfere with importing the commands through a remote PowerShell connection which is required for the Exchange Management Shell cmdlets to run properly.
You can reference the help system on this topic by running Get-Helpabout_Execution_Policies
.
Flow control statements are used in the shell to run one or more commands based on the result of a conditional test. You can use the If
statement to test one or more conditional statements, and you can also use switch
statements when multiple If
statements would otherwise be required. This recipe will show you how to control the flow of execution that your scripts will use in the shell.
Let's store the status of a database called DB1
in a variable that can be used to perform some conditional checks:
$DB1 = Get-MailboxDatabase DB1 -Status
When using an If
statement, you use the If
keyword followed by an expression enclosed in parenthesis that performs a conditional check. If the expression is evaluated as true, any commands in the proceeding script block will be executed:
if($DB1.DatabaseSize -gt 5gb) { "The Database is larger than 5gb" }
You can use the ElseIf
keyword to add another conditional check:
if($DB1.DatabaseSize -gt 5gb) { "The Database is larger than 5gb" } elseif($DB1.DatabaseSize -gt 10gb) { "The Database is larger than 10gb" }
You can also add the Else
statement
to run commands if none of the conditions evaluate as true:
if($DB1.DatabaseSize -gt 5gb) { "The Database is larger than 5gb" } elseif($DB1.DatabaseSize -gt 10gb) { "The Database is larger than 10gb" } else { "The Database is not larger than 5gb or 10gb" }
If you need to check more than a few conditions, you may want to consider using a switch
statement instead of a series of If
and ElseIf
statements:
switch($DB1.DatabaseSize) { {$_ -gt 5gb} {"Larger than 5gb"; break} {$_ -gt 10gb} {"Larger than 10gb"; break} {$_ -gt 15gb} {"Larger than 15gb"; break} {$_ -gt 20gb} {"Larger than 20gb"; break} Default {"Smaller than 5gb"} }
To control the flow and execution of commands in your scripts, you can use the If
, Elseif
, and Else
conditional statements. The syntax of an If
statement is pretty straightforward. Let's break it down into simple terms. In the first example, we're simply asking PowerShell if the database size of DB1
is greater than 5 gigabytes, and, if it is, to output a string with the message "The database is larger than 5gb"
.
In the second example, we extend this logic by simply asking another question: if the database size of DB1
is greater than 10 gigabytes, output a string with the message "The database is larger than 10gb"
.
Next, we use an Else
statement that will only run commands if either the If
or ElseIf
statements do not evaluate to true. If that's the case we simply output a string with the message "The database is not larger than 5gb or 10gb"
.
One interesting thing to point out here is that the code within parenthesis is like any other expression we might type into the shell. There's no requirement to first create a variable, as shown previously. We could just do something like this:
if((Get-MailboxDatabase DB1 -Status).DatabaseSize -gt 5gb) { "The database is larger than 5gb" }
Since we know that the Get-MailboxDatabase
cmdlet can return an object with a DatabaseSize
property, we can simply wrap the command in parenthesis and access the property directly using dot notation. This is a technique that can cut down on the amount of code you write and greatly speed up your work when you are typing commands interactively into the shell.
It's possible to use multiple ElseIf
statements to run a series of multiple conditional checks, but the switch statement is much better suited for this task. The switch
statement syntax may be a little harder to understand. After using the switch
keyword, you specify the object that you want to perform multiple conditional checks against. Each line within the body of switch
can evaluate an expression or check for a precise value. If an expression evaluates to true or a match is found, any commands in the associated script block will run.
In our previous example, we evaluated a number of expressions to determine if the size of the database was greater than a specific value. Notice that in each script block we used the break
keyword. This means that we exit the switch statement immediately after an expression has been evaluated as true and any following checks will be skipped. Finally, the last item in the switch uses the Default
keyword which will only run if the previous expressions are false.
You can also use a switch
statement that will run commands when matching a specific value. Take a look at the following code:
$number = 3 switch ($number) { 1 {"One" ; break} 2 {"Two" ; break} 3 {"Three" ; break} 4 {"Four" ; break} 5 {"Five" ; break} Default {"No matches found"} }
In this example, the $number
variable is set to 3
. When the switch
statement runs, the word Three
will be returned. If $number
had been set to a value that was not defined, such as 42
, the Default
script block would run and output the string "No Matches Found"
.
Switch
statements can also be used to perform complex matches with regular expressions, wildcards, exact matches, case sensitive values, and data read in from external files. For more details, run Get-HelpAbout_Switch
.
Let's take a look at a more practical example of how you might use flow control statements in a real script. Here we'll loop through each mailbox in the organization to configure some of the mailbox quota settings:
foreach ($mailbox in Get-Mailbox) { if($mailbox.office -eq "Sales") { Set-Mailbox $mailbox -ProhibitSendReceiveQuota 5gb ` -UseDatabaseQuotaDefaults $false } elseif($mailbox.office -eq "Accounting") { Set-Mailbox $mailbox -ProhibitSendReceiveQuota 2gb ` -UseDatabaseQuotaDefaults $false } else { Set-Mailbox $mailbox -UseDatabaseQuotaDefaults $true } }
In this example we are checking to see if the Office
setting for each mailbox is set to "Sales"
using the If
statement. If so, ProhibitSendReceiveQuota
is set to 5gb
. If not, the ElseIf
statement will check that the Office
setting is set to "Accounting"
, and, if it is, ProhibitSendReceiveQuota
is set to 2gb
. If the Office
setting is not set to either of these values, we can configure the mailbox to use database quota defaults.
The fact that PowerShell is an object-based shell gives us a great deal of flexibility when it comes to writing one-liners, scripts, and functions. When generating detailed reports, we need to be able to customize the data output from our code so it can be formatted or piped to other commands that can export the data in a clean, structured format. We also need to be able to control and customize the output from our code so that we can merge data from multiple sources into a single object. In this recipe, you'll learn a few techniques used to build custom objects.
The first thing we'll do is create a collection of mailbox objects that will be used as the data source for a new set of custom objects:
$mailboxes = Get-Mailbox
You can add custom properties to any object coming across the pipeline using calculated properties. This can be done using either the Select-Object
or Format-Table
cmdlets:
$mailboxes | Select-Object Name, Database, @{name="Title";expression={(Get-User $_.Name).Title}}, @{name="Dept";expression={(Get-User $_.Name).Department}}
Another easy way to do this is by assigning a hash table to the -Property
parameter of the New-Object
cmdlet:
$mailboxes | %{ New-Object PSObject -Property @{ Name = $_.Name Database = $_.Database Title = (Get-User $_.Name).Title Dept = (Get-User $_.Name).Department } }
You can also use the New-Object
cmdlet to create an empty custom object, and then use the Add-Member
cmdlet to tack on any custom properties that are required:
$mailboxes | %{ $obj = New-Object PSObject $obj | Add-Member NoteProperty Name $_.Name $obj | Add-Member NoteProperty Database $_.Database $obj | Add-Member NoteProperty Title (Get-User $_.Name).Title $obj | Add-Member NoteProperty Dept (Get-User $_.Name).Department Write-Output $obj }
Each of these three code samples will output the same custom objects that combine data retrieved from both the Get-Mailbox
and Get-User
cmdlets. Assuming that the Title
and Department
fields have been defined for each user, the output would look similar to the following:

The reason we're building a custom object here is because we want to merge data from multiple sources into a single object. The Get-Mailbox
cmdlet does not return the Title
or Department
properties that are tied to a user account: the Get-User
cmdlet needs to be used to retrieve that information. Since we may want to generate a report that includes information from both the Get-Mailbox
and Get-User
cmdlets for each individual user, it makes sense to build a custom object that contains all of the required information. We can then pipe these objects to other cmdlets that can be used to export this information to a file.
We can modify one of our previous code samples and pipe the output to a CSV file used to document this information for the current user population:
$mailboxes | Select-Object Name, Database, @{n="Title";e={(Get-User $_.Name).Title}}, @{n="Dept";e={(Get-User $_.Name).Department}} | Export-CSV –Path C:\report.csv -NoType
Keep in mind that even though you can also create calculated properties using the Format-Table
cmdlet, you'll want to use Select-Object
, as shown previously, when converting these objects to CSV or HTML reports. These conversion cmdlets do not understand the formatting information returned by the Format-Table
cmdlet, and you'll end up with a lot of useless data if you try to do this.
When building custom objects with the Select-Object
cmdlet, we can select existing properties from objects coming across the pipeline and also add one or more calculated properties. This is done by using a hash table that defines a custom property name in the hash table key and a script block within the hash table value. The script block is an expression where you can run one or more commands to define the custom property value. In our previous example, you can see that we've called the Get-User
cmdlet to retrieve both the Title
and Department
properties for a user that will be assigned to calculated properties on a new object.
The syntax for creating a calculated property looks a little strange at first glance since it uses name
and expression
keywords to create a hash table that defines the calculated property. You can abbreviate these keywords as shown next:
$mailboxes | Select-Object Name, Database, @{n="Title";e={(Get-User $_.Name).Title}}, @{n="Dept";e={(Get-User $_.Name).Department}}
The property name uses the string value assigned to n
, and the property value is assigned to e
using a script block. Abbreviating these keywords with n
and e
just makes it easier to type. You can also use label
or l
to provide the calculated property name.
Using the New-Object
cmdlet and assigning a hash table to the -Property
parameter is a quick and easy way to create a custom object. The only issue with this technique is that the properties can be returned in a random order. This is due to how the .NET Framework assigns random numeric values to hash table keys behind the scenes, and the properties are sorted based on those values, not in the order that you've defined them. The only way to get the properties back in the order you want is to continue to pipe the command to Select-Object
and select the property names in order, or use one of the other techniques shown in this recipe.
Creating an empty custom object and manually adding note properties with the Add-Member
cmdlet can require a lot of extra typing, so generally this syntax is not widely used. This technique becomes useful when you want to add script methods or script properties to a custom object, but this is an advanced technique that we won't need to utilize for the recipes in the remainder of this book.
There is another useful technique for creating custom objects which utilizes the Select-Object
cmdlet. Take a look at the following code:
$mailboxes | %{ $obj = "" | Select-Object Name,Database,Title,Dept $obj.Name = $_.Name $obj.Database = $_.Database $obj.Title = (Get-User $_.Name).Title $obj.Dept = (Get-User $_.Name).Department Write-Output $obj }
You can create a custom object by piping an empty string variable to the Select-Object
cmdlet, specifying the property names that should be included. The next step is to simply assign values to the properties of the object using the property names that you've defined. This code loops through the items in our $mailboxes
object and returns a custom object for each one. The output from this code returns the same exact objects as all of the previous examples.
One of the reasons we first stored the collection of mailboxes in the $mailbox
variable is due to the way PowerShell deals with multiple cmdlets executing through a remote session. Ideally, we would just do the following:
Get-Mailbox | %{ New-Object PSObject -Property @{ Name = $_.Name Database = $_.Database Title = (Get-User $_.Name).Title Dept = (Get-User $_.Name).Department } }
Unfortunately, even though this syntax is completely valid, it will not work consistently in the Exchange Management Shell. This is because, as the Get-Mailbox
cmdlet is sending objects down the pipeline to ForEach-Object
, we're also trying to run the Get-User
cmdlet to build our custom object, and PowerShell remoting does not support more than one pipeline executing at a time. To get around this, use the technique shown previously to save the results of the first command to a variable, and then pipe that variable to ForEach-Object
. For more details on this, refer to the recipe entitled Dealing with concurrent pipelines in remote PowerShell in Chapter 2, Exchange Management Shell Common Tasks.
Functions are used to combine a series of commands into a reusable block of code that can be called using a single command. Functions can make a configuration change or return one or more objects that can either be displayed in the console or exported to an external file. You can assign the output of functions to a variable, or pipe a function to another cmdlet. In this recipe, you'll learn how to create a PowerShell function.
To create a function, you need to use the function
keyword, followed by the name of the function, and then the function code enclosed within curly braces {}
. For example, this very basic function displays three properties of a mailbox in list format:
function Get-MailboxList { param($name) Get-Mailbox $name | fl Name,Alias,ServerName }
When running the function, you must supply the identity of the mailbox as a parameter. The mailbox Name
, Alias
, and ServerName
are displayed in a list.
PowerShell functions give us the ability to run a sequence of commands that can be called using a single function name. We can add input parameters to our own functions and also process pipeline input. This gives us the ability to write our own reusable functions that can behave just like a cmdlet.
There are a few ways you can add functions into your shell session. First, you can save your functions inside a .ps1
script. To make them available in the shell, your script just needs to be "dotted", or dot sourced. You do this by typing a period, a space, and then the path to the file. There has to be a space between the dot and the filename, otherwise it won't work. See the recipe Creating and running scripts for an example.
Another convenient method for adding functions to your shell session is to use a profile. PowerShell profiles are actually just a .ps1
script that gets executed when you start the shell. If you don't have a profile set up, check out the recipe entitled Setting up a profile.
If you're working interactively, you may find it convenient to simply copy and paste the function code straight into the shell. Keep in mind that if you do this, the function will only be available during the current session. If you close the shell and open a new instance, the function will no longer be available.
The best way to provide input to a function is to declare formal parameters. We did this with the previous example, and the $name
parameter was added to the function using the param
keyword. We can add a list of parameters using this syntax by separating each parameter name with a comma.
We can access informal function parameters inside the body of a function using the automatic $args
variable, which is an array that contains an element for each unbound argument passed to a function. While this can be useful in some cases, formal parameters provide much more flexibility. Formal parameters with descriptive names are easier to understand; they can be initialized with default values and support several attributes such as the position ID, whether or not they accept pipeline input, and whether they are required or optional.
In other scripting or programming languages, it is sometimes required to use a keyword to return a value from a function, but we don't have to do this in PowerShell. Let's say we've called the Get-Mailbox
cmdlet inside the body of a function, without capturing the output in a variable. In this case, the return value for the function will be the data returned by the cmdlet. You can explicitly return an object using the Write-Output
cmdlet and, although it makes for good readability when viewing the code, it is not required.
PowerShell functions can be written to accept and process pipeline input using three stages of execution by utilizing Begin
, Process
, and End
blocks, each of which is described next:
Begin: The
Begin
block runs only once, at the beginning of the function. Any customization or initialization can happen here.Process: The
Process
block runs once for each object in the pipeline. Each object that comes through the pipeline can be accessed using the$_
automatic variable.End: The
End
block runs after all of the objects in the pipeline have been processed.
We can create a simple pipeline function using only the Process
block. The Begin
and End
blocks are optional. For example, the following function will return the name for each mailbox sent across the pipeline:
function Get-MailboxName { process { "Mailbox Name: $($_.Name)" } }
We can pipe the Get-Mailbox
command to this function and each mailbox name will be returned:

Let's take a look at a practical example that combines the Get-MailboxStatistics
and Set-Mailbox
cmdlets into a function used to automate a task and demonstrate the capabilities of PowerShell functions. The following function will set the ProhibitSendReceiveQuota
limit for a mailbox, given values for the mailbox name and desired quota size. The function will only modify a mailbox if the total mailbox size does not already exceed the value provided for the quota setting:
function Set-SendReceiveQuota { param( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName = $true)] $name, [Parameter(Mandatory=$true)] $quota ) begin { $count = 0 Write-Output "Started: $(Get-Date -format T)" } process { $count += 1 $mailboxstatistics = Get-MailboxStatistics $name $total = $mailboxstatistics.TotalItemSize.Value.ToMB() if($total -lt $quota) { Set-Mailbox $name -ProhibitSendReceiveQuota $quota ` -UseDatabaseQuotaDefaults $false } } end { Write-Output "Ended: $(Get-Date -format T)" Write-Output "Mailboxes Processed: $count" } }
You can see in this example that we've added the [Parameter()]
attribute in order to define characteristics for each parameter. In this case, both parameters are mandatory and the $name
parameter will accept its value from the pipeline by property name.
Tip
Parameters can use a number of arguments and attributes. For a complete list, run Get-Helpabout_Functions_Advanced_Parameters
.
Like a cmdlet, this function can process pipeline input and it can also be run against one object at a time. Let's start off by running the function for a single mailbox:

The Begin
block runs only once, immediately at the beginning, and the start time is returned as soon as the function is called. Within the Process
block, the code is run once and we increment the $count
variable to keep track of how many objects have been processed. The End
block is run last, reporting the total number of items that have been processed. We can see from the output in the previous screenshot that the function processed one mailbox and the operation only took one second to complete.
Now let's run the function for a collection of mailboxes:

The syntax of the command is very different this time. We pipe all of the mailboxes starting with the letter t
to the Set-SendReceiveQuota
function. Notice that we've only specified the -quota
parameter. This is because the $name
parameter will automatically receive a value from each mailbox object's Name
property as it comes across the pipeline. Looking at the output again, you can see that the operation took one second to complete, and we modified three mailboxes in the process.
PowerShell functions are a very broad topic and could easily be the focus of an entire chapter. We've covered some key points about functions here, but to learn more, run Get-Helpabout_functions
and Get-Helpabout_functions_advanced
.
You can use a PowerShell profile to customize your shell environment and to load functions, modules, aliases, and variables into the environment when you start your Exchange Management Shell session. In this recipe, we'll take a look at how you can create a profile.
Profiles are not created by default, but you may want to verify one has not already been created. Start off by running the Test-Path
cmdlet:
Test-Path $profile
If the Test-Path
cmdlet returns $true
, then a profile has already been created for the current user. You can open an existing profile by invoking notepad.exe
from the shell:
notepad $profile
If the Test-Path
cmdlet returns $false
, you can create a new profile for the current user by running the following command:
New-Item -type file –path $profile -force
A PowerShell profile is just a script with a .ps1
extension that is run every time you start the shell. You can think of a profile as a logon script for your PowerShell or Exchange Management Shell session. Inside your profile you can add custom aliases, define variables, load modules, or add your own functions so that they will be available every time you start the shell. In the previous example, we used the automatic shell $profile
variable to create a profile script for the current user, which in this case would create the profile in $env:UserProfile\Documents\WindowsPowerShell\directory
.
Since PowerShell is simply executing a .ps1
script to load your profile, your execution policy must allow the execution of scripts on your machine. If it does not, your profile will not be loaded when starting the shell and you'll receive an error.
There are four types of profiles that can be used with PowerShell:
$Profile.AllUsersAllHosts
: This profile applies to all users and all shells and is located in$env:Windir\system32\WindowsPowerShell\v1.0\profile.ps1
$Profile.AllUsersCurrentHost
: This profile applies to all users and only thePowerShell.exe
host and is located in$env:Windir\system32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
$Profile.CurrentUserAllHosts
: This profile applies to the current user and all shells and is located in$env:UserProfile\Documents\WindowsPowerShell\profile.ps1
$Profile.CurrentUserCurrentHost
: This profile applies to the current user and only to thePowerShell.exe
host and is located in$env:UserProfile\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Using the $profile
variable alone to create the profile will default to the CurrentUserCurrentHost
location and is probably the most commonly used profile type. If you need to create a profile for all the users on a machine, use one of the AllUsers
profile types.
You may be wondering at this point what the difference is between the "Current Host" and "All Hosts" profile types. The PowerShell runtime can be hosted within third-party applications, so the "All Hosts" profile types apply to those instances of PowerShell. The "Current Host" profile types can be used with PowerShell.exe
and when you are running the Exchange Management Shell.
In addition to defining custom aliases or functions in a profile, you may want to consider loading any other modules that may be useful. For example, you may want to load the Active Directory module for PowerShell so that those cmdlets are also available to you whenever you start the shell.
When you're done making changes to your profile, save and close the file. In order for the changes to take effect, you can either restart the shell, or you can dot-source the script to reload the profile:
. $profile
You can create multiple .ps1
scripts that include aliases, functions, and variables and then dot-source these scripts within your profile to have them loaded every time you start your PowerShell session.
You can reference the help system on this topic by running Get-Helpabout_profiles
.
Trying to remember all of the profile types and their associated script paths can be a little tough. There's actually a pretty neat trick that you can use with the $profile
variable to view all of the profile types and file paths in the shell. To do this, access the psextended
property of the $profile
object:
$profile.psextended | Format-List
This will give you a list of each profile type and the path of the .ps1
script that should be used to create the profile.