Deep dive:Exchange Online PowerShell and MFA

When you read the headline, you’re might thinking “Oh no! Another post about this topic!”. But I think this post is worth reading as I’ll go deep into details.

Over the last months I have seen an increase of questions from various teammates and other teams in regards of the Exchange Online Remote PowerShell Module. The questions where mostly related to connectivity issue and prompts for re-authentication as PSSessions got into a broken state.

Also the fact that in some areas a proxy needs to be used, might be confusing as well as the question what to do if you have a service account or want to use the module in ISE.

What are we talking about?

You can connect to Exchange Online with built-in PowerShell. The procedure is described here:

Connect to Exchange Online PowerShell

But from security perspective you should enable MFA, either on per user or (better and preferred way) via Conditional Access. In order to connect to Exchange Online, when you are MFA enforced, you need to download and install the Exchange Online Remote Module. You can do this by either following the steps here:

Connect to Exchange Online PowerShell using multi-factor authentication:What do you need to know before you begin?

or just use this link for downloading and installing:

Once installed you can use the module described here:

Connect to Exchange Online PowerShell by using MFA

Where do I find the module?

You won’t be able to find the module, using the Cmdlet Get-Module -ListAvailable -All, which determines all installed PowerShell modules.  When you install the module you’re installing a ClickOnce application, which will be installed into the directory:


The folder contains a folder with a cryptic name, which is normal for a ClickOnce application:


As we know where what will be installed the most common questions and scenarios:

Connecting Exchange Online behind a proxy

I guess a lot of companies uses proxy servers for internet access. So do we. Now when you try to connect to Exchange Online you have to understand that PowerShell is not using by default any settings from IE. Thus means you need to tell PowerShell to use a proxy in order to connect. You do this by defining an option for the PSSession. You do this with the Cmdlet New-PSSessionOption. Here some examples:

#connecting using proxy setting from Internet Explorer
Connect-EXOPSSession -PSSessionOption (New-PSSessionOption -ProxyAccessType IEConfig)
#connecting using setting of WinHttpConfig
Connect-EXOPSSession -PSSessionOption (New-PSSessionOption -ProxyAccessType WinHttpConfig)

There are several other options available, which enables you to customize your connection options, based on your needs.

UserPrincipalName or not

The function Connect-EXOPSSession has a parameter UserPrincipalName. When you start the Exchange Online Remote PowerShell Modul, a hint about the usage is shown:


It’s indicating you should use this parameter, but there are possible issue, which I’ll describe later in this post.

The short version is to use the parameter as to take advantage and after 1 hour the function Connect-EXOPSSession will take care and use the existing Refresh token and request a new Access token. For those of you who doesn’t want to know the details, you can skip the following and jump to the next section here.

For the other ones:

Why after 1 hour? That’s the default Access token lifetime, which could be configured. Read more about how to do this here:

As next you need to understand how the module works. There are two main components:

  • CreateExoPSSession.ps1
  • Microsoft.Exchange.Management.ExoPowershellModule.dll


I don’t know why the functions are split across a DLL and a PowerShell script. For this we need to ask Microsoft.

Nevertheless, the PowerShell script works as a wrapper for a function in the DLL. You can use your favourite .NET decompiler to look behind the scenes. I’m using ILSpy.

When you open the DLL, you’ll see it contains only one function: NewExoPSSession


The PowerShell script brings all the logic around this function! The important part whether a Refresh token is used or not is based on the fact whether the parameter UserPrincipalname was used, when you established the connection. If the parameter was used, the PowerShell script will create a global variable:

$global:UserPrincipalName = $UserPrincipalName;

The script pas on this variable to the function NewExoPSSession. And here comes the problem. The function will take different actions based on the value of the variable:


If the variable is NULL, you’re forced to run through the whole authentication process and perform MFA. If UserPrincipalName exists, depending on the existence of a Refresh token for the user, a new Access token s requested and you don’t have to run through the authentication process.

