Web References
First, here are some helpful links to get started with invoking the management shell from C# code:
Creating Applications that Use the Default Host
http://msdn2.microsoft.com/en-us/library/ms714671.aspx
Mstehle's Introduction to Exchange Powershell Automation
Part 1: http://blogs.msdn.com/mstehle/archive/2007/01/25/fyi-introduction-to-exchange-powershell-automation-part-1.aspx
Part 2: http://blogs.msdn.com/mstehle/archive/2007/01/25/outbox-introduction-to-exchange-powershell-automation-part-2.aspx
Sample Code: Calling Exchange cmdlets from .Net code
http://www.viveksharma.com/techlog/2006/07/27/sample-code-calling-exchange-cmdlets-from-net-code
Caveats
- Creating the necessary Runspace and RunspaceInvoke instances will take 2-3 seconds to get setup in a compiled application.
- It can take 6-9 seconds to execute the first Exchange command on a computer with only the Exchange 2007 management tools installed. You may also receive the following errors in the event log:
Event Type: Error
Event Source: MSExchange ADAccess
Event Category: General
Event ID: 2152
Date: 3/26/2007
Time: 7:38:52 PM
User: N/A
Computer: HASTY
Description:
Process eIDAD.exe (PID=2984). A remote procedure call (RPC) request to the Microsoft Exchange Active Directory Topology service failed with error 1753 (Error 6d9 from HrGetServersForRole). Make sure that the Remote Procedure Call (RPC) service is running. In addition, make sure that the network ports that are used by RPC are not blocked by a firewall.
Event Type: Error
Event Source: MSExchange Common
Event Category: Devices
Event ID: 106
Date: 3/26/2007
Time: 7:38:57 PM
User: N/A
Computer: HASTY
Description:
Performance counter updating error. Counter name is Latest Exchange Topology Discovery Time in Seconds, category name is MSExchange Topology. Optional code: 2.
- Invoking the Runspace and adding the Exchange management snap-in does not give you the same environment as opening the Exchange Management Shell. One of the most striking differences is that the $AdminSessionADSettings environment settings are not available. By default, all Exchange management cmdlets will search the entire forest. You can best replicate this environment by starting a normal PowerShell instance and add the Microsoft.Exchange.Management.Powershell.Admin snap-in. (Run “Add-PSSnapin -Name:’Microsoft.Exchange.Management.PowerShell.Admin’ ”).
- To compile your code you must add a reference for Systems.Management.Automation. By default this is added to the global assembly cache when PowerShell is installed. The best way to add this DLL as a reference is to manually edit the .csproj file associated with the project and add the following line to the references itemgroup.
<reference include="System.Management.Automation">
Adding the reference in this manner will ensure that you are always using the appropriate DLL whether you are running on a 32 or 64 bit machine.
Birth of the wrapper class
My first attempt at integrating recipient management into our identity management provisioning code worked well but I paid quite a time penalty when invoking the shell and issuing commands to update/set attributes on each account. Updating a single account was not too bad (~1 second per account) after the Runspace was completely setup and all the Exchange DLLs were loaded. However, when updating all 60,000+ accounts the time penalty added up and became an unacceptable bottleneck. To prevent this, I implemented logic to check if each attribute needed to be updated before invoking the command to set the attribute. This logic removed the bottleneck and very soon the code was running just as quickly as before; except for the time needed to create the Runspace. These optimizations made the Runspace unnecessary for most instances when an update was run on a single account and no Exchange attributes were changed. Thus the wrapper class was setup to store a Runspace that would only be initialized if a command was invoked.
Notes About the Wrapper Class
- The wrapper class is a singleton and must me instantiated as such. Because the class is a singleton, only one Runspace will be created no matter how many times the class is instantiated.
- The Runspace is only created once the first command is invoked. The same is true with RunspaceInvoke.
- The wrapper class combines methods from both the Runspace and RunspaceInvoke classes.
The Wrapper Class
using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Runspaces;
namespace EMSDemo.Utility
{
public sealed class ExchangeManagementShellWrapper
{
#region Variable Declaration
private RunspaceConfiguration rc; //RunspaceConfiguration
private Runspace r; //Runspace
private RunspaceInvoke ri; //RunspaceInvoke
private static readonly ExchangeManagementShellWrapper instance = new ExchangeManagementShellWrapper(); //Singleton instance
#endregion
private ExchangeManagementShellWrapper(){}
public static ExchangeManagementShellWrapper Instance
{
get
{
return instance;
}
}
private void InitializeRunspace()
{
//Setup Runspace environment for Exchange Management Shell
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyResolver);
//Create RunspaceConfiguration
rc = RunspaceConfiguration.Create();
//Add PSSnapIn for Exchange 2007 and check for warnings
PSSnapInException warning;
PSSnapInInfo info = rc.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out warning);
if (warning != null)
{
// A warning is not expected, but if one is detected
// write the warning and return.
System.Console.Write(warning.Message);
return;
}
//Create and open runspace
r = RunspaceFactory.CreateRunspace(rc);
r.Open();
}
private void InitializeRunspaceInvoke()
{
//Create runspace if it has not already been created
if (r == null) { InitializeRunspace(); }
//Create RunspaceInvoke
ri = new RunspaceInvoke(r);
}
public ICollection<PSObject> RunspaceInvoke(string EMSCommand)
{
//Create RunspaceInvoke if it has not already been created
if (ri == null) { InitializeRunspaceInvoke(); }
//Invoke the commmand and return the results
return ri.Invoke(EMSCommand);
}
public ICollection<PSObject> RunspaceInvoke(string EMSCommand, IEnumerable input)
{
//Create RunspaceInvoke if it has not already been created
if (ri == null) { InitializeRunspaceInvoke(); }
//Invoke the commmand and return the results
return ri.Invoke(EMSCommand, input);
}
public ICollection<PSObject> RunspaceInvoke(string EMSCommand, out IList errors)
{
//Invoke the RunspaceInvoke method with a null input
return RunspaceInvoke(EMSCommand, null, out errors);
}
public ICollection<PSObject> RunspaceInvoke(string EMSCommand, IEnumerable input, out IList errors)
{
//Create RunspaceInvoke if it has not already been created
if (ri == null) { InitializeRunspaceInvoke(); }
//Invoke the commmand and return the results and errors
return ri.Invoke(EMSCommand, input, out errors);
}
public ICollection<PSObject> PipelineInvoke(Collection<Command> EMSCommands, out PipelineReader<object> Errors)
{
//Create Runspace if it has not already been created
if (r == null) { InitializeRunspace(); }
//Create Pipeline and add the commands
Pipeline pipeline = r.CreatePipeline();
foreach (Command item in EMSCommands)
{
pipeline.Commands.Add(item);
}
//Invoke the commands and return the results and errors
ICollection<PSObject> results = pipeline.Invoke();
Errors = pipeline.Error;
pipeline = null;
return results;
}
public ICollection<PSObject> PipelineInvoke(Collection<Command> EMSCommands)
{
//Create Runspace if it has not already been created
if (r == null) { InitializeRunspace(); }
//Create Pipeline and add the commands
Pipeline pipeline = r.CreatePipeline();
foreach (Command item in EMSCommands)
{
pipeline.Commands.Add(item);
}
//Invoke the commands and return the results
return pipeline.Invoke();
}
public ICollection<PSObject> PipelineInvoke(Command EMSCommand, out PipelineReader<object> Errors)
{
//Create Runspace if it has not already been created
if (r == null) { InitializeRunspace(); }
//Create Pipeline and add the command
Pipeline pipeline = r.CreatePipeline();
pipeline.Commands.Add(EMSCommand);
//Invoke the command and return the results and errors
ICollection<PSObject> results = pipeline.Invoke();
Errors = pipeline.Error;
pipeline = null;
return results;
}
public ICollection<PSObject> PipelineInvoke(Command EMSCommand)
{
//Create Runspace if it has not already been created
if (r == null) { InitializeRunspace(); }
//Create Pipeline and add teh command
Pipeline pipeline = r.CreatePipeline();
pipeline.Commands.Add(EMSCommand);
//Invoke the command and return the results
return pipeline.Invoke();
}
public void Dispose()
{
//Close the Runspace and cleanup
r.Close();
ri = null;
r = null;
}
private static System.Reflection.Assembly AssemblyResolver(object p, ResolveEventArgs args)
{
//Add path for the Exchange 2007 DLLs
if (args.Name.Contains("Microsoft.Exchange"))
{
return Assembly.LoadFrom(Path.Combine("C:\\Program Files\\Microsoft\\Exchange Server\\bin\\", args.Name.Split(',')[0] + ".dll"));
}
else
{
return null;
}
}
}
}
Examples
Each example will perform the same task and produce the same results using both the RuspaceInvoke and PipelineInvoke methods.
Initializing the Exchange Management Shell Wrapper
Singletons do not have a constructor so you must create an ‘instance’.
static void Main(string[] args)
{
//Initialize the ExchangeManagementShellWrapper
ExchangeManagementShellWrapper ems = ExchangeManagementShellWrapper.Instance;
ICollection<PSObject> results;
...
Invoking a Single Command
In this example both the RunspaceInvoke and PipelineInvoke methods are used to get information about the mailbox for ‘knsmith’. In each foreach loop are different methods for accessing the data within the PSObject.
//Use the RunspaceInvoke command with a command string
results = ems.RunspaceInvoke("Get-Mailbox knsmith");
foreach (PSObject item in results)
{
Console.WriteLine(item.Members["Name"].Value.ToString());
}
//Use the PipelineInvoke command with a Command object
Command EMSCommand = new Command("Get-Mailbox");
EMSCommand.Parameters.Add("Identity", "knsmith");
results = ems.PipelineInvoke(EMSCommand);
foreach (PSObject item in results)
{
//A different way of accessing property info from the results
PSPropertyInfo prop = (PSPropertyInfo)item.Properties["Name"];
if (prop != null)
{
Console.WriteLine(prop.Value.ToString());
}
}
Invoking Commands With Errors Ouput
In this example each command will attempt to create a new file at c:\temp called test.ps1. A new file will be created it one does not already exist. If the file does exist, an error will be returned and the output will be: “Error: The file ‘C:\temp\test.ps1’ already exists.
//Using RunspaceInvoke with errors
IList IErrors;
results = ems.RunspaceInvoke("New-Item -Path:'c:\\temp\\test.ps1' -Type:File", out IErrors);
foreach (PSObject item in results)
{
Console.WriteLine(item.Members["Name"].Value.ToString());
}
foreach (object item in IErrors)
{
Console.WriteLine("Error: {0}", item.ToString());
}
//Using PipelineInvoke with errors
EMSCommand = new Command("New-Item");
EMSCommand.Parameters.Add("Path", "c:\\temp\\test.ps1");
EMSCommand.Parameters.Add("Type", "File");
PipelineReader<object> errors;
results = ems.PipelineInvoke(EMSCommand, out errors);
foreach (PSObject item in results)
{
Console.WriteLine(item.Members["Name"].Value.ToString());
}
if (errors.Count > 0)
{
foreach (object obj in errors.ReadToEnd())
{
Console.WriteLine("Error: {0}", obj.ToString());
}
}
Invoking Multiple Commands
Invoking multiple commands using a pipe is one of my most common tasks in PowerShell. The RunspaceInvoke method is pretty straight forward as you can just pass in a command string the same as you use in the Exchange Management Shell. The PipelineInvoke method is a little different. You must create a Command object for each command you wish to issue. After the Command objects have been created, add them in the order they are to be executed to the collection.
//Using the RunspaceInvoke command with a command string
ems.RunspaceInvoke("Get-Mailbox knsmith | Set-Mailbox -EmailAddressPolicyEnabled:$True");
//Using PipelineInvoke with a Command Collection
Collection<Command> CommandsList = new Collection<Command>();
Command GetMailbox = new Command("Get-Mailbox");
GetMailbox.Parameters.Add("Identity", "knsmith");
CommandsList.Add(GetMailbox);
Command SetMailbox = new Command("Set-Mailbox");
SetMailbox.Parameters.Add("EmailAddressPolicyEnabled", true);
CommandsList.Add(SetMailbox);
ems.PipelineInvoke(CommandsList, out errors);
Passing an Input Object to the Runspace
The RunspaceInvoke method offers the added ability to pass in an input collection. The key to this is that you must start your command string with “$input | ” so that your input will be processed. This example will show how you can get the attributes for a mailbox, make a change and commit the changes by passing back the results and using the Set-Mailbox cmdlet.
//Use the RunspaceInvoke command with a command string
results = ems.RunspaceInvoke("Get-Mailbox knsmith");
foreach (PSObject item in results)
{
//Set the property to false
item.Members["UseDatabaseQuotaDefaults"].Value = false;
}
IList SetErrors;
//Save the changes we made to the results
//The $input variable will be equal to the 'results' variable we pass in
ems.RunspaceInvoke("$input | Set-Mailbox", results, out SetErrors);
foreach (object error in SetErrors)
{
Console.WriteLine(error.ToString());
}
Download the wrapper class and example code. (Right-click and 'Save Target As')
Conclusion
Whether or not you decide to use this wrapper class I hope it at least helps you with examples of how to utilize the RunspaceInvoke and Pipeline methods for invoking the Exchange management shell.
--Nick
63 comments:
Hi Nick,
Nice and useful article.
Had a question,
If i invoke a command from C# code, is there a way to get the result value as failed or passed?
Or is it if object count in IErrors is zero then its success.
After i invoke cmd mount-database or dismount-database i want to know if it was successful.
Thanks
WN
I would think that if the number of errors were equal to zero you could assume a successful operation. If you really wanted to be sure you could invoke a 'Get-MailboxDatabase <databaseID> -Status' cmdlet to check the mount status.
Remember to append a ' -Confirm:$False' to your cmdlet string when dismounting the database. Otherwise you will receive errors or an exception since you can't confirm the operation manually.
Nick,
I have used a similar techique for executing cmdlets; however, I have discovered that a cmdlet can fail without raising a terminating error or populating the error property on the pipeline that executed the command.
Is there any other ways to determine if the cmdlet actually worked in this situation? ie) checking the count property on the result set collection?
That really depends on the command you are trying to invoke. Not all commands will return a result set. I would suggest running a Get- command to ensure the values were set correctly.
Hi Nick,
Thanks for the article.
I have used your wrapper class to create mailbox in exchange 2007 from my c# code.
It works great in my development machine, with 32bit exchange server and visual studio 2005.
I am facing some issues in the production environment.
The exchange server in production is 64bit and 32bit powershell and 32 bit exchange management console is installed in the machine where my code runs.
My code runs without raising any exception, the invoke method returns with no result (PSObject with count 0).
I see following error messages in the Application eventlog:
An remote procedure call (RPC) request to the Microsoft Exchange Active Directory Topology service failed with error 1753 (Error 6d9 from HrGetServersForRole). Make sure that the Remote Procedure Call (RPC) service is running. In addition, make sure that the network ports that are used by RPC are not blocked by a firewall.
and
Performance counter updating error. Counter name is Latest Exchange Topology Discovery Time in Seconds, category name is MSExchange Topology. Optional code: 2.
I confirmed that the firewall is not blocking the RPC calls.
I also tried executing the code in the Exchange Server. I get the PSObject with count 0 and no error messages in the eventlog.
Please help me to resolve this.
Thanks
Thobias
Thobias,
The error messages you mentioned are pretty standard for a machine (32 or 64 bit) running only the management tools.
Do you see the same results when running the code on but your development workstation and production server? Is the code running as a user that has the appropriate Exchagne permissions? Do you get a PSObject with a count of 0 when running a simple command like "Get-Mailbox"?
Would you please email the string that you are passing to the invoke method to me at k.nick.smith at gmail.com. I'd be happy to help you troubleshoot.
--Nick
Is there a way to trap the WARNINGS (yellow text). This would be a useful if you are running a cmdlet like "Test-SystemHealth" that has useful information in the Errors and Warnings.
TIA,
Matt
Hi,
thank you for the wrapper. I use it in my C# Code to Mailbox Enable newly created users. On my production machine i get the following error. On my develop machine everything is fine. I suggest a problem with threading nut not sure. Can you help ?
Cannot load Windows PowerShell snap-in Microsoft.Exchange.Management.PowerShell.Admin because of the following error: Could not load file or assembly 'Microsoft.Exchange.PowerShell.Configuration, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. Failed to grant permission to execute. (Exception from HRESULT: 0x80131418)
Greetings
So what scope are we running the cmdlets in if we user your wrapper? Domain Scope or Forest Scope?
Neppoz,
Sorry, I missed your comment earlier. The two possibilities that come to mind are:
1) Did you install Exchange onto a different drive letter than C:\? If so you will need to change the path at the bottom on the wrapper in the AssemblyResolver method.
2) Are you running the code with an account that has permissions on the Exchange bin directory?
Let me know if any of these resolve your problem.
--Nick
Lushdog,
Commands executed from the wrapper class run in the forest scope.
--Nick
Hi Nick,
before i answer your questions i must say that i developed on my test machine. There i had no problem. I suppose it's a problem with the Powershell itself. Ok, lets see:
1) Exchnage installed normally. No paths adjusted
2) That's rather interesting. My Webapp (Sharepoint Webpart) is using impersonation. In fact the account that is visiting the site under normal circumstance has no rights on the Exchnage bin directory. By the way Exchange Machine is remote, not installed locally.
Let me now.
Greetings neppoz
Hello,
I tried to execute a simple Exchange 2007 Powershell command in my C# code:
Get-MailboxStatistics -Identity TestUser1
This Command works well in the Powershell command window.
I tried your wrapper, which is by the way really great! And I also tried the follwowing code. Each time the same. The mailbox could not be found. No results...
Paradoxically it works for some mailboxes and don't work for others. Whereas is ALWAYS works in the Powershell window on the same computer.
--------------------------
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
PSSnapInException snapInException = null;
PSSnapInInfo info =
rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out
snapInException);
Runspace myRunSpace = RunspaceFactory.CreateRunspace(rsConfig);
myRunSpace.Open();
Pipeline pipeLine = myRunSpace.CreatePipeline();
RunspaceInvoke ri = new RunspaceInvoke(myRunSpace);
Collection /PSObject/
commandResults =
ri.Invoke("Get-MailboxStatistics -Identity testuser1");
foreach (PSObject cmdlet in commandResults)
{
string cmdletName = cmdlet.Properties["DisplayName"].Value.ToString();
System.Diagnostics.Debug.Print(cmdletName);
}
------------------
But unfortunatly for some Users this command returns, that the mailbox could
not be found, even though the command works well in the Powershell window.
Where are differences in executing a command in the powershell window or in
code???
Alex
Just a new information:
This only appears, if the program runs in Debug mode. If the program is started outside VS2005 it works well....
Alex
Hi Nick, great post.
Is there a way to get the output of the commands?
I have the following code, which runs without error:
mailboxCommand = new Command("Get-MailboxServer");
CommandParameter userParam = new CommandParameter("Identity", serverName);
mailboxCommand.Parameters.Add(userParam);
pipeLine.Commands.Add(mailboxCommand);
formatlistCommand = new Command("Format-List");
CommandParameter formatParam = new CommandParameter("Property", "MessageTrackingLogPath");
formatlistCommand.Parameters.Add(formatParam);
pipeLine.Commands.Add(formatlistCommand);
commandResults = pipeLine.Invoke();
What I need to get is the value of the MessageTrackingLogPath property, so I can then process the logs. commandResults is a PSObject collection, which does not give me anything useful.
Neither obj.Properties["MessageTrackingLogPath"].Value.ToString() nor obj.Properties["Name"].Value.ToString() work - returning "Reference not set to an instance of the object".
Any ideas?
Thanks
How about deployment? Can an application using the System.Management.Automation be deployed to any client. What would the prerequisites be for deploying this kind of application.
(Since I'm having an error like: "An error occurred when loading the system Windows PowerShell snap-ins". This when trying to run an application with similar code on a clean server...)
Hey,
do you have any tips to get this faster? If the application loads first time, its really slow. The second time and later it works fine. I think this resides on the management tools, but i have no idea where i can begin to search for a fix.
Many thanks!
Wow, lots of new comments. I apologize I have not been as responsive as normal (and lack of updates). I have been in the process of changing jobs and trying to move to a new city. I promise to answer your questions and hopefully I will have some time at the airport this afternoon to do so.
I'll start my replies from the newest...
Anonymous 11/14/2007,
How long is it taking you to complete the first command? Running the application on a machine with an Exchange 2007 role installed will decrease the initialization time a bit (because it can access the ADAccess service). But even then there is a bit if initiation time penalty. Think of it as the time it takes to start the Exchange Management Shell.
--Nick
Burt,
In order for code using this wrapper to work that machine must have Powershell and the Exchange 2007 management tools installed. Additionally the user account invoking the code must have the necessary permissions to perform the intended tasks.
--Nick
Anonymous 11/12/2007,
I haven't tried using the format-list command in my managed code. I have alyways just pulled the information directly from the initial results. My suggestion would be to remove the format-list pipeline command it see if you can then access the desired information.
--Nick
Alex,
Thanks for the info. I have always compiled the program and executed it in a seperate command window when testing. (Mostly because I was never developing with an account that had the necessary Exchange permissions.)
--Nick
neppoz,
One of the requirements of this wrapper is that the Exchange management tools must be installed locally.
Our solution to incorporating our Exchange user management and a web front end was to have the webpage store all the configuration data within a database. We scheduled a job on a machine with the Exchange tools installed that ran every few minutes. It would query the database for any recent changes and run the necessary commands.
We chose the scheduled job option because we were unable to launch a real-time update onto a 64-bit machine. If you are running the code on a 32-bit machine with the management tools installed you can launch a PSExec command to that machine invoking the code. We have used both methods with great success.
--Nick
Hi Nick,
I wrote a c# class dll which
was used to retrieves Exchange
Server 2007 information using
cmdlets. I have another C++
code that interfaces with my C#
dll through COM object.
I always got errors from
CoCreateInstance()
complaining invalid argument
on win32 build and class not
not registered on x64
build. I was using Visual Studio
2005 to complile the code.
I run my C# code as a stand
alone program without problems.
Any idea? Thanks in advance!
Wchai
Wchai,
My suggestion would be to make sure that when you are compiling your applicaion ensure that "Any CPU" or "x64" is selected for the platform.
--Nick
Hi Nick,
Yes, I have selected "any cpu" when building my C# dll and "x64" for my
C++ code. Do you know if the interop between C++ and managed 64 bit code is supported on 64 bit Windows 2003 server?
Wchai.
I'm creating application for creating users and mailbox in Exchange 2007 environment in VB.NET (2008). I've not been able to find how to add/remove values from multivalued properties f.ex. EmailAddresses. In powershell SDK they say you should do it like this:
$Mailbox = Get-Mailbox "Kim Akers"
$Mailbox.EmailAddresses += "kim@contoso.com"
Set-Mailbox "Kim Akers" -EmailAddresses $Mailbox.EmailAddresses.
How can I do this in code?
Regards,
Thorir
Anonymous,
The code that you have posted can be accomplished using a scriptblock. Scriptblocks require a seperate runspace instance and I don't have that covered in the wrapper class.
For the purpose of adding email addresses using the wrapper class you can use the following one-liner command:
Set-Mailbox "Kim Akers" -EmailAddresses ((Get-Mailbox "Kim Akers").EmailAddresses + "smtp:kim@contoso.com")
--Nick
Hi Nick,
This has helped me greatly on implementing some web services that help manage our small ISP.
I am having one wierd issue I was hoping you could give me some insight into. I am new to powershell but understand the concepts and syntax pretty well.
Whenever I execute:
ems.RunspaceInvoke("($Private:secureString = ConvertTo-SecureString \"password\" -AsPlainText -Force) | foreach {New-mailbox -UserPrincipalName chris@contoso.com -alias chris -Name ChrisAshton -OrganizationalUnit aod -FirstName Chris -LastName Ashton -DisplayName \"Chris Ashton\" -Database (\"cd747c5d-aa8e-4f81-a136-ea4ff89e2896\") -Password $secureString}");
I get an error in C# that states:
Database "cd747c5d-aa8e-4f81-a136-ea4ff89e2896" was not found. Please make sure you have typed it correctly.
However, if I excute directly in powershell console, it works without any issues. Why am I unable to find the database from the code, but I can when in the console?
-Timothy
Timothy,
Executing cmdlets from the wrapper class will not always operate the same way as they do in the Exchange Management Shell. To accurately test cmdlets that fail in the shell you will want to open a new instance of plain powershell. Execute the following command to register the Exchange cmdlets:
Add-PsSnapin Microsoft.Exchange.Management.PowerShell.Admin
You will be able to execute Exchange cmdlets after running this command but you will notice that all commands will run against the entire forest. I imagine this will give you insite as to why the command is failing.
--Nick
Hello,
I am trying to implemented a few exchange management web services with .net and the c# wrapper class you have kindly provided. I have successfully been able to implement services for looking up mailboxes (ie Get-mailbox, etc). However any cmdlets that involve a write operation (Such as new-mailbox) I get the following error:
Active Directory operation failed on [name of our domain controller]. This error is not retriable. Additional information: Access is denied.
Active directory response: 00000005: SecErr: DSID-03151E04, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0
Any ideas on what I can do to fix this error? Is there some kind of IIS, .Net or exchange configuration I am missing? .Net is telling me that the web services are being executed as myself (and yes I have the permissions to execute these cmdlets manually from PowerShell).
Thanks.
Derrall,
My initial guess would be to check the identity that they application pool was running as.
I have not tried to use this code directly as part of web appliction. You may also want to avoid using impersonation when running this code from a web applicaion.
--Nick
Nick, this article is a great help. In my particular application im trying to deal with Exchange quotas and can find very little documentation.
Do you know how I can detect if the IssueWarningQuota property of a PSObject resulting from a get-mailbox is set to "Unlimited" in a "safe" way that isn't comparing typename strings (as that doesn't feel very future proof!)
Anonymous,
If you want to check that the IssueWarningQuota is set to "Unlimited" without comparing strings I would recommend checking the user account attributes directly using the DirectoryEntry C# method. Look for the mDBStorageQuota to be set to null and mDBUseDefaults value to be set to False.
--Nick
This is a great site. Thank you for your information. I THANK YOU I SALUTE YOU IT,S A AMZING SITE.
Hi Nick,
Thanks for this very useful post, a lot of nifty ideas and solutions :)
Cheers,
Jon
Hi Nick,
I have a problem, and as i see, you can help me resolve it. I made an msi package, what installs my application and runs a script, like you. If i take my code to a general application, then it's working well, but if i try to use from the msi package (the same code), then i receive an error. I posted my question to the microsoft forum too. It's here:
http://forums.microsoft.com/TechNet/ShowPost.aspx?PostID=3798889&SiteID=17&mode=1
Please take a look, and if you can please help me.
Thanks for all!
Kindly regards:
Imre Kovács
I'm trying to run the wrapper as a web app to create mailboxes, and am getting the following: Access to the address list service on all Exchange 2007 servers has been denied.
I don't have the impersonination on and am using Windows authentication and logging on as Administrator. What's the simplest way to accomplish this? It works if I just try to list a user or something...
Thank you !
Hi,
Did you set the permissions? I used the add-mailboxpermission... command for every operation. (accessrights fullaccess). It's working for me. (or you can use the impersonation... :) )
If i would like to set anything, then the error response is the same. But if i run query (for example: Get-Exchangeserver), then it's working fine.
Thanks,
Nicek, this is a fantastic page for people who are struggling with exchange shell commands. Thank you. I have a problem I need to address:
I am writing C# code to execute the Move-DatabasePath and Move-StorageGroupPath commands programmatically. They look like this:
Move-DatabasePath –Identity ‘MAIL2K7\First Storage Group\Mailbox Database’ –EdbFilePath ‘X:\MailStore\Mailbox Database.edb’ –Confirm:$False
Move-StorageGroupPath ‘MAIL2K7\First Storage Group’ –LogFolderPath ‘X:\MailStore’ –SystemFolderPath ‘X:\MailStore’ –ConfigurationOnly –Confirm:$False
The code I have is as follows:
Command myCommand = new Command(strCommand);
myCommand.Parameters.Add("Identity", strIdentity);
myCommand.Parameters.Add("EdbFilePath", strEDBFilePath);
myCommand.Parameters.Add("Confirm", false);
pipeLine.Commands.Add(myCommand);
ICollection< PSObject > results = pipeLine.Invoke();
When I run this, I get an exception: “System.Management.Automation.CmdletInvocationException: Cannot invoke this function because the current host does not implement it. ---> System.Management.Automation.Host.HostException: Cannot invoke this function because the current host does not implement it.”
I think it is because of the ‘Confirm’ parameter. These commands ask for confirmation, and I think the pipeline doesn’t accept confirm programmatically.
How can I programmatically send the Confirm -> False argument?
If you could help me with this, that would be awesome.
Mahesh,
After playing around with the Move-DatabasePath cmdlet I found that you need both the -Confirm:$False and -Force parameters to avoid a confirmaion prompt (there are actually two confirmation prompts when running this command). I was able to get the code to run with the following commands:
string strCommand = "Move-DatabasePath";
string strIdentity = @"EX2007ALL\First Storage Group\Mailbox Database";
string strEDBFilePath = @"C:\Exchange\Databases\Mailbox Database.edb";
Command myCommand = new Command(strCommand);
myCommand.Parameters.Add("Identity", strIdentity);
myCommand.Parameters.Add("EdbFilePath", strEDBFilePath);
myCommand.Parameters.Add("OutVariable", "$Status");
myCommand.Parameters.Add("Force");
myCommand.Parameters.Add("Confirm", false);
results = ems.PipelineInvoke(myCommand);
--Nick
Really great this what I wanted to know but still I am facing a issue that I am unable to execute any of the exchange relalted cmdlets .when ever I try to run the code it says
Unhandled Exception: System.Management.Automation.CommandNotFoundException: The
term 'Get-excommand' is not recognized as a cmdlet, function, operable program,
or script file. Verify the term and try again.
at System.Management.Automation.CommandDiscovery.LookupCommandInfo(String com
mandName)
at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(Strin
g commandName, CommandOrigin commandOrigin)
at System.Management.Automation.CommandFactory._CreateCommand(String commandN
ame, CommandOrigin commandOrigin)
at System.Management.Automation.CommandFactory.CreateCommand(String commandNa
me, CommandOrigin commandOrigin)
at System.Management.Automation.Runspaces.Command.CreateCommandProcessor(Exec
utionContext executionContext, CommandFactory commandFactory, Boolean addToHisto
ry)
at System.Management.Automation.Runspaces.LocalPipeline.CreatePipelineProcess
or()
at System.Management.Automation.Runspaces.LocalPipeline.InvokeHelper()
at System.Management.Automation.Runspaces.LocalPipeline.InvokeThreadProc()
Nick,
This is great! My code is executing in one domain. I need to create a mailbox in another domain. I'm running as an impersonated domain admin in the domain I'm trying to create the mailbox in. Using Exchange Management Shell I set default scope in $adminsessionADSettings to the domain I want to create the
mailbox in. I also set the configuration domain controller to a domain controller in that domain. Then I use the enable-mailbox command.
My question is: How do I duplicate setting $adminsessionADSettings in C#?
Thanks,
Candi
Hi Nick,
Great example. But, I am not able to execute simple Get-Mailboxstatistics command though it works find through powershell.
Any ideas why ?
Thanks
John
Nick,
In your code you assume that the Identity parameter represents the sAMAccountName property of the user object. Your assumption is not completely wrong, the parameter must be specified differently though e.g. as DOMAIN\sAMAccountName.
Only in this way you can be sure the query for that user will search for the sAMAccountName, and not the name property instead.
Greetz,
Mike
I love your code and it works great for everything I need to do. However I was wondering if it was possible to specify creds for this. In our enviroment our daily accounts are limited and we have alt accounts we use for privalaged things. So when debuging my normal account isn't an exchange admin so i get the following error, "Access to the address list service on all Exchange 2007 servers has been denied." however when I run Visual Stuido or my project as exchange admin i'm able to complete is there any way I can hardcode Creds into the connection?
Hello Nick,
Very good article. Thanks.
I have to dismount the database using the sample code you provided. What should be the cmdlet to be fired, so the confirmation is not asked. i have following code snippet
Command EMSMntCommand = new Command("Dismount-Database");
EMSMntCommand.Parameters.Add("Identity", prop2.Value.ToString());
EMSMntCommand.Parameters.Add("Confirm", "FALSE");
Here prop2.value.tostring is "Mailbox Database" //the default database available.
When i run the code snippet i am getting exception as
"An unhandled exception of type 'System.Management.Automation.ParameterBindingException' occurred in System.Management.Automation.dll"
Any help would be appreciated.
Thank you so much
DK
Hi Nick,
Very nice article.I have one task like i want to execute a command "get-help add-content -detailed" in c# using runspaces and pipeline and get the help details.
After invoking the pipeline i would be getting a PSObject for which i have to create nested loops and get the relevant help details like syntax,examples and related links.
My question is do we have an option to get the whole help details as a single string.
something like ps.properties["Text"] should give all the help details. Thanks in advance
Does anyone know if we can set default scope or other $adminsessionADSettings variables in the runspace before we execute a cmdlet? It would be very helpful to know. Thanks,
Tyler
Hi Nick,
Nice Article.
I have a question and need your help.
I am executing the cmdlets using C# code.
rc = RunspaceConfiguration.Create();
info = rc.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapEx);
r = RunspaceFactory.CreateRunspace(rc);
r.Open();
ri = new RunspaceInvoke(r);
I am executing ret = ri.Invoke(sCmd, null, out errors);
where sCmd = "Get-Queue"
I get the following error in my application log
RunspaceInvoke.Invoke returned error
Invoke error: The Queue Viewer operation on computer "localhost" has failed with an exception. The error message is: Access is denied
I am able to Run the cmdlet in Exchange Management Shell and get the available queues.
Using the same code I am able to run cmd-lets like get-ADSiteLink.
But Get-Queue fails.
Can I some way debug/log the powershell activity?
Please help me.
Regards,
Seema
Hello !!
I use your class in one of my projects and it seems the initialisation fails to work when called in a thread.
You can find more info on the problem here : http://stackoverflow.com/questions/1013331/powershell-runspace-in-a-thread
Thanks for your class !!!!
Hi Nick,
great post
why must it be a singleton? i have tried to do it non-singleton, and it works the same...
regards
Pilu
This is a great tool. How about Exchange 2010? The PowerShell interface is very different... (grrr) - could you provide an equivalent class that uses the Ex2010 PS interface?
I agree with "The Iguana"! how do you manage exchange 2010 with c# code???
Hi,
For Exchange 2010 you can use same code and just have to change this
Microsoft.Exchange.Management.PowerShell.Admin
to this
Microsoft.Exchange.Management.PowerShell.E2010
I have tested it successfully.
Regards,
I have Windows 7 64-bit installed, Exchange 2007 SP2 installed (with supposed PowerShell 2.0 support) and Visual Studio 2008.
I'm working on a ASP.NET 2.0 application.
When I try and do the same thing, I get "No powershell snap-in's available for v2" (paraphrased)
When I run Exchange Management Powershell tool, and I list the snapins it says that the Microsoft.Exchange.Management.Powershell.Admin snap in is PSVersion 1.0 and the Microsoft.Exchange.Management.Powershell.Support snapin is PSVersion 2.0.
WHY IS THE ADMIN STUFF NOT VERSION 2.0?!
Excellent work on this one. Question, how could the following be called:
Get-MailboxStatistics -Identity foo | fl totalItemSize
There were mant troubles, when I lost my Exchange info. Luckily with the help of one program I resolved it. This issue might determine as well as mine - microsoft exchange email recovery.
Hi Nick,
this looks great, I'm just battling to find out how to use this to generate a new alias on an existing mailbox instead of creating a new mailbox.
Hi Nick,
I need some help to integrate Exchange Server 2007 using C#.
Currently I am doing on my Testing machine which is 32 bit Windows XP(SP3) with Visual Studio 2010. I am using Windows Powershell 1.0 that already shipped with windows.
I am using the sample code that you have mentioned in your second post but unfortunality getting error ""Microsoft.Exchange.Management.PowerShell.Admin " is not installed on my machine. Please advice what all prerequiste i need to install on my local machine to test the integration.
Thanks & Regards
Himanshu Bhargav
Hi Nick,
I really like your article. Helped me a lot to understand how I should organize powershell calls in my app.
I see that you keep ExchangeManagementShellWrapper instance in static field. Do you think if ExchangeManagementShellWrapper is hosted in web application that it will be thread safe?
Right now, I create Runspace instance for each web request.
Thanks,
Miroslav
For those who are looking for Impersonation, see this post
http://stackoverflow.com/questions/7527469/error-when-using-impersonation-with-powershell-and-exchange-2007-from-c-sharp
Aczire
How can you do this with impersonation?
I've tried a couple of ways and they all fail with impersonation, but work fine without it.
I am trying to execute the following exchange management shell cmdlets using C# in order to get total number of mailbox on the server.
cmdlets:-
Get-mailbox -resultsize unlimited
My code snippet is as following
PSCredential credential = new PSCredential("Administrator", securePassword); // the password must be of type SecureString
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(connectTo,schemaURI, credential);
connectionInfo.MaximumConnectionRedirectionCount = 5;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
try
{
Runspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo);
remoteRunspace.Open();
var command = new Command("Get-mailbox");
command.Parameters.Add("resultsize", "unlimited");
var pipeline = remoteRunspace.CreatePipeline();
pipeline.Commands.Add(command);
// Execute the command
var results = pipeline.Invoke();
MessageBox.Show(results.Count.ToString());
remoteRunspace.Dispose();
}
catch (Exception ex)
{
//Handle error
}
The above code gives the desired result i.e. total number of mailboxes.But how can i select some properties of all the mailboxes i.e. how can i execute the following cmdlets
cmdlets:
Get-mailbox | select-object DisplayName, PrimarySmtpAddress, ForwardingAddress, alias, identity, legacyexchangeDN | where-object {$_.ForwardingAddress -ne $Null}
Please guid, how can i execute the above given cmdlets...
Thank you
Post a Comment