Howto: Calling Exchange Powershell from an impersonated thread.



There are not many examples on doing doing impersonation for Exchange Powershell and non of the ones I see currently cover a few of the important gotccha’s.  So, I put this together and wish to share.


 


Impersonation and Exchange PowerShell:


 


In order to do impersonation on a thread and have it work with PowerShell for Exchange, you need to be sure the following is covered:


 


1)     Be sure that Rollup 1 for Exchange SP1 is installed.  This has a fix allowing impersonation work with powershell for Exchange.  See KB 943937…


 


How to obtain the latest service pack or update rollup for Exchange 2007


http://support.microsoft.com/kb/937052


     


At the time of this article, Rollup 3 is out, so why not install it (it includes Rollup 1)?


 


Description of Update Rollup 3 for Exchange Server 2007 Service Pack 1


http://support.microsoft.com/kb/949870/


     


 


2) Specify the -DomainController parameter when doing the call.  This is also covered in KB 943937.


 


Ex: AP1111XX.mydomain.net


 


 


Here is how to build the sample:


 


1)     Install the Exchange management tools on the box if they are already not installed.


 


2)     Install Powershell if your not running code on an Exchange Server.


 


How to Download Windows PowerShell 1.0


http://www.microsoft.com/windowsserver2003/technologies/management/powershell/download.mspx


 


3)     Install the PowerShell SDK…


 


How to Install Windows PowerShell and Download the Windows PowerShell SDK


http://msdn2.microsoft.com/en-us/library/Bb204630.aspx


 


4)   Create a C# winform application.


 


5)   Add the following controls:


 


Text boxes:


      tbImpersonateUsername        name of acct to impersonate.


      tbImpersonatePassword        password for acct to impoersonate


      tbSamAccountName             server\acct name of user to mail enable.


      tbServerDatabase                 database where mailbox will be.


 


Check Box:


      chkUseImpersonation            choose to impersonate or not…


 


Button:


      btnEnableMailbox                 calls impersonation routines.


 


6)   Add-in the code below.


 


7)   Set a reference to the “System.Management.Automation.dll”


 


This is installed by the Windows Powershell SDK under:


 


C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0


 


Here is an environment I used for testing this code:


 


Setup:


Box1 is:


·   Windows 2003.


·   This is the DC (AP1111XX.mydomain.net). Domain is mydomain.net.


·   Exchange 2007 SP1 is installed.


·   Installed Latest SPs, rollups, updates and fixes for Windows and Exchange


·   Created User TestX1 is a domain user and having no mailbox – used Active Directory Users and Computers to do this.


 


Box2 is :


·   Windows 2003 under same domain (mydomain.net) as theExchange 2007 server.


·   Visual Studio 2008 is installed.


·   Installed Exchange 2007 admin tools.


·   Installed Powershell SDK.


 


OK, here is the sample code:


 


using System;


using System.Collections.Generic;


using System.ComponentModel;


using System.Data;


using System.Drawing;


using System.Linq;


using System.Text;


using System.Windows.Forms;


 


using System.DirectoryServices;


using System.DirectoryServices.ActiveDirectory;


using System.Management.Automation;


using System.Management.Automation.Runspaces;


using System.Security;


using System.Security.Principal;


using System.Runtime.InteropServices;


 


namespace PowerShell101


{


    public partial class Form1 : Form


    {


        private IntPtr _userToken = IntPtr.Zero; // Holds the new impersonation token.


        private WindowsImpersonationContext _impersonationContext = null;


 


        public Form1()


        {


            InitializeComponent();


        }


 


        // WindowsIdentity..::.Impersonate Method (IntPtr)


        // http://msdn.microsoft.com/en-us/library/chf6fbt4.aspx


        [DllImport(“advapi32.dll”, SetLastError = true)]


        public static extern bool LogonUser(


            string lpszUsername,


            string lpszDomain,


            string lpszPassword,


            int dwLogonType,


            int dwLogonProvider,


            ref IntPtr phToken);


 


        // User clicked run button….


        private void btnEnableMailbox_Click(object sender, EventArgs e)