How can you workaround this issue, when you haven’t used the parameter UserPrincipalName as you might suffer the issue described later?

There are two options:

  • Once you’ve established a session, you need to create a global variable manual from the existing PSSession either this way:


  • modify the PowerShell script, once you have installed the module, by adding the following code after line 82

# If UserPrincipal is NULL, but a PSSession exist set variable to refresh token from cache
if ([System.String]::IsNullOrEmpty($global:UserPrincipalName) -and (-not [System.String]::IsNullOrEmpty($script:PSSession.Runspace.ConnectionInfo.Credential.UserName)))
    Write-PSImplicitRemotingMessage ('Set global variable UserPrincialName ...')
    $global:UserPrincipalName = $script:PSSession.Runspace.ConnectionInfo.Credential.UserName

With this additional code, the script creates for the current PSSession the global variable. It should look like this:


What is the difference between these two options?

Adding a global variable is much easier than modifying the PowerShell script, but the modified script makes life easier, when you have to switch PSSessions due to different customers and tenants. But whenever the module was updated and the script overwritten, you need to modify the script again.

Another improvement is that the existing tokens are used, when you’re switching between account and don’t have to do MFA (depending on your CA policy!).

How to check your existing tokens in your current PowerShell?

I highly recommend reading fellow MVP Vasil Michev article:

Hacking your way around Modern authentication and the PowerShell modules for Office 365

I currently have a case open with Microsoft. Maybe my modification will be considered and the script updated.

Usage for Windows PowerShell ISE

There are several examples out there how to use the module in ISE. Most of them advice you to import the DLL Microsoft.Exchange.Management.ExoPowershellModule.dll.

Let me tell you that this is the wrong way! Why? Just read the previous section and don’t skip the part where I describe how the module works. Briefly spoken, the problem is that you are using ONLY the funcion NewExoPSSession and NOT the main function Connect-EXOPSSession, which brings all the additional functionality.

The proper way of loading the module is to find the folder, which contains all the DLLs and PowerShell scripts, which means the following folder:


You can do this with the following one-liner:

