Microsoft Exchange Server 2013 PowerShell Cookbook: Second Edition

By Jonas Andersson , Mike Pfeiffer
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. PowerShell Key Concepts

About this book

Microsoft Exchange Server 2013 is a complex messaging system. Windows PowerShell 3 can be used in conjunction with Exchange Server 2013 to automate and manage routine and complex tasks to save time, money, and eliminate errors.

Microsoft Exchange Server 2013 PowerShell Cookbook: Second Edition offers more than 120 recipes and solutions to everyday problems and tasks encountered in the management and administration of Exchange Server. If you want to write scripts that help you create mailboxes, monitor server resources, and generate detailed reports, then this Cookbook is for you.

This practical guide to Powershell and Exchange Server 2013 will help you automate and manage time-consuming and reoccurring tasks quickly and efficiently. Starting by going through key PowerShell concepts and the Exchange Management Shell, this book will get you automating tasks that used to take hours in no time.

With practical recipes on the management of recipients and mailboxes as well as distribution groups and address lists, this book will save you countless hours on repetitive tasks. Diving deeper, you will then manage your mailbox database, client access, and your transport servers with simple but effective scripts.

This book finishes with advanced recipes on Exchange Server problems such as server monitoring as well as maintaining high availability and security. If you want to control every aspect of Exchange Server 2013 and learn how to save time with PowerShell, then this cookbook is for you.

Publication date:
May 2013
Publisher
Packt
Pages
504
ISBN
9781849689427

 

Chapter 1. PowerShell Key Concepts

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

 

Introduction


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.

Performing some basic steps

To work with the code samples in this chapter, follow these steps to launch the Exchange Management Shell:

  1. Log on to a workstation or server with the Exchange Management Tools installed.

  2. 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 
    
  3. 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.

  4. 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.

 

Using the help system


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.

How to do it...

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.

How it works...

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's more...

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.

Getting help with cmdlets and functions

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.

See also

  • 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

 

Understanding command syntax and parameters


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.

How to do it...

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

How it works...

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.

Tip

For a complete list of common parameters, run Get-Helpabout_CommonParameters.

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.

There's more...

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.

Tip

Quoting rules are documented in detail in the PowerShell help system. Run Get-Helpabout_Quoting_Rules for more information.

See also

  • The Using the help system recipe

  • The Working with variables and objects recipe

 

Understanding the pipeline


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.

How to do it...

The following pipeline command would set the office location for every mailbox in the DB1 database:

Get-Mailbox -Database DB1 | Set-Mailbox -Office Headquarters

How it works...

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.

There's more...

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.

See also

  • 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

 

Working with variables and objects


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.

How to do it...

To create a variable that stores an instance of the testuser mailbox, use the following command:

$mailbox = Get-Mailbox testuser

How it works...

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 = "[email protected]"

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.

Tip

PowerShell also provides a -Split operator that can split a string into one or more substrings. Run Get-Helpabout_Split for details.

There's more...

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.

Variable expansion in strings

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 [email protected]

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.

Strongly typed variables

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.

 

Formatting output


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.

How to do it...

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

How it works...

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.

There's more…

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

See also

  • The Understanding the pipeline recipe

  • The Exporting reports to text and CSV files recipe in Chapter 2, Exchange Management Shell Common Tasks

 

Working with arrays and hash tables


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.

How to do it...

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
}

How it works...

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.

There's more…

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 += "[email protected]"
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 -= "[email protected]"
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.

See also

  • The Working with variables and objects recipe

  • The Creating custom objects recipe

 

Looping through items


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.

How to do it...

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}

How it works...

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's more…

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.

See also

  • The Working with arrays and hash tables recipe

  • The Understanding the pipeline recipe

  • The Creating custom objects recipe

 

Creating and running scripts


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.

How to do it...

  1. 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
    
  2. Next, we'll save the file on the C:\ drive using the name Update-SalesMailbox.ps1.

  3. 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
    
  4. When the script runs, the specified mailbox will be updated with the settings provided.

How it works...

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.

Tip

Comments can be added to a script using the pound (#) character.

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.

There's more…

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.

Execution policy

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.

See also

  • The Setting up a profile recipe

 

Using flow control statements


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.

How to do it...

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"}
}

How it works...

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.

There's more...

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.

Tip

Notice the use of the back tick (`) character used in the previous example with the Set-Mailbox cmdlet. This can be used as a line continuation character to break up long commands into multiple lines.

See also

  • The Looping through items recipe

 

Creating custom objects


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.

How to do it...

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:

How it works...

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's more...

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.

Watch out for concurrent pipeline errors

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.

See also

  • The Looping through items recipe

  • The Working with variables and objects recipe

  • The Exporting reports to text and CSV files recipe in Chapter 2, Exchange Management Shell Common Tasks

  • The Dealing with concurrent pipelines in remote PowerShell recipe in Chapter 2, Exchange Management Shell Common Tasks

 

Creating PowerShell functions


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.

How to do it...

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.

How it works...

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.

There's more…

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:

Taking it a step further

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.

See also

  • The Understanding the pipeline recipe

  • The Creating and running scripts recipe

  • The Setting up a profile recipe

 

Setting up a profile


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.

How to do it...

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

How it works...

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 the PowerShell.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 the PowerShell.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.

There's more…

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.

See also

  • The Creating and running scripts recipe

About the Authors

  • Jonas Andersson

    Jonas Andersson is devoted to constantly developing himself and his skills. He started out in the IT business in 2004 and initially worked in a support center, where he acquired a basic knowledge of the industry. In 2007, he started his career as a Microsoft Infrastructure consultant, and from 2008 onwards his focus has been on Unified Communication. At the start of 2010, he was employed at a large outsourcing company as a messaging specialist, specializing in Microsoft Exchange. His work included designing, implementing, and developing messaging solutions for enterprise customers. In 2014, he joined Microsoft Consulting Services, and from then onward his main focus has been Office 365. His role was of a deployment consultant with Microsoft's Office 365 Global Practice EMEA team. In 2016, he started to work for Sweden’s largest retail companies with a known brand as a Product Specialist for Office 365, mostly focusing on Skype for Business Online but also on the other workloads. As a reward for his work in the community, he was awarded the Microsoft Most Valuable Professional for the Microsoft Exchange Server product in 2014.

    Browse publications by this author
  • Mike Pfeiffer

    Mike Pfeiffer is a 20-year IT industry veteran, published author, and international conference speaker. He's a former architect for Amazon Web Services and engineer for Microsoft. Today, Pfeiffer serves as chief technologist for CloudSkills, a cloud computing training and consulting firm.

    Browse publications by this author
Book Title
Access this book and the full library for FREE
Access now