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:
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:
$env:USERPROFILE\AppData\Local\Apps\2.0
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
- UserPrincipalName or not
- Usage for Windows PowerShell ISE
- Common issues:
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?
- Once you’ve established a session, you need to create a global variable manual from the existing PSSession either this way:
$global:UserPrincipalName='admin@contoso.com'
- 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 admin@contoso.com from your machine and your user has a UserPrincipalname user1@contoso.com. 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 user1@contoso.com and establish the connection to Exchange Online with these credentials instead of using admin@contoso.com.
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!
Conclusion
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!
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!
LikeLike
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…
Ciao,
Ingo
LikeLiked 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.
LikeLike
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.
Ciao,
Ingo
LikeLiked by 1 person
Are you having issues with ISE freezing after 5-10 minutes of using it. This only happens when we have MFA turned on.
LikeLike
Hi Tim,
yes there is a known bug in ISE when WinForms is used, which is when you’re doing MFA. Check this out:
https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11733891-powershell-ise-crashes-after-loading-winforms
https://office365.uservoice.com/forums/264636-general/suggestions/19537864-powershell-ise-freezes-when-mfa-is-enabled
Ciao,
Ingo
LikeLike
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]?
LikeLike
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.
Ciao,
Ingo
LikeLike
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!
austinmc
LikeLike
Hey Austin,
that’s a valid point and thanks for the tip!
Ciao,
Ingo
LikeLike