PowerShell and Exchange with C#

When writing a program that needs to interact with Exchange (whether on-premises or off-premises/online), the easiest way is to use PowerShell commands.

This is done by adding the following above your namespace declaration:

using System.Management.Automation;

The key thing that I have found to be a problem for C# programs using PowerShell is that occasionally you will receive an error message that says “A connection to the directory on which to process the request was unavailable. This is likely a transient condition.”

This is caused by trying to query PowerShell too often. If you are using PowerShell to query Exchange, you have a connection string that you will use – something like these two commands:

$Global:exchangeUri = "http://powershell.onyourdomain";$session = New-PSSession -Name $Global:sessionName -ConfigurationName Microsoft.Exchange -ConnectionUri $Global:exchangeUri -Authentication Kerberos;
Import-PSSession $session

If you attempt to do this every time you want to update some information in Exchange, or every time you want to query something in Exchange, you will quickly experience the transient condition error.

To avoid this, make a connection once per session. I have done this, in the code below, using an object named “Exchange” (because I’m imaginative like that):

public class Exchange
{
    public Exchange(bool onPrem, string userEmail = "NoEmail", string password = "NoPassword")
    {
        OnPrem = onPrem;
        if (!OnPrem)
        {
            if (password == "NoPassword" || userEmail == "NoEmail")
                throw new ArgumentException("If Online Exchange, we must have a username and password passed");
            CreateCredential(password, userEmail);
        }
    }
    Runspace _LocalRunSpace { get; set; }
    Runspace LocalRunSpace
    {
        get
        {
            if (_LocalRunSpace == null)
            {
                _LocalRunSpace = RunspaceFactory.CreateRunspace();
                _LocalRunSpace.Open();
            }
            return _LocalRunSpace;
        }
    }
    private PSCredential Credential;
    private PSCredential CreateCredential(string password, string emailAddress)
    {
        SecureString secpassword = new SecureString();
        foreach (char c in password)
            secpassword.AppendChar(c);
        return Credential = new PSCredential(emailAddress, secpassword);
    }
    private bool OnPrem { get; set; }
 private PowerShell _powershell { get; set; }
private PowerShell PowerShellInstance
{
     get
     {
          if (_powershell == null)
          {
             _powershell = PowerShell.Create();
             _powershell.Runspace = LocalRunSpace;
             if (OnPrem)
             {
                  _powershell.AddScript(exchangeScript);
                  Collection results = _powershell.Invoke();
                  foreach (PSObject result in results)
                      Console.WriteLine(result.ToString());
              }
              else
              {
                  var newSession = _powershell.AddCommand("New-PSSession")
                         .AddParameter("ConfigurationName", "Microsoft.Exchange")
                           .AddParameter("ConnectionUri", "https://ps.outlook.com/powershell/")
                           .AddParameter("Credential", Credential)
                           .AddParameter("Authentication", "Basic")
                           .AddParameter("AllowRedirection", true)
                           .Invoke();
                  var session = newSession[0];
                  _powershell.Commands.Clear();
                  Collection results = _powershell
                           .AddCommand("Import-PSSession")
                           .AddParameter("Session", session)
                           .Invoke();
                  foreach (PSObject result in results)                           
                         Console.WriteLine(result.ToString());                              
              }
           }
     return _powershell;
     }
}   

You can then use a method like the following, to run commands against the existing PowerShell connection. When I need both online exchange, and on-prem exchange, I create two instances of the Exchange object, and each will persist for as long as they are needed:

public string RunCommand(string command)
{
     try
     {
          PowerShellInstance.Commands.Clear();
          PowerShellInstance.AddScript(command);          
          PowerShellInstance.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
          Collection results;
          try { results = PowerShellInstance.Invoke(); }
          catch (System.Threading.ThreadAbortException threadError)
          {
                Console.WriteLine("PowerShell script ran into thread error:\n" + threadError.Message);
                return string.Empty;
           }
           string dataToReturn = string.Empty;
           foreach (PSObject result in results)
                dataToReturn += string.Format(result.ToString(), "; ");              
           return dataToReturn;
      }
      catch { return string.Empty; }
}

I hope this is helpful. If you have any questions or comments, please use the comments section below!

Leave a Reply

Your email address will not be published. Required fields are marked *

WP Twitter Auto Publish Powered By : XYZScripts.com