How to Connect to Exchange Online PowerShell via v2 Module
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 connect 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.
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.
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.
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 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.
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.
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.
Granting Admin Consent to the Application
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.
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.
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.
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.
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.
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.