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

0

Overview

If you’re seeking to automate Exchange Management Shell cmdlets in a .Net Framework application, you must 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

You must use this workaround because we don’t currently expose a permissions model for you to use with your own app registration.

Please disregard the update I made on the 29th July 2019. The permissions I had mentioned back then are not yet usable.

Requirements

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.

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

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:

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

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.
0

Please rate this

6 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

Leave a Reply

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