        {


            // Use the current domain and domain controller


            Domain thisDomain = Domain.GetCurrentDomain();


            string domain = thisDomain.Name;  


            string domainController = thisDomain.PdcRoleOwner.Name;


 


            try


            {


                this.Cursor = Cursors.WaitCursor;


                EnableMailbox(domain, domainController);


            }


            catch (Exception ex)


            {


                MessageBox.Show(ex.ToString());


            }


            finally


            {


                this.Cursor = Cursors.Default;


                MessageBox.Show(“Done”);


            }


        }


 


        private void EnableMailbox(string domain, string domainController)


        {


            RunspaceConfiguration config = RunspaceConfiguration.Create();


            PSSnapInException warning;


 


            // Load Exchange PowerShell snap-in.


            config.AddPSSnapIn(“Microsoft.Exchange.Management.PowerShell.Admin”, out warning);


            if (warning != null) throw warning;


 


            // Start impersonation?


            if (chkUseImpersonation.Checked == true)


            {


                BeginImpersonation(domain);


            }


 


            using (Runspace thisRunspace = RunspaceFactory.CreateRunspace(config))


            {


                try


                {


                    thisRunspace.Open();


                    using (Pipeline thisPipeline = thisRunspace.CreatePipeline())


                    {


 


                        thisPipeline.Commands.Add(“Enable-Mailbox”);


                        thisPipeline.Commands[0].Parameters.Add(“Identity”, tbSamAccountName.Text); 


                        thisPipeline.Commands[0].Parameters.Add(“Database”, tbServerDatabase.Text);


                        thisPipeline.Commands[0].Parameters.Add(“DomainController”, domainController); // Need for impersonation


                        // Need above line when impersonating. KB943937. Need rollup 1 for sp1.


 


                        try


                        {


                            thisPipeline.Invoke( );


                        } 


                        catch (Exception exx)


                        {


                            MessageBox.Show(“Error: “ + exx.ToString(), “Error”);


                        }


 


                        // Check for errors in the pipeline and throw an exception if necessary.


                        if (thisPipeline.Error != null && thisPipeline.Error.Count > 0)


                        {


                            StringBuilder pipelineError = new StringBuilder();


                            pipelineError.AppendFormat(“Error calling Enable-Mailbox.”);


                            foreach (object item in thisPipeline.Error.ReadToEnd())


                            {


                                pipelineError.AppendFormat(“{0}\n”, item.ToString());


                            }


                            throw new Exception(pipelineError.ToString());


                        }


                    }


                }


                finally


                {


                    thisRunspace.Close();


                    if (chkUseImpersonation.Checked == true)


                    {


                        EndImpersonation();


                    }


                }


            }


        }


 


        private void BeginImpersonation(string domain)


        {


            EndImpersonation();


            if (tbImpersonateUsername.Text.Length > 0)


            {


 


                System.Diagnostics.Debug.WriteLine(“User Before Impersonation: “ + WindowsIdentity.GetCurrent().Name);


                bool success = LogonUser(


                        tbImpersonateUsername.Text,


                        domain,


                        tbImpersonatePassword.Text,


                        2,


                        0,


                        ref _userToken);


 


                // Did it work?


                if (!success) throw new Exception(string.Format(“LogonUser returned error {0}”, System.Runtime.InteropServices.Marshal.GetLastWin32Error()));


               


                WindowsIdentity fakeId = new WindowsIdentity(_userToken);


                _impersonationContext = fakeId.Impersonate();


                System.Diagnostics.Debug.WriteLine(“User After Impersonation: “ + WindowsIdentity.GetCurrent().Name);


            }


        }


 


        private void EndImpersonation()


        {


            if (_impersonationContext != null)


            {


                try


                {


                    _impersonationContext.Undo();


                }


                finally


                {


                    _impersonationContext.Dispose();


                    _impersonationContext = null;


                }


            }


        }


    }


}


 


Example of what I would enter when the code is run:


     


 


Text boxes:


      tbImpersonateUsername            Administrator


      tbImpersonatePassword            My1AdminPassword


      tbSamAccountName                  mydomain \ TestX1


      tbServerDatabase                     AP1111XX\First Storage Group\Mailbox Database


 


Check Box:


      chkUseImpersonation                checked


 


See what happens with and without impersonation.


 


About Mailboxes and Exchange 2007:


What is true for the Exchange 2007 tools should be true for Exchange PowerShell since the tools use PowerShell.


 


·   Exchange 2007 mailboxes must be managed with Exchange 2007 management console or shell.Exchange 2007 mailboxes MUST NOT be managed with Exchange 2003 tools. Note that this is not blocked, but mailboxes managed from Exchange 2003 ADUC will not be fully functional.


·   Exchange 2003 mailboxes can be edited or removed with Exchange 2007 tools, but cannot be created by Exchange 2007 tools.


·   Exchange 2003 mailboxes can be managed with Exchange 2003 tools.


·   Both Exchange 2003 and Exchange 2007 mailboxes can be moved (in either direction) with the Exchange 2007 tools. Exchange 2003 move mailbox cannot be used to move mailboxes to or from Exchange 2007 mailbox server.


 


For further information on PowerShell, please visit the link below:


 


Links on Common PowerShell Automation Questions


http://blogs.msdn.com/webdav_101/archive/2008/09/26/links-on-common-powershell-automation-questions.aspx


 


 

Comments (17)

  1. I’ve put together a list of articles which cover common questions on PowerShell automation. These links

  2. We are working to get official public documentation on this subject, I will update this post once we

  3. dmitry_2000 says:

    Great! It really works.

    But I am not quite sure how to understand support current installed Exchange Client Tools this functionality or not. How can I understand it (e.g. analyze dll’s version or something also)

    It’s very important for me

    Thank you,

    Dmitry

  4. froded says:

    Hi! Any information on how to do impersonation on Win 2008 x64 server? I cannot get the impersonation running on IIS 7. Using Exchange 2007 SP1 Rollup 5. Getting the usual "Access to the address list service on all Exchange 2007 servers has been denied" error among others.

  5. Juanito says:

    Has anybody gotten impersonation to work under ASP.NET and running permission specific Exchange commands (such as “Enable-Mailbox”)? Thanks!

  6. Webdav101 says:

    Hello;

    froded – it looks like your not running with enough privs.  You need to run under an Exchange Admin account.  

    Be sure that both the box running the .net code and the Exchange server have SP1 rollup 5.

    Juanito and froded:  you might try looking at the more recent post I did on trying to get impersonation to work under ASP.NET using 64bit Exchange PowerShell. Please refer to http://blogs.msdn.com/webdav_101/archive/2008/12/30/an-adventure-building-an-asp-net-application-to-call-64bit-powershell.aspx.

    Each of you might be running into a different issue.  You might want to open a support case for your issue.

    One thing which seems to always work for a work-around is to put the code into a COM+ component and call from your application.  The code would be running in com+ under an Exchange Admin account.

    I’m trying to collect info on issues people run-into in this area and put together a walk-through on how to properly build an asp.net app using impersonation for both 32bit and 64bit.  I’ll also try to include a common problems section or put that into a seperate blog post.

    If you get "access to the address list service on all exchange 2007 servers has been denied", then your not running under sufficient permissions.

    If you have already resoled your problem, please share the details of what you found and how you resolved it.

    Thanks,

    Dan

  7. npopov says:

    Thank you Dan for sharing your code, it fills a big gap in the documentation and code examples – in fact this is the only working code in regard to using enable-mailbox in conjuction with impersonation – it resolved a realy big isues for me. But how about move-mailbox? I’m setting the -DomainControler parameter but without any sucess – it kills the app and logs in the event log an event with M.E.D.D.ConnectionPoolManager.BlockImpersonatedCallers in it (naturally with all paches applied on all boxes).

  8. Webdav101 says:

    I would suggest checking to be sure that SP1 Roll-up 1 is on all Exchange servers involved AND on the box you are running the code on.

  9. Webdav101 says:

    If a user certain adminstrative permission granted,  then they will not be allowed mailbox access by default.  If you are getting a block/denied error, then be sure to read the following and check what is granted on the accounts involved in you code execution.

    Exchange 2007 Permissions: Frequently Asked Questions

    http://technet.microsoft.com/en-us/library/bb310792.aspx

    If your logon account is the Administrator account, a member of the root Domain Administrators, a member of the Enterprise Administrators groups, or a member of the Exchange Organization Administrators role, you are explicitly denied access to all mailboxes that are not your mailbox, even if you have full administrative rights over the Exchange system. All Exchange 2007 administrative tasks can be performed without having to grant an administrator sufficient rights to read other people’s mail.

    You can achieve the results that you want in the following ways, but do so only in accordance with your organization’s security and privacy policies:

    • In the Exchange Management Shell, use the following command to allow access to all mailboxes on a given mailbox store:

    Add-ADPermission -identity "mailbox database" -user "serviceaccount" -ExtendedRights Receive-As

    • In the Exchange Management Shell, use the following command to allow access to an individual mailbox:

    Add-MailboxPermission -identity "user" -user "serviceaccount" -AccessRights FullAccess

  10. Webdav101 says:

    The code above works fine under ASP.NET when used on an Exchange 2007 server where the DC, Mailbox and CAS are all on one box.  However, it fails when run on a configuration where the DC, CAS and Mailbox servers are on different boxes.   To make this work, you need to modify the aspnet.config file.

    So, if you are having problems with Impersonation under ASP.NET, you may need to add two entries to the aspnet.config file in order to make impersonation work properly.  The aspnet.config file is used to configure ASP.NET.  Its located under “C:WINDOWSMicrosoft.NETFrameworkv2.0.50727” (32bit) and under “C:WINDOWSMicrosoft.NETFramework64v2.0.50727” (64bit).

    Impersonation and Hosting PowerShell

    <configuration>

       <runtime>

           <legacyImpersonationPolicy enabled=”false”/>

           <alwaysFlowImpersonationPolicy enabled=”true”/>

       </runtime>

    </configuration>

    Please refer to the following links for further information:

    Impersonation and Hosting PowerShell

    http://blogs.msdn.com/powershell/archive/2007/09/10/impersonation-and-hosting-powershell.aspx

    Security Guidelines: ASP.NET 2.0

    http://msdn.microsoft.com/en-us/library/ms998258.aspx

    In .NET Framework 1.1, impersonation tokens did not automatically flow to newly created threads. This situation could lead to security vulnerabilities because new threads assume the security context of the process. In ASP.NET 2.0 applications you can now change this default behavior by configuring the ASPNET.config file in the %Windir%Microsoft.NETFramework{Version} directory.

    If you need to flow the impersonation token to new threads, set the enabled attribute to true on the alwaysFlowImpersonationPolicy element in the ASPNET.config file, as shown in the following example.

    Copy Code

    ….

    <configuration>

     <runtime>

       <alwaysFlowImpersonationPolicy enabled="true"/>

        </runtime>

    </configuration>

    ….

    If you need to prevent impersonation tokens from being passed to new threads programmatically, you can use the ExecutionContext.SuppressFlow method.

    You can check the credentials by running the following aspx page from the same folder as your asp.net code.  

    <%@ Page Language="C#" %>

    <%@ Import Namespace="System.Net.Security" %>

    <%@ Import Namespace = "System.Security.Principal" %>

    <%@ import Namespace="System.Net" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

    <script runat="server">

       protected string DoPs()

       {

               try

               {

                   System.Management.Automation.Runspaces.Runspace myRunSpace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace();

                   myRunSpace.Open();

                   System.Management.Automation.Runspaces.Pipeline pipeline = myRunSpace.CreatePipeline("[System.Security.Principal.WindowsIdentity]::GetCurrent().Name");

                   System.Collections.ObjectModel.Collection<System.Management.Automation.PSObject> objectRetVal = pipeline.Invoke();

                   myRunSpace.Close();

                   string result=objectRetVal[0].BaseObject.ToString();

                   return result;

               }

               catch (Exception ex)

               {

                   return(ex.ToString());

               }

               return("");

       }

    </script>

    <html xmlns="http://www.w3.org/1999/xhtml"&gt;

    <head id="Head1" runat="server">

       <title></title>

    </head>

    <body style="font-family: verdana; font-size: small;">

       <form id="form1" runat="server">

       DateTime.Now = <%=DateTime.Now.ToString()%>

       <hr />

           [System.Security.Principal.WindowsIdentity]::GetCurrent().Name = <%= DoPs() %>

       <hr />

           ASP.Net Logged On User Info:<br />

           this.User.Identity.Name = <%=this.User.Identity.Name%> <br />

           this.User.Identity.AuthenticationType = <%=this.User.Identity.AuthenticationType%> <br />

       <hr />

           Current Thread Identity:<br />

           System.Security.Principal.WindowsIdentity.GetCurrent().Name = <%= System.Security.Principal.WindowsIdentity.GetCurrent().Name %> <br />

           System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel = <%= System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel %> <br />

           System.Security.Principal.WindowsIdentity.GetCurrent().AuthenticationType = <%= System.Security.Principal.WindowsIdentity.GetCurrent().AuthenticationType %> <br />

       </form>

    </body>

    </html>

  11. Webdav101 says:

    With Exchange 2010, you should be using Remote Powershell. Below are some articles to help get you started.

    How to call Exchange 2010 cmdlet’s using Remote Powershell in code

    http://blogs.msdn.com/dvespa/archive/2009/10/22/how-to-call-exchange-2010-cmdlet-s-using-remote-powershell-in-code.aspx

    Remote Powershell Sample Explained…

    http://blogs.msdn.com/dvespa/archive/2009/10/22/remote-powershell-sample-explained.aspx

    Programmatic Access via Remote PowerShell in Exchange Server 2010

    http://msexchangeteam.com/archive/2009/11/02/453016.aspx

    Connect Remote Exchange Management Shell to an Exchange Server

    http://technet.microsoft.com/en-us/library/dd297932.aspx

    Install Windows Management Framework

    http://technet.microsoft.com/en-us/library/dd335147.aspx

  12. Petri X says:

    Do you know is there a differences between different cmdlets? As I'm able to use "Enable-Mailbox", "Disable-Mailbox", "New-DistributionGroup" etc… but not able to use e.g.: "Add-MailboxPermission". It kills the application pool and writes on the event log: M.E.D.D.ConnectionPoolManager.BlockImpersonatedCallers

    So are some cmdlets more against impersonating than others?

  13. Webdav101 says:

    I don't know what might be causing that.  Did you enable impersonation as described on 4/21?

  14. Webdav101 says:

    Note that using the snap-in method may or may not work with Exchange 2010 – its not supported. You really need to use Remote PowerShell against Exchange 2010 if you want reliability.

  15. Dumitru says:

    I'm interacting with the Exchange 2007 SP3 using locally installed administration components. I've stuck to the official documentation, and as long as I run the app under an account which has the proper permissions, all is well.

    Since in the end the code needs to run as a service (LOCAL System) I searched around and found that since SP1 rollup1, impersonation is supported. I then implemented impersonation as described in your article.

    The commandlets I'm using are Get-Mailbox, Get-UMMailbox, Set-UMMailbox, Set-Mailbox, Disable-UMMailbox and Enable-UMMailbox.

    Things work out just fine for the first 4 commandlets. Disable-UMMailbox also runs if I specify an account that has no UM – but as soon as I specify an account that has UM, my application simply disappears, no exception no nothing (nothing in the eventlog either). The same goes for Enable-UMMailbox… run it under impersonation and your app will simply disappear into thin air.

    I'm doing this under Win7 64bit with the Exchange 2007 SP3 management components installed. I'm testing while logged into the machine as local admin, not with a domain account (the machine is a member of the same domain as the exchange though.. and I adapted the impersonation code so that I get all the domain information after impersonation (before the code in the sample doesn't work when you're not logged in with a domain account)).

    Has anybody successfully managed to run Disable/Enable-UMMailbox under impersonation against Exchange 2007 SP3?

  16. Webdav101 says:

    I don't think impersonation is supported in this area as I recall. However, it would be be best to open a support case on this.

  17. Webdav101 says:

    I don't think impersonation is supported in this area as I recall. However, it would be be best to open a support case on this.

Skip to main content