Acquiring OAuth2 access tokens for automating Exchange Management Shell cmdlets 5/5 (2)

Overview

If you’re seeking to automate Exchange Management Shell cmdlets in a .Net Framework application, you can use the new Exchange Online PowerShell V2 module

The other option is to use a workaround which consists of using the well-known Exchange Management Shell app ID.

Application ID: a0c73c16-a7e3-4564-9a95-2bdf47383716
Redirect URL: urn:ietf:wg:oauth:2.0:oob

This workaround is offered because we don’t currently expose a permissions model for you to use with your own app registration but using the new V2 module is preferred.

Requirements

If you wish to use the new Exchange Online PowerShell V2 module, you will need to first install the module as per the instructions in the module documentation.

Acquiring OAuth2 tokens for managing the Exchange organisation, requires the use an Exchange administrator account that can be used to authorise the requests.

Only delegate (user) tokens can be acquired which means a username and password will have to be specified when acquiring the oAuth credential.

No permissions are currently exposed for acquiring Application tokens by means of certificates or ClientID / ClientSecret pairs.

How it works

In order to acquire a token, you will need to authorise an Exchange administrator account using the common endpoint and acquire an authorisation code. This means you will need to prompt the admin for a username and a password.

If you use the new Connect-ExchangeOnline cmdlet included in the Exchange Online PowerShell V2 module, the cmdlet will do the work for you and no further action will be required.

If you are using the New-PSSession workaround, once you’ve obtained the authorization code you can acquire an access token and pass the acquired token as a password in a PSCredential object, either to the New-PSSession cmdlet or when working with System.Management.Automation.Runspace objects.

Detailed steps

Connecting using the Exchange Online PowerShell V2 Module

You can use both basic authentication and MFA enabled accounts to connect using the new V2 Module.

The following example demonstrates how to connect using the new Exchange Online PowerShell V2 Module, using the Connect-ExchangeOnline cmdlet.

You will need to install the module before you attempt to run this sample. The install procedure is available in the V2 Module documentation.

using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;
using System.Threading;

namespace ExchangeManagementShell
{
    class Program
    {
        static PowerShell powershell;
        static Collection<PSObject> InvokeScript(string script)
        {
            powershell.AddScript(script);
            Collection<PSObject> result = powershell.Invoke();

            if (powershell.HadErrors)
            {
                if (powershell.Streams != null &amp;&amp; powershell.Streams.Error != null &amp;&amp;
                powershell.Streams.Error.Count > 0)
                {
                    throw powershell.Streams.Error[0].Exception;
                }
                else
                {
                    // handle other exceptions here
                }
            }
            return result;
        }

        static void Main(string[] args)
        {
            string userPrincipalName = "user@contoso.com";

            powershell = System.Management.Automation.PowerShell.Create();
            powershell.Runspace = RunspaceFactory.CreateRunspace();
            powershell.Runspace.ApartmentState = ApartmentState.STA;

            powershell.Runspace.Open();
            InvokeScript("Import-Module ExchangeOnlineManagement");
            InvokeScript($"Connect-ExchangeOnline -UserPrincipalName {userPrincipalName}");
            InvokeScript("Get-Mailbox");
        }
    }
}

Connecting using a token and the New-PSSession cmdlet

To simplify the process we recommend you use the Microsoft.IdentityModel.Clients.ActiveDirectory (ADAL) library or the Microsoft Authentication Library for DotNet (MSAL) library.

The following example demonstrates how to acquire a delegate token using ADAL:

using Microsoft.IdentityModel.Clients.ActiveDirectory;     

public static async Task<string> GetExchangeManagementShellToken()
{
    try
    {
        AuthenticationContext authenticationContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/common", false);
        AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync("https://outlook.office365.com", "a0c73c16-a7e3-4564-9a95-2bdf47383716", new Uri("urn:ietf:wg:oauth:2.0:oob"), new PlatformParameters(PromptBehavior.Always)); 
        return authenticationResult.CreateAuthorizationHeader();
    }
    catch (Exception ex)
    {
        // handle exceptions here
    }
    return "";
}

The following example demonstrates how to use the newly acquired token with the New-PSSession cmdlet:

