PowerShell and Exchange with C#

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!



Like many creative people on þe internet, I have a Patreon account. If you would like to support my creative writing (on https://shortbooks.online) or my blogging efforts, please take a look at my Patreon page.

Become a Patron!

8 thoughts on “PowerShell and Exchange with C#

  1. Since I wrote this, I have discovered another problem with using PowerShell commands in a multi-threaded application: Only one PowerShell command can be run at a time from a single computer. If you are running into problems, let me know, and I can write a post about locking access to PowerShell commands.

    1. Hi,

      I’m starting to write a simple wrapper to our Exchange in C#. I’m really surprised, there is a problem with multi-threaded application. Please can you write me, how to solve it properly?

      Thank you very much.

      Best regards,
      Peter from Prague

  2. Hi, your code in the example above does not compile:

    exchangeScript

    What should this value be?

    1. This is a good spot, thanks! The script will be bespoke to your environment, but will be in the following form:
      private readonly string exchangeScript = "$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri URI -Authentication Kerberos; Import-PSSession $Session";
      where the URI is the URI for your PowerShell Exchange config.

  3. Your solution was perfect for our hybrid environment so I thank you for providing me with a new direction for using both an exchange online and an on-premises connection. I was wondering though if you encountered issues with running the simple command Get-Mailbox in the on-prem session when you are also executing commands to exchange online? For some reason, the Get-Mailbox command is not recognized even though when I run a Get-PSSession command, I get a result. Thank you.

    1. I think Get-Mailbox only works if you are entirely on-prem, from memory. I think, in that case, your local command should be Get-RemoteMailbox. Let me know if I’ve not understood, or if I am going mad!

  4. Ok, thank you. I am used to running Get-RemoteMailbox from on-prem to see if there is an exchange online mailbox but not the other way around. I did try Get-RemoteMailbox from an exchange online session and even specified the domain controller but did not have success. I’m sure I have missed something but will keep working at it. Thanks again!

  5. I tried another way and it is now working. In the on-prem connection string, I specified the commands I would be executing in the Import-PSSession section. I just added -Name Get-Mailbox, etc… Import-PSSession -Session $Session -Name Get-Mailbox, Disable-UMMailbox, Set-CasMailbox, Set-MailboxAutoReplyConfiguration -AllowClobber

Leave a Reply to Petr Cancel reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.