Connect to Exchange Online PowerShell with EXO V2 Module

Published:11 August 2020 - 11 min. read

Azure Cloud Labs: these FREE, on‑demand Azure Cloud Labs will get you into a real‑world environment and account, walking you through step‑by‑step how to best protect, secure, and recover Azure data.

If you’re an Office 365 administrator, you’re probably using PowerShell scripts to perform various automation tasks. However, it is unlikely that you’re using MFA to authenticate and connect to Exchange Online via PowerShell for unattended scripts. This would mean that your scripts are still using basic authentication.

Microsoft initially planned to remove basic authentication for Exchange Online on October 13, 2020. Then, the plan was moved to a tentative date of the second half of 2021.

Sooner than later, admins need to redesign their scripts while considering the following:

  • PowerShell scripts must use modern authentication while keeping the ability to run unattended.
  • PowerShell scripts must authenticate securely without having to exclude a service account for multi-factor authentication in Office 365.

Previously, those two conditions could not be met with the existing solutions. Fortunately, Microsoft has released Exchange Online V2 PowerShell module version 2.0.3-preview. This new version added the ability to use a certificate-based authentication associated with an Azure AD app.

In this article, you will learn how to prepare to use the EXO V2 module to run Exchange Online unattended scripts with app-only modern authentication. You’ll learn how to:

  • Register a new app in Azure Active Directory and enable its service principal.
  • Assign API permissions and roles.
  • Generate and upload a self-signed certificate.
  • Use the app and certificate to authenticate and connec to Exchange Online PowerShell.

With that said, don’t expect to see many click-and-point instructions in this article. Most of the walkthrough instruction will be done in PowerShell.

Prerequisites

For you to follow along successfully, make sure that you have the following requirements in place.

  • Access to an Office 365 tenant with Global Admin permissions. Use a non-production/dev tenant for testing. You should consider signing up for an Office 365 trial tenant.
  • A Windows computer with Windows PowerShell 5.1. In this article, you will use a computer that is running on Windows 10 version 1909.
  • Install the latest AzureAD PowerShell Module. The current version used in this article is 2.0.2.106.
  • A code editor such as Notepad++, Atom, Windows PowerShell ISE, or Visual Studio Code. Use whichever one you’re most comfortable with.
  • Create a working directory for your test. In this article, the working directory is C:\exo_v2_demo.
  • Download a copy of the Create-SelfSignedCertificate.ps1 script and save it to your working directory. In this article, we will save the script in the working directory C:\exo_v2_demo.

Setting Up App-Only Authentication using PowerShell

You might be used to using a generic account, which is often referred to as a service account to run your PowerShell scripts. That type of account is a “shared” account in nature. Anyone who knows that account’s credential can use it to log in and perform all sorts of admin stuff on your organization; that is a security concern.

The app-only authentication attempts to solve that security concern. App-only authentication requires the use of an Azure AD app with service principal and selected permissions and role. Use token or certificate to authenticate.

Creating an Azure AD Application with API Permissions

The first step is to create a new app in Azure AD with the right API permissions. First, open an elevated Windows PowerShell (run as admin) and make sure to connect to Azure AD.

Connect to Azure AD
Connect to Azure AD

The code below will register a new app in Azure AD with the name Exo_V2_App and assign the Exchange.ManageAsApp permission of the Office 365 Exchange Online API.

If you prefer to use a different name for your app, edit the value of the $appName variable in the code below. Copy the code and run it in PowerShell.

# CODE TO REGISTER APP, ASSIGN API PERMISSIONS, AND ENABLE SERVICE PRINCIPAL
## Define the client app name
$appName = 'Exo_V2_App'

## Get the Office 365 Exchange Online API details.
$api = (Get-AzureADServicePrincipal -Filter "AppID eq '00000002-0000-0ff1-ce00-000000000000'")

## Get the API permission ID
$permission = $api.AppRoles | Where-Object { $_.Value -eq 'Exchange.ManageAsApp' }

## Build the API permission object (TYPE: Role = Application, Scope = User)
$apiPermission = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId  = $api.AppId ;
    ResourceAccess = [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = $permission.Id ;
        Type = "Role"
    }
}