static void Main(string[] args)
   {
    string connectionUri = "https://outlook.office365.com/PowerShell-LiveID?BasicAuthToOAuthConversion=true";
    string authorisationHeader = GetExchangeManagementShellToken().Result;
    string userPrincipalName = "admin@contoso.com";

    if (authorisationHeader != string.Empty)
    {
        Collection<PSObject> psSessions = null;
        PSCredential psCredential = new PSCredential(userPrincipalName, ToSecureString(authorisationHeader));
  
        PowerShell powershell = PowerShell.Create(RunspaceMode.NewRunspace);
        PSCommand command = new PSCommand();
  
        command.AddCommand("New-PSSession");
        command.AddParameter("AllowRedirection", true);
        command.AddParameter("Authentication", "Basic");
        command.AddParameter("ConfigurationName", "Microsoft.Exchange");
        command.AddParameter("ConnectionUri", connectionUri);
        command.AddParameter("Credential", psCredential);
  
        powershell.Commands = command;
        psSessions = powershell.Invoke();
  
        if (powershell.HadErrors)
        {
            if (powershell.Streams != null &amp;&amp; powershell.Streams.Error != null &amp;&amp;
                powershell.Streams.Error.Count > 0)
            {
                throw powershell.Streams.Error[0].Exception;
            }
            else
            {
                // handle other exceptions here
            }
        }
    }
} 

Conclusion

You cannot register your own application in order to acquire OAuth2 tokens for automating Exchange Management Shell cmdlets from .Net or using PowerShell.

The only way to acquire oAuth tokens at present is to use the well known app registration for Exchange PowerShell.

In addition, only tokens available are delegate token and acquiring such a token requires explicitly prompting the administrator for a username and password. 

We are currently investigating the possibility of adding a permissions model to allow registering your own applications and generating both delegate and application tokens.

Notes

The only tokens you’ll be able to acquire using this app registration are delegate tokens. Acquiring client secret or certificate-based application tokens is not possible.

Connect-EXOPSSession already uses this logic so if you’re only trying to run Exchange Management Shell cmdlets, we recommend you use it instead of New-PSSession.

Both Microsoft.IdentityModel.Clients.ActiveDirectory and System.Managemnet.Automation namespaces are available as nuget packages.

Downloads

PowerShellSample.zip

References

Connect-EXOPSSession

Microsoft.IdentityModel.Clients.ActiveDirectory

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

System.Management.Automation

Exchange Online PowerShell V2 Module

Revisions

Revision type

Date Author Comments

Original

2018-09-07 Andrei Ghita  

Update

2019-07-29 Andrei Ghita Updated post to include the new permissions model.

Update

2019-07-31 Andrei Ghita Corrected erroneous post update with regards to permissions that are not yet in effect.

Update

2020-03-24 Andrei Ghita Added Exchange Online PowerShell V2 code sample.

Please rate this

1+

