Editor's Note: The following is a guest post by Exchange Server MVP Mike Pfeiffer as part of the MVP Award Program Blog's "MVPs for Exchange Online" series. Mike Pfeiffer has been in the IT field for over 13 years, spending most of his time as an enterprise consultant focused on Active Directory and Exchange implementation and migration projects. He is a Microsoft Certified Master on Exchange 2010, and a Microsoft Exchange MVP. You can find his writings online at http://www.mikepfeiffer.net , where he blogs regularly about Exchange Server and PowerShell-related topics.
One of the great things about Exchange Online is that it provides many of the application development capabilities that are available through an Exchange 2010 on-premises environment. This is made possible mainly through the functionality provided by Exchange Web Services (EWS). Just take a look at the developer resources for Exchange Online on the Exchange Server Developer Center. Many of the technical articles showcase the EWS Managed API, which provides the ability to send e-mail messages, create inbox rules, search mailbox folders, and much more, through managed code.
In addition to using EWS, we can also build applications that utilize the Exchange Management Shell cmdlets on servers in the cloud, and this provides even more automation capabilities and the ability to perform administrative tasks. Just like the on-premises version of Exchange 2010, Exchange Online can be managed with remote PowerShell, and the cmdlets can easily be called from a .NET application. In this article, I’ll take you through the process of building a simple console application written in C# that will invoke Exchange cmdlets in the cloud. Other than Visual Studio, PowerShell v2 and an internet connection are the only prerequisites. In these examples, we'll be using Visual Studio 2010 on a Windows 7 machine, which has PowerShell v2 installed by default.
Creating an Application
The first thing we’ll need to do is start Visual Studio 2010. Go to File > New > Project, or press Ctrl+Shift+N to create a new project. Select the Console Application template, type a name for your application (I’ll use o365Shell), and click Ok.
Before we start writing code, we need to do a couple of things. First, we’ll need to add a reference to the System.Management.Automation.dll assembly. In the Solution Explorer, right click on your project and click on Add Reference:
Under the Assemblies tab, click on Browse:
On a Windows 7 machine, the System.Management.Automation.dll assembly is located in sub-folder under %windir%\assembly\GAC_MSIL\System.Management.Automation. Locate this file and click on Open:
That will take you back to the Add Reference screen where you can click on Add:
When viewing the Solution Explorer, you should see that the reference has been successfully added to the project:
Finally, we’ll add some namespace references to the top of our Program.cs file:
using System.Management.Automation;
using System.Management.Automation.Remoting;
using System.Management.Automation.Runspaces;
using System.Collections.ObjectModel;
using System.Security;
Connecting to Exchange Online
At this point, we’re ready to write some code. All of the following examples should be added within the Main method of your application. Since we're making a connection to a remote server, we'll obviously need to authenticate before we can start running commands. This first code sample will create an object instance of the PSCredential class. This will store our username, and a password encrypted with the SecureString class. Make sure you replace the username and password provided with one of your Microsoft Online Portal Administrator accounts:
string userName = "admin@uclabs.onmicrosoft.com";
string password = "P@ssw0rd";
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
PSCredential credential = new PSCredential(userName, securePassword);
Next, we'll create an instance of the WSManConnectionInfo class, which will provide the connection information that is required to create and connect to the remote runspace. We'll use our PSCredential object from the previous step to authenticate to the cloud:
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(
new Uri("https://ps.outlook.com/powershell"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
connectionInfo.MaximumConnectionRedirectionCount = 2;
There are a few interesting things to point out here. Notice that we’ve used https://ps.outlook.com/powershell as the endpoint Uri, along with setting the MaximumConnectionRedirectionCount property of the connectionInfo object to a value of 2. This will allow us to establish a connection to the initial Exchange Online endpoint, and then be redirected to the appropriate Exchange Online server for our Office 365 tenant.
Running a Cmdlet
Now that we've created our connectionInfo object, we'll use it to create a runspace on a remote Exchange Online server. The following code will open the runspace and invoke a cmdlet:
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
//Create the command and add a parameter
powershell.AddCommand("Get-Mailbox");
powershell.AddParameter("RecipientTypeDetails", "UserMailbox");
//Invoke the command and store the results in a PSObject collection
Collection<PSObject> results = powershell.Invoke();
//Iterate through the results and write the DisplayName and PrimarySMTP
//address for each mailbox
foreach (PSObject result in results)
{
Console.WriteLine(
string.Format("Name: {0}, PrimarySmtpAddress: {1}",
result.Properties["DisplayName"].Value.ToString(),
result.Properties["PrimarySmtpAddress"].Value.ToString()
));
}
}
}
This code runs a single command; Get-Mailbox, with just one parameter. Basically, we're asking for a list of user mailboxes, excluding other types such as discovery and resource mailboxes, by setting the RecipientTypeDetails parameter to UserMailbox. The command output is stored in a PSObject collection, and it will provide access to multiple properties for each mailbox returned by the command. When we run the application, we iterate through each PSObject result and write out the DisplayName and PrimarySmtpAddress property values for each mailbox. The output should look similar to the following:
Running Multiple Cmdlets in a Pipeline Command
So, what about stringing multiple cmdlets together to build a one-liner? Well, this only requires a few slight modifications to our previous example. What we want to do is add multiple Command objects for each cmdlet we want to invoke. The following code disables the ActiveSync protocol for all the users in the cloud using a pipeline command:
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
//First command
Command getCasMailbox = new Command("Get-CasMailbox");
powershell.Commands.AddCommand(getCasMailbox);
//Second command and parameter
Command setCasMailbox = new Command("Set-CasMailbox");
setCasMailbox.Parameters.Add(new CommandParameter("ActiveSyncEnabled", false));
powershell.Commands.AddCommand(setCasMailbox);
//Invoke our one-liner
powershell.Invoke();
}
}
This time we're not retrieving any output, we're just modifying a setting for each mailbox. One of the ways you can build a pipeline command is to simply add one command at a time to the PowerShell object instance. Each time you use the AddCommand method, it's like you're adding a pipe character at the end of a command when you're working in the shell. Each Command objects Parameters property has an Add method, and you can tack on individual parameters one at a time, along with their associated values. The previous code sample accomplishes the same result as the following shell one-liner:
Get-CasMailbox | Set-CasMailbox -ActiveSyncEnabled $false
Remember, there's no limit to the number of cmdlets you can chain together in a pipeline command. This provides a lot of flexibility if you are developing an application that needs to automate administrative tasks in the cloud. I'd recommend testing commands like these in a remote PowerShell instance. Once you've determined the syntax is correct and everything works, move into Visual Studio and start writing code.
Dealing with Errors
Let's take our previous example a step further and add some error handling:
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
//First command
Command getCasMailbox = new Command("Get-CasMailbox");
powershell.Commands.AddCommand(getCasMailbox);
//Second command and parameter
Command setCasMailbox = new Command("Set-CasMailbox");
setCasMailbox.Parameters.Add(new CommandParameter("ActiveSyncEnabled", false));
powershell.Commands.AddCommand(setCasMailbox);
try
{
//Invoke our one-liner
Collection<PSObject> results = powershell.Invoke();
if (powershell.Streams.Error.Count > 0)
{
//Write command errors
foreach (ErrorRecord error in powershell.Streams.Error)
{
Console.WriteLine(error.ToString());
}
}
}
catch (RuntimeException ex)
{
//Write execption message
Console.WriteLine(ex.Message);
}
}
}
There are basically two types of errors we might need to deal with when invoking cmdlets from C# — command errors and exceptions. As you can see from the updated code sample, we're checking the Streams property of the PowerShell object to see if any command errors were generated. The errors we find in here will be related to invalid logic, or errors related to using bad parameter values. We've also added a try-catch block used to capture any exceptions thrown by our command. Any syntax errors, or calls to non-existent cmdlets, can be handled in the catch block.
Keep in mind that many of the on-premise cmdlets you're used to may not be available in the cloud. For example, server based cmdlets, such as Get-ExchangeServer, will not be visible since Exchange Online is a hosted solution. Take a look at the Reference to Available PowerShell Cmdlets in Exchange Online to get an idea of which cmdlets are available to you in the cloud.