## Register the new Azure AD App with API Permissions
$myApp = New-AzureADApplication -DisplayName $appName -ReplyUrls 'http://localhost' -RequiredResourceAccess $apiPermission

## Enable the Service Principal
$mySP = New-AzureADServicePrincipal -AppID $myApp.AppID

## Display the new app properties
$myApp | Format-List DisplayName,ObjectID,AppID

The demonstration below shows the code in action. In the end, it will present the DisplayName, ObjectID, and AppID properties of the new app is displayed. $myApp variable stores these properties. The $mySP variable holds the property values of the service principal.

Register a new Azure AD app
Register a new Azure AD app

TIP: Make sure to save the application’s properties for quick reference.

Export the property values of the application using this command below.

$myApp | Export-Csv -NoTypeInformation "$($appName).csv"

Assigning an Azure AD Role to the Application

After creating the app, the next step is to assign an Azure AD role to the app’s service principal. You’ll need to decide the type of role you should assign to your app.

The valid supported roles for Exchange Online V2 are these below.

  • Company administrator
  • Compliance administrator
  • Security reader
  • Security administrator
  • Helpdesk administrator
  • Exchange Service Administrator
  • Global Reader

You should only assign the least privileged role that you deem appropriate to your script. In this example, the code below will assign the Exchange Service administrator role to the app’s service principal.

## The role to assign to your app
$directoryRole = 'Exchange Service Administrator'

## Find the ObjectID of 'Exchange Service Administrator'
$RoleId = (Get-AzureADDirectoryRole | Where-Object {$_.displayname -eq $directoryRole}).ObjectID

## Add the service principal to the directory role
Add-AzureADDirectoryRoleMember -ObjectId $RoleId -RefObjectId $mySP.ObjectID -Verbose

When you run the command above in PowerShell, you should see an output similar to the one shown in the demo below.

Assigning an Azure AD role to the app
Assigning an Azure AD role to the app

Generating and Attach a Self-Signed Certificate to the Application

The next step is to generate a self-signed certificate and attach that certificate to your app. You’ll need to use the Create-SelfSignedCertificate.ps1 script for this step.

The script below will generate a self-signed certificate using your app’s name as its subject name, such as Exo_V2_App. The certificate will be valid for one (1) year.

If you want to change the certificate’s validity, you should change the $certYears value to the number of years you prefer. You may also change the $certPassword value if you want to use a different password for the resulting certificate (PFX) file.

## Number of years of certificate validity
$certYears = 1

## Certificate (PFX) password
$certPassword = '4~mt4G*8Qd@G'

.\Create-SelfSignedCertificate.ps1 -CommonName $appName `
-StartDate (Get-Date).AddDays(-1) `
-EndDate (Get-Date).AddYears($certYears) `
-Password (ConvertTo-SecureString $certPassword -AsPlainText -Force) `
-Force

When you run the code above in PowerShell, two files will be created, as you can see from the demo below.

Generate a self-signed certificate
Generate a self-signed certificate

The next step is to upload the certificate that you’ve just created to your Azure AD app. The code below will locate the certificate (.CER) file in your working directory and then attach it to the Azure AD app. There’s no need to modify the code, just copy and run it in PowerShell.

## Get the certificate file (.CER)
$CertificateFilePath = (Resolve-Path ".\$($appName).cer").Path
## Create a new certificate object
$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cer.Import("$($CertificateFilePath)")
$bin = $cer.GetRawCertData()
$base64Value = [System.Convert]::ToBase64String($bin)
$bin = $cer.GetCertHash()
$base64Thumbprint = [System.Convert]::ToBase64String($bin)