Import-Module (Get-ChildItem -Path $($env:LOCALAPPDATA+"\Apps\2.0\") -Filter '*ExoPowershellModule.dll' -Recurse | Foreach{(Get-ChildItem -Path $_.Directory -Filter CreateExoPSSession.ps1)} | Sort-Object LastWriteTime | Select-Object -Last 1).FullName

With this all necessary DLLs and functions from the script will be imported and can be used in ISE.

Common issues

Reauthentication enforced after approx. 1 hour

This issue is related to the fact that currently an existing Refresh token is not used to request a new Access token after expiration. Workarounds are described here.

Blank screen during authentication with federated domains

I’ve seen this issue in combination with SSO. As an example when you have ADFS in place and have WIA (which makes perfect sense!) enabled and also the correct settings in your browser in place, but no exclusion in your proxy settings for your STS.

Note: Keep in mind that for the PSSession different proxy settings might be used than you have in your default browser set!

What happens is that you try to establish a connection and have used the parameter UserPrincipalName, the function NewExoPSSession triggers a web-request against the STS with the method AcquireToken from the AuthenticationContext class the DLL Microsoft.IdentityModel.Clients.ActiveDirectory.dll with the PromptBehavior Auto.

In this case you will see a blank login screen as the proxy will break WIA to the STS.

The fix would be a bypass for the STS, either in the proxy script or added to the proxy-bypass list.

Wrong user with federated domains

This issue is related to the previous one, but happens when you have properly configured your proxy-bypass list.

Note: Keep in mind that for the PSSession different proxy settings might be used than you have in your default browser set!

Assuming you want to connect to Exchange Online using from your machine and your user has a UserPrincipalname When you have used the parameter UserPrincipalname, the PromptBehavior Auto is used and when you get redirected to ADFS (or your SSO solution), your client will perform a silent login with the user and establish the connection to Exchange Online with these credentials instead of using

The fix for this is not to use the parameter UserPrincipalname. But keep in mind that in this case you either need to have one of the options in place or you have to go through the authentication process.

Why get my other PSSessions removed?

Of course you can create and have multiple PSSessions in a single PowerShell session. But all of them get removed by the function Connect-EXOPSSession, when it’s executed. The function is not only looking for its own PSSessions, nope it just removes all. Here is the code snippet:


Therefore you’ll see a note at the beginning of the article Connect to Exchange Online PowerShell by using MFA


Execution policy AllSigned

When your Windows PowerShell execution policy is set to AllSigned, you won’t be able to use this module. This is a matter of security and highly depends on you security settings. Read more about these kind of policies here:

Windows PowerShell Execution Policies

Why you cannot use it? Surprisingly the module is not signed. That’s also the reason why you’re able to modify and use the script mentioned in the workaround here.

One solution when your execution policy is set to AllSigned, is to sign it by yourself. Here are some links on how you can do:

Maybe worth to mention that you should NOT use SHA1 as described in Microsoft Docs as SHA1 is deprecated!


I hope I could clarify some facts and this post helps you managing Exchange Online in a secure manner with MFA enabled. If I missed something….feedback is always welcome!

10 thoughts on “Deep dive:Exchange Online PowerShell and MFA

  1. We just enabled MFA for all Office 365 admins this past weekend and we (Exchange Admins) are suffering big time with this session time out issues, your post was very enlightening and helped me to understand many things. Our main problem right now is not the continuos re-connection but the fact that long running scripts such as search-mailbox are getting terminated before they really finish their work. Changing the Access Token Lifetime in AzureAD seems to be the solution for this but I would prefer not to mess with it and have this resolved at the EXO module.

    Thank you for taking the time of documenting this. Really appreciated!


    • Hey Christian,
      thank you for the feedback. In regards of this specific issue, your session should be reconnect automatically. Once the Access token has expired, the Refresh token will be used to get a new one and re-establish the connection. But this happens only when you have used the -UserPrincipalName parameter or added the global variable manual.
      Besides this the remote PowerShell sessions got less reliable and slower over the last month. This was also reported to the PG at MS from several sources. They are working on getting things better. I know this is not great…myself and my team is also having a lot of issues in our daily work with this…

      Liked by 1 person

      • I have tried with using the -userprincipalname parameter and also added the global variable manually, but it seems that it takes a bit of time before a new token is fetched, leading to errors in the script for a while (missing out on some data). Do you have any idea, if and how I could check for the token validity and put the script in sleep for a bit before continuing? Maybe some sort of a try catch structure would suffice.


      • Hi,
        the token will be refreshed after 1 hour, which is the default valid time period. The issue you’re running into is well known, especially in large environments. I actually have no solution, which works for every scenario. The only way is to parse possible errors and take action on these.

        Liked by 1 person

  2. I’m confused on what exactly Connect-ExoPSSession does besides pass the value of the upn string to New-ExopPSSession. Would it be possible to achieve the same refresh functionality using New-ExoPSSession -UserPrincipalName [upn_here]?


    • Hi Britton,
      I agree it’s confusing. The problem is that it’s a combination of the DLL and the script. To have it properly working you want to call the script function Connect-EXOPSSession. Also the error handling is done in this function. Overall I’m hoping for a new module, which can be installed from PSGallery.


  3. Ingo, I really appreciate your deep dive into this module! One quick comment, the note you mentioned warning that you can’t connect to both security & compliance as well as exchange online is misleading. I’ve put in a request to get that changed in the documentation. The trick to connect to more than one pssession in the same window is to first use connect-ippsession which then exposes the commandlet new-exosession. You pointed out correctly that the connect-exosession removes all other pssessions, so here’s an example on how to connect to more than one:

    Connect-IPPSSession -PSSessionOption
    $EXOSession = New-ExoPSSession -pssessionoption
    Import-PSSession $EXOSession -Prefix EXO

    Keep up the great work with your blog!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s