23 thoughts on “Acquiring OAuth2 access tokens for automating Exchange Management Shell cmdlets

  1. I’m sorry but your code does not make sense to me.
    How do you pass token to PSCredential(“”, token);?
    The second argument in PSCredential must be a secure string, and even when I convert it to secure string (as password) it fails. Was there some other way of getting it passed through? Would you by any chance have a complete code on how to accomplish this task?

    0
  2. So I am acquiring a full token: (“Login token: Bearer eqse23…..”)
    I am passing it to:
    public static Collection GetMailboxTest(String token)
    {
    Collection psobjs;
    System.Security.SecureString sspassword = new System.Security.SecureString();
    for (int i = 0; i < token.Length; i++)
    {
    sspassword.AppendChar(token[i]);
    }
    PSCredential psCredential = new PSCredential("spock@enterprise.net", sspassword);
    RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
    WSManConnectionInfo wsManConnectionInfo = new WSManConnectionInfo(new Uri("https://outlook.office365.com/PowerShell-LiveID&quot;), "http://schemas.microsoft.com/powershell/Microsoft.Exchange&quot;, psCredential);
    using (Runspace runspace = RunspaceFactory.CreateRunspace(wsManConnectionInfo))
    {
    PowerShell powershell = PowerShell.Create();
    powershell.AddCommand("Get-Mailbox");
    runspace.Open();
    powershell.Runspace = runspace;
    psobjs = powershell.Invoke();
    }
    return psobjs;

    }

    and I get an exception "An unhandled exception of type 'System.Management.Automation.Remoting.PSRemotingTransportException' occurred in System.Management.Automation.dll

    Additional information: Connecting to remote server outlook.office365.com failed with the following error message : [ClientAccessServer=ABCDEFG,BackEndServer=,RequestId=5b656a0b-04bc-46e4-a5d2-fc5ed0eg28bz,TimeStamp=7/30/2019 7:26:51 PM] Access Denied For more information, see the about_Remote_Troubleshooting Help topic."

    Is there something wrong with my code?

    1+
    1. Hi Stan,

      I’ve just updated the post with a correct sample for creating a session in .Net. Subsequent cmdlets can executed in the same fashion. The old sample wasn’t accurate, my apologies.

      You would need to import the PSSession created with New-PSSession. Something in the lines of:
      command = new PSCommand();
      command.AddCommand(“Import-PSSession”);
      command.AddParameter(“-Session”, psSessions[0]);
      powershell.Commands = command;

      You might also need to set the execution policy to Unrestricted:
      command.AddCommand(“Set-ExecutionPolicy”);
      command.AddArgument(“Unrestricted”);

      Please let me know if you have any other questions.

      Andrei

      0
  3. I have one more question for you, if you could answer it I would highly appreciate it.
    So I wanted to use this code in a web page, and to have it log on automatically in the background and do some tasks.
    In order to obtain with username and password I found a method:
    UserPasswordCredential uc = new UserPasswordCredential(“username@organization.net”, “password”);
    AuthenticationResult authenticationResult = await AuthenticationContextIntegratedAuthExtensions.AcquireTokenAsync(authenticationContext, “https://outlook.office365.com”,
    “a0c73c16-a7e3-4564-9a95-2bdf47383716”, uc);

    However this did not work, and it did not aquire the token Would you have any recommendation on what would you proceed with this?

    Thank you!

    0
    1. Hi Stan,

      Sorry for the late reply. In order for that code to work with basic authentication, you need to remove “?BasicAuthToOAuthConversion=true” from the connectionUri, otherwise the server will try and interpret your credentials as an oAuth credential.

      Andrei

      0
  4. Wouldn’t this still open a connecting using basic authentication? What if we want to open a connection using modern authentication since basic will be disabled this year?

    “command.AddParameter(“Authentication”, “Basic”);”

    Thanks!

    0
      1. Hi,

        Are you sure it will not work after disable “basic auth”? I just did some tests with “Security default” enabled (which includes the “Block legacy authentication (preview) ” rule) and it seems to work.

        0
      1. I assume you would need to save the token somewhere? Any idea how this is done?

        Looking forward to the modern authentication V2!
        Thanks.

        0
  5. Hi Andrei
    >>You must use this workaround because we don’t currently expose a permissions model for you to use with your own app registration.
    Can you please give an update? Is it still impossible to use own appID?

    1+
    1. Hi Alex,

      Unfortunately this is stil the case, we still don’t expose permissions but I will follow up with engineering to try and get some answers.

      In the meantime, I will update the post to include a sample that uses the V2 Exchange Management Shell module.

      1+
      1. Hi Andrey,

        We’ve tried to use the latest version of the V2 Exchange Management Shell module to authenticate using credentials of our own app. No luck. Are you sure the type of authentication is supported by the module at this moment?

        1+
      2. Hi Andrei,

        When do you plan to write an update about the V2 Exchange Management Shell? I would like to read it. I tried to use this module (just like Eugene), but without success.
        I can use the REST API with OAuth, but PS doesn’t work (works only with basic auth).

        1+
  6. Hi Andrei
    In our python application, we use the Exchange Management Shell cmdlets to perform some tasks. (In fact, the python application runs .Net application, which calls the Exchange Management Shell cmdlets.) Currently we use basic authentication with the New-PSSession cmdlet. On the other hand, we are already acquiring OAuth2 access tokens for different APIs and resources. Now we would like to use OAuth2 authentication with the Exchange Management Shell cmdlets.

    We have a few questions:

    1. Is there a way to get access tokens without using ADAL and MSAL?
    2. Is there a way to get a new access token using a refresh token?
    3. It seems there is no chance to use an external(system) browser, isn’t there? Thank you in advance.

    0
    1. Oh, in fact, everything is very simple:
      1. Request for an authorization code: ‘https://login.microsoftonline.com/common/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A55555&client_id=a0c73c16-a7e3-4564-9a95-2bdf47383716&response_type=code’
      2. Request for an access token:
      URL: ‘https://login.microsoftonline.com/common/oauth2/token’
      body: resource=https%3A%2F%2Foutlook.office365.com&client_id=a0c73c16-a7e3-4564-9a95-2bdf47383716&redirect_uri=http%3A%2F%2Flocalhost%3A9999&code=YOUR_AUTHORIZATION_CODE&client_secret=&grant_type=authorization_code

      0
      1. Hello,

        Will this solution still prompt you with a prompt to authenticate?

        With Basic Authorization going away, EWS in sustaining mode, what other avenues do we have to write non-interactive client apps that will manage mailboxes on Exchange Online?

        Most of these… “hacks”… seem to still require a human intervention. Even the solution of retrieving a RefreshToken (which some say in the comment section is not returned) is not viable as the RefreshToken can be revoked / invalidated.

        Am I missing something here or it’s just not possible right now?
        My choices are:
        – Stick with Basic Auth for now and hope for the best (being able to authenticate with our own registered apps / being able to manage mailboxes via Graph) by next year?
        – Since we’re on a hybrid setup, accept there are some attributes we can’t manage if they’re migrated.

        Thank you all.

        0

Leave a Reply

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