## Upload and assign the certificate to application in AzureAD
$null = New-AzureADApplicationKeyCredential -ObjectId $myApp.ObjectID `
-CustomKeyIdentifier $base64Thumbprint `
-Type AsymmetricX509Cert -Usage Verify `
-Value $base64Value `
-StartDate ($cer.NotBefore) `
-EndDate ($cer.NotAfter)

When you run the code above in PowerShell, you can expect to see no output, unless an error was encountered. The demo below shows the result when the code execution is successful.

Attaching the certificate to the Azure AD app
Attaching the certificate to the Azure AD app

You’re almost done with your set up. The next step is for a Global Admin to grant consent to your Azure AD app. This step can be executed by yourself or by another Global Admin in your organization.

A Global admin can grant the consent from the Azure Active Directory admin center. But, you can also just generate a consent URL in PowerShell. Either give it to the Global admin, or you can use it yourself to grant consent.

The consent URL follow this format below.

https://login.microsoftonline.com/{TenantID}/adminconsent?client_id={ApplicationID}

The {TenantID} value is the directory ID or verified domain of your Office 365 tenant. The {ApplicationID} value is the AppID of the Azure AD application that you created previously.

The code below will generate the consent URL based on the values stated above. The consent URL will then be displayed on the screen and launched using the computer’s default browser.

## Get the TenantID
$tenantID = (Get-AzureADTenantDetail).ObjectID

## Browse this URL
$consentURL = "https://login.microsoftonline.com/$tenantID/adminconsent?client_id=$($myApp.AppId)"

## Display the consent URL
$consentURL

## Launch the consent URL using the default browser
Start-Process $consentURL

Refer to the demo below to see what happens when you run the code above in PowerShell.

Generate consent URL and grant consent
Generate consent URL and grant consent

Connecting to Exchange Online PowerShell

After creating the app and assigning the permission and role, you’ll now need to upload and attach the certificate. You are now ready to connect to Exchange Online PowerShell using the app’s certificate credentials.

There are two ways to utilize the certificate credentials; using the local certificate file (.pfx), and using the thumbprint of the certificate installed in the current user’s personal certificate store.

Authenticating Using Local PFX Certificate

To connect to Exchange Online PowerShell using a local certificate to authenticate, you must have the following information:

  • The Directory ID or verified domain of your Azure AD tenant.
  • The AppID of the application that you registered previously.
  • The full file path of the self-signed PFX certificate.
  • The password of the seld-sign PFX certificate.

Next, change the value of the $tenantID, $appID, $CertificateFilePath, and $pfxPassword variables in the code below. Once you’ve change the value of the variables as needed, copy the code and run it in PowerShell.

## set the tenant ID (directory ID or domain)
$tenantID = 'poshlab.ga'

## Set the Exo_V2_App app id
$appID = '3f76be04-5cf0-47f1-9df6-d05981a450fc'

## Set the certificate file path (.pfx)
$CertificateFilePath = 'C:\exo_v2_demo\Exo_V2_App.pfx'

## Get the PFX password
$pfxPassword = '4~mt4G*8Qd@G'

## Connect to Exchange Online
Connect-ExchangeOnline -CertificateFilePath $CertificateFilePath `
-CertificatePassword (ConvertTo-SecureString -String $pfxPassword -AsPlainText -Force) `
-AppID $appID `
-Organization $tenantID

The demo below shows that the connecting to Exchange Online PowerShell si successful using the local certificate file authentication.

Authenticating Using Local PFX Certificate
Authenticating Using Local PFX Certificate

If you look closely at the code again, one glaring problem is that the pfx certificate password is visible. You may consider using some kind of secret management solution to store the certificate credential for added security.

Authenticating Using Certificate Thumbprint

This authentication method can be considered more secure than using the local certificate with a password. In this method, you will need to import the certificate to the Personal certificate store. You only need to use the thumbprint to identify which certificate to use for authentication.

The first step is to import the PFX certificate into the Personal certificate store. Note that you only need to do this step once for the current user.

# CODE TO IMPORT THE PFX CERTIFICATE INTO THE CURRENT PERSONAL CERTIFICATE STORE
## Set the certificate file path (.pfx)
$CertificateFilePath = 'C:\exo_v2_demo\Exo_V2_App.pfx'

## Get the PFX password
$mypwd = Get-Credential -UserName 'Enter password below' -Message 'Enter password below'

## Import the PFX certificate to the current user's personal certificate store.
Import-PfxCertificate -FilePath $CertificateFilePath -CertStoreLocation Cert:\CurrentUser\My -Password $mypwd.Password

The demo below shows how to import the PFX certificate into the personal certificate store.

Importing the PFX certificate
Importing the PFX certificate

As you can see above, you will see the result of the PFX import process. Make sure to copy the value of the Thumbprint for quick reference later on.

After importing the certificate, your scripts can now authenticate with Exchange Online PowerShell using its thumbprint.

Edit the $tenantID, $appID, and $CertificateThumbPrint in the code below to match your correct values. Then, copy and run the code in PowerShell.

## set the tenant ID (directory ID or domain)
$tenantID = 'poshlab.ga'

## Set the Exo_V2_App app id
$appID = '3f76be04-5cf0-47f1-9df6-d05981a450fc'

## Set the certificate thumbprint
$CertificateThumbPrint = 'DED486B87C38CEA966EC71F8EE90BB3AAE694A74'

## Connect to Exchange Online
Connect-ExchangeOnline -CertificateThumbPrint $CertificateThumbPrint `
-AppID $appID `
-Organization $tenantID

Running the code above in PowerShell will give you an output similar to the demo below.

Output after running the code in Powershell
Output after running the code in Powershell

Connecting and Running Exchange Online PowerShell Scripts with App-Only Authentication

So far, in this article, you’ve only been copying and pasting code into PowerShell. But now that you’re familiar with how app-only authentication works, you should apply it to run your PowerShell scripts.

The script below connects to Exchange Online PowerShell using the certificate thumbprint to authenticate. Then, once connected, the script will get all the mailboxes available. The script is a saved in C:\exo_v2_demo\ListExoMailbox.ps1

## Clean up Exchange Online Session
Get-PSSession | Where-Object {$_.name -like "ExchangeOnline*"} | Remove-PSSession -ErrorAction SilentlyContinue

## set the tenant ID (directory ID or domain)
$tenantID = 'poshlab.ga'

## Set the Exo_V2_App app id
$appID = '3f76be04-5cf0-47f1-9df6-d05981a450fc'

## Set the certificate thumbprint
$CertificateThumbPrint = 'DED486B87C38CEA966EC71F8EE90BB3AAE694A74'

## Connect to Exchange Online
Connect-ExchangeOnline -CertificateThumbPrint $CertificateThumbPrint `
-AppID $appID `
-Organization $tenantID

## Get All Mailbox
Write-Output "Getting all mailboxes"
Get-Mailbox -ResultSize Unlimited | Format-Table Name,DisplayName

After you’ve saved the script, run it in PowerShell. The script should connect to Exchange Online without any prompts and perform its function. Refer to the results shown in the demo below.

PowerShell Script with App-Only Authentication
PowerShell Script with App-Only Authentication

Summary

The release of the EXO V2 PowerShell module is a welcome development. Knowing that Microsoft has decided to take away basic authentication for connecting to Exchange Online via PowerShell, and having this new app-only authentication feature allows admins to update their existing scripts.

However, implementing EXO V2 app-only authentication is not without its challenges.

  • Using a local certificate file still requires a PFX password. If you can implement a credential or secret management strategy, you should get around this password exposure issue.
  • Using a certificate in the personal store is relatively more confident. But the certificate can only be accessed by the current user. So, if you’ve set up a scheduled task to run the script using the credential of UserA, then the certificate must be imported to the personal certificate store of UserA.
  • Certificates have expiration dates. This would mean that certificates need to be monitored, renewed, and re-attach it to the Azure AD app. Otherwise, the script will stop working due to authentication failure.

The benefits of using the new EXO V2 PowerShell module outweigh these challenges.

In this article, you’ve learned the step-by-step process of setting up the app-only authentication for the Exchange Online V2 PowerShell module. You’ve also learned how to connect to Exchange Online PowerShell using a self-signed certificate.

Now you do not have to deal with Exchange Online PowerShell MFA prompts and use app-only certificate-based authentication in your scripts.

If you want to take things a little further, perhaps you’d want to test what you’ve learned in this article with Jenkins or Azure Automation. Or, maybe build your own PowerShell functions and modules

Thank you for reading!

Further Reading

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!