Sunday, July 10, 2011

Running Exchange Online and Office365 Powershell cmdlets in C# and managed code

When you’re looking at automating Office365 and Exchange Online from managed code you need to be aware of the 2 sets of cmdlets that you may need to use depending on the tasks that your trying to perform. Most of the administration of ExchangeOnline is done using remote powershell where Exchange Online provides a subset of the normal on-premise Exchange 2010 SP1 cmdlets. The other cmdlet set to be aware of is the MSOnline powershell module which you need download and install http://onlinehelp.microsoft.com/en-us/office365-enterprises/hh124998.aspx. The MSOnline module contains cmdlets to allow administration of the wider Office365 service and perform more of the directory/service and service provider functions more akin to Active directory management in a on premise environment (eg adding users to groups etc).

So when using this from Managed code to use Remote powershell against Exchange Online you use the standard code you would use against an on-premise Exchange 2010 deployment against the endpoint https://ps.outlook.com. With the Office365 MSOnline module you need to load this into a runspace and then firstly use the Connect-MsolService cmdlet to connect to and authenticate against Office365. Then you execute as per normal the desired cmdlets.

Here's some sample code the first sample uses remote powershell to connect to ExchangeOnline.

System.Security.SecureString secureString = new System.Security.SecureString();
string myPassword = "password";
foreach (char c in myPassword)
secureString.AppendChar(c);
PSCredential credential = new PSCredential("glen@domain.com", secureString);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://ps.outlook.com/PowerShell-LiveID?PSVersion=2.0"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;

connectionInfo.MaximumConnectionRedirectionCount = 4;
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
// Make a Get-Mailbox requst using the Server Argument
Command gmGetMailbox = new Command("get-mailbox");
gmGetMailbox.Parameters.Add("ResultSize", "Unlimited");
Pipeline plPileLine = runspace.CreatePipeline();
plPileLine.Commands.Add(gmGetMailbox);
Collection RsResultsresults = plPileLine.Invoke();
Dictionary gmResults = new Dictionary();
foreach (PSObject obj in RsResultsresults)
{
gmResults.Add(obj.Members["WindowsEmailAddress"].Value.ToString(), obj);
}
plPileLine.Stop();
plPileLine.Dispose();

This second example loads the MSOnline powershell module into a runspace

InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new[] { "MSOnline" });
using (Runspace psRunSpace = RunspaceFactory.CreateRunspace(iss))
{
psRunSpace.Open();
using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
{
powershell.Runspace = psRunSpace;
Command connect = new Command("Connect-MsolService");
System.Security.SecureString secureString = new System.Security.SecureString();
string myPassword = "password";
foreach (char c in myPassword)
secureString.AppendChar(c);

connect.Parameters.Add("Credential", new PSCredential("glen@domain.com", secureString));
powershell.Commands.AddCommand(connect);
Collection results = null;
Collection errors = null;
results = powershell.Invoke();
errors = powershell.Streams.Error.ReadAll();
powershell.Commands.Clear();
Command getuser = new Command("Get-MsolUser");
getuser.Parameters.Add("MaxResults", 100);
powershell.Commands.AddCommand(getuser);
results = null;
errors = null;
results = powershell.Invoke();
}
}
}





11 comments:

Elijah said...

Hi Glen, thanks for these helpful examples.

Do you think it would be possible to create a web app that multiple people could use at once where the web app would utilize an "always open" remote powershell pipeline to the cloud? In other words, is there a way I can avoid creating a new connection to the cloud every time the web app needs to issue a set of commands? It takes several seconds to create the connection to the cloud and I think that could cause http timeouts.

Glen said...

Yes but you will need to spend some time designing it and you may have potential security issues around doing such a thing. Eg write you own back channel web or WCF service and connection manager etc.

Cheers
Glen

Ayajahmed Shaikh said...

Hi Glen, thanks for such a great article.

I have installed Microsoft online Service module for Windows power shell and I am trying "Connect-MsolService" command using your code in C# but I am getting following error for importing "MSOnline"



System.Management.Automation.Runspaces.RunspaceOpenModuleLoadException: One or more errors occurred processing the module 'MSOnline' specified in the InitialSessionState object used to create this runspace. See the ErrorRecords property for a complete list of errors. The first error was: The specified module 'MSOnline' was not loaded because no valid module file was found in any module directory.
at System.Management.Automation.Runspaces.LocalRunspace.DoOpenHelper()
at System.Management.Automation.Runspaces.LocalRunspace.OpenHelper(Boolean syncCall)
at System.Management.Automation.Runspaces.RunspaceBase.CoreOpen(Boolean syncCall)
at System.Management.Automation.Runspaces.RunspaceBase.Open()


what could be the problem since I could able to connect through power shell but not able to connect through c# code.

Lost Code said...

...but you have the password there. How to use user account who has sign in into web page credentials to that? :)

Glen said...

You can't at least not from a script as its not portable across to the power-shell session

Cheers
Glen

Anonymous said...

Hello Based on your code I wrote a CreateUser method that I am invoking from c# web service. I am getting following error

false100stambi - The term 'import-module MSOnline' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. at System.Management.Automation.CommandDiscovery.LookupCommandInfo(String commandName, CommandOrigin commandOrigin)
at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(String commandName, CommandOrigin commandOrigin, Nullable`1 useLocalScope)
at System.Management.Automation.CommandFactory._CreateCommand(String commandName, CommandOrigin commandOrigin, Nullable`1 useLocalScope)
at System.Management.Automation.Runspaces.Command.CreateCommandProcessor(ExecutionContext executionContext, CommandFactory commandFactory, Boolean addToHistory)
at System.Management.Automation.Runspaces.LocalPipeline.CreatePipelineProcessor()
at System.Management.Automation.Runspaces.LocalPipeline.InvokeHelper()
at System.Management.Automation.Runspaces.LocalPipeline.InvokeThreadProc()



Here is my code

System.Security.SecureString secureString = new System.Security.SecureString();

InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new[] { "MSOnline" });
using (Runspace psRunSpace = RunspaceFactory.CreateRunspace(iss))
{
psRunSpace.Open();
using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
{
Collection results = null;
Collection errors = null;

powershell.Runspace = psRunSpace;

if(settings.debugflag)
settings.LogInfo("Admin ID: " + this.adminuser + " AdminPass: " + this.adminpassword,100);

foreach (char c in adminpassword)
secureString.AppendChar(c);

Command connect = new Command("Connect-MsolService");

connect.Parameters.Add("Credential", new PSCredential(adminuser, secureString));
powershell.Commands.AddCommand(connect);

results = powershell.Invoke();
errors = powershell.Streams.Error.ReadAll();
powershell.Commands.Clear();

Command cuser = new Command("New-MsolUser");
cuser.Parameters.Add("UserPrincipalName", userprincipalname);
cuser.Parameters.Add("DisplayName", displayname);
cuser.Parameters.Add("firstName", firstname);
cuser.Parameters.Add("lastName", lastname);
cuser.Parameters.Add("Password", userpassword);
powershell.Commands.AddCommand(cuser);
results = null;
errors = null;
results = powershell.Invoke();
foreach (PSObject obj in results)
{
if (obj.Members["userPrincipalName"].Value.ToString().Equals(userprincipalname))
return true;
}
return true;

Glen said...

Check the version of the module you installed. Eg if you have installed the 64bit version of the module you need to make sure that the project your using in being compiled as x64

Cheers
Glen

Bella said...

hi. thanks for this post. I Tried the first code, but I got an error for this part

Collection RsResultsresults = plPileLine.Invoke();
Dictionary gmResults = new Dictionary();

Error 1:
Using the generic type 'System.Collections.ObjectModel.Collection' requires 1 type arguments

Error 2:
Using the generic type 'System.Collections.Generic.Dictionary' requires 2 type arguments

Error 3:
Using the generic type 'System.Collections.Generic.Dictionary' requires 2 type arguments

WaiveStar said...

Hi Glen, good work on the above.
Any idea how I would run a command on each result? eg to assign a sendas permission for each mailbox using a PS command:
Get-Mailbox -ResultSize Unlimited | Add-RecipientPermission -AccessRights SendAs -Trustee "trustee"
However I cant think of a way to do it using the above code.
Please help

Glen Scales said...

You need to pass the results collection of one cmd into the other cmd using the inputobject parrameter

Anonymous said...

Is is possible to Connect-MSOnline in the o365 runspace only ?