Now that CDOEXM and WMI providers are gone from Exchange 2007, many custom application developers are probably wondering: "what do I use instead?". The answer is the same as it is for admins who want to move from using CDOEXM/WMI to PowerShell, which is: use the Exchange cmdlets from code.

The PowerShell SDK provides a great set of APIs that will let you host an instance of the PowerShell engine and run cmds / scripts through it. So let's talk about how to code against PowerShell. Think of the PowerShell engine as being similar to the SQL engine. Both engines do specialized jobs, which require a dedicated processing engine. In SQL's case, the processing engine (the database) does all kinds of complicated stuff like joins, sorts, merges, relational queries etc. Well, to access all this good stuff, you need to invoke the database engine---basically you use SQL Command to interact with the engine. PowerShell is very similar: it has lots of specialized logic (pipelines, warning streams, progress streams, errors and so on) that can only be available through the PowerShell engine. Since all the Exchange cmdlets use this good stuff, you need to invoke the PowerShell engine when calling our cmdlets.

Enough talk, here is the sample app (very lightly tested, please use this is a template only).

Important Things to Note:  

You'll note that I don't have any references to Exchange namespaces, except adding the snapin. This is the recommended pattern, see "dealing with PSobject" below. You can also see in the sample that I don't treat the returned data as strong typed data. I deal with everything using "PSObject", which is a generic property bag that PowerShell keeps internally. This is again the recommended pattern. Another reason for dealing with MSHObjects and not casting to a strong type, is that Exchange has not interesting methods on the output classes. That's right---there are no methods on our output classes. The pattern is to use the property collection to find props and set/get them and to use cmdlets to achieve actions (like get/set/add/remove/mount/dismount etc.). The sample assumes an Exchange 2007 installation. Exchange 2007 beta 2 uses Monad RC0, whilst the SDK link above is the newly renamed PowerShell RC1, both are functionally equivalent, however that means that whereever you see PS in the name of a class or type, substitute MSH :)
Caveats:
You may see a 'path cannot be a null' exception when you pass in bad args to the app (and thus to the shell), this is a red herring, its our watson generating code messing up. Fixed in post beta 2 builds. Make sure to quote arguments properly when passing them to the app (and thus to the shell). For example: expshtest 'get-mailbox \"some user with space in their name\" '. Or expshtest 'get-mailboxdatabase \"server1\test db\" ' Make sure to pass in -confirm:$false to Exchange cmdlets that are destructive, or allow the user to pass in that option. Otherwise you'll get a 'host does not support operation' exception from PowerShell (actually right now you get the path cannot be null error. yuck!). This is a super simple app, so make sure to test for null args and so on. I stripped out all the basic stuff to keep the code small.

Sample: 

using System; using System.IO;
using System.Reflection; 
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Host;

namespace Test
{
 class TestOne
 {
  static void TestCallCmdlet(string args)
  {
   try
   {
           RunspaceConfiguration rc = RunspaceConfiguration.Create();            
          
MshSnapInException snapEx = null;

           MshSnapInInfo info = rc.AddMshSnapIn("Microsoft.Exchange.Management.Msh.Admin", out snapEx);

           Runspace r = RunspaceFactory.CreateRunspace(rc);
           r.Open();
           RunspaceInvoke ri = new RunspaceInvoke(r);
           ICollection results = ri.Invoke( args );
           List mailboxList = new List();

           foreach (MshObject mo in results)
           {
             MshPropertyInfo prop = (MshPropertyInfo)mo.Properties["Name"];
             if (prop != null)
                {
                    Console.WriteLine("Cmdlet: " + args + ",
val: " + prop.Value);
                    mailboxList.Add(prop.Value.ToString());
                }
           }
           ri.Dispose();
           r.Close();
   }
   catch (Exception ex)
   {
    Console.WriteLine(ex.Message.ToString() );
   }
  }

  static void Main(string[] args)
  {
   TestCallCmdlet(args[0]);
  }
 }
}

And here is the step to compile this sample (adjust it to match your install points):   

csc expshtest.cs /r:"c:\Program Files\Microsoft Command Shell\v1\System.Management.Automation.dll"