Michel de Rooij, with over 25 years of mixed consulting and automation experience with Exchange and related technologies, is a consultant for Rapid Circle. He assists organizations in their journey to and using Microsoft 365, primarily focusing on Exchange and associated technologies and automating processes using PowerShell or Graph. Michel's authorship of several Exchange books and role in the Office 365 for IT Pros author team are a testament to his knowledge. Besides writing for Practical365.com, he maintains a blog on eightwone.com with supporting scripts on GitHub. Michel has been a Microsoft MVP since 2013.
After updating your PowerShell modules which support managing parts of the Microsoft 365, some of us are curious about what changes are introduced with the updated module. In the world of continuous change, it is hard to keep track of these changes. New cmdlets or parameters get added to support new features, and some get removed as they become obsolete. So, how to discover what those changes are after updating to the latest module?
Time to blog on a small script I created for this purpose a long time ago, Compare-Cmdlets.ps1. This script has two operating modes:
Export currently available cmdlets and parameters for supported modules.
Compare two exports of cmdlets & parameters and report the differences.
Currently, the following command sets are supported:
Module
Test Cmdlet
Export File
AzureAD
Get-AzureADUser
AzureAD-<version>.xml
ExchangeOnline
Get-Mailbox
ExchangeOnline-<version>.xml
ExchangeOnlineManagement
Get-ExoMailbox
ExchangeOnlineManagent-<version>.xml
MicrosoftOnline
Get-MsolUser
MSOnline-<version>.xml
Teams
Get-Team
MicrosoftTeams-<version>.xml
Command sets are exported per module, where a module is assumed to be present by a simple check for cmdlet availability (specified in column Test Cmdlet). That is, if Get-Mailbox is available, the ExchangeOnline module is assumed to be available. It does not distinguish between the Exchange PowerShell module or ‘classic’ Remote PowerShell session, nor will it take into account the repository origin of the module, nor if the Get-AzureADUser is coming from the AzureAD or AzureADPreview module.
That said, here’s how this is works. Load up PowerShell and have your modules installed and ready. Some modules like ExchangeOnlineManagement require connecting to the service first to import the cmdlet functions, so for ExchangeOnlineManagement run Connect-ExchangeOnline first. Same applies to the newer Teams modules, where the Skype Connector functions are only available after running New-CsOnlineSession.
Then run Compare-Cmdlets to export the cmdlets and parameters for those modules. The commands will by default be exported to an XML in a subfolder named ‘data’. The name of the file is mentioned in the table above. If you want to use a different folder to store the XML files, use DataFolder parameter.
Note that with Exchange, the cmdlets available to you depend on which role you have been assigned in Exchange’s Role-Based Access Control model. For example, if you haven’t explicitly assigned Mailbox-ImportRequest to your account, you will not see it in the exports. Therefor, when exporting module changes, it is required using an account with the same roles assigned to have proper exports. But when needed, you can also use it to report on command set differences between two Exchange Online accounts.
After updating some of the modules, or downloading one of the command set reference XMLs I stored with the script on GitHub, you can use Compare-Cmdlets to compare different versions of module exports. For example, to compare the cmdlets of Microsoft Teams module 1.1.4 with those after updating to 1.1.5, use
The cmdlet Get-TeamChannel has a new GroupId parameter.
The cmdlet New-CsGroupPolicyAssignment parameter PolicyType has been removed.
The cmdlet Add-TeamChannelUser is new.
Note that common parameters (e.g. Verbose and ErrorAction) and optional common parameters (e.g. WhatIf) are left out of the equation. Also, parameters are not compared in depth and only presence is checked. If for example a parameter changes type (e.g. string to multivalue), Compare-Cmdlets does not pick that up.
As-is, the script is made to run on demand from an interactive PowerShell session. Ideally, this would run scheduled and serverless from within the service, reporting changes by e-mail.
The script Compare-Cmdlets.ps1 can be downloaded from GitHub here. If you find this useful, would like to comment or have suggestions, use the comments below or leave them on GitHub.
The Exchange Team released the quarterly Cumulative Updates for Exchange Server 2019 as well as Exchange 2016. Like recent Cumulative Updates for these products, they require .NET Framework 4.8. Apart from fixes as well as security updates included from the previous CU, the Exchange 2019 CU7 also comes with an update for the Exchange Sizing Calculator.
Links to the updates as well as a description of changes and fixes are described below.
4570248 Get-CASMailbox uses wrong LDAP filter for ECPEnabled in Exchange Server 2019
4576652 Updates for Exchange Server 2019 Sizing Calculator version 10.5
4570252 Intermittent poison messages due to NotInBagPropertyErrorException in Exchange Server 2019
4576649 System.InvalidCastException when you change passwords in Outlook on the web in Exchange Server 2019
4570251 Inbox rule applying a personal tag doesn’t stamp RetentionDate in Exchange Server 2019
4570245 ESEUtil /p fails if any long value (LV) is corrupted in Exchange Server 2019
4570255 NullReferenceException occurs when running TestFederationTrust in Exchange Server 2019
4576650 Can’t add remote mailbox when setting email forwarding in Exchange Server 2019 Hybrid environment
4570253 CompletedWithErrors without details for mailbox migration batches in Exchange Server 2019
4570247 CSV log of Discovery export fails to properly escape target path field in Exchange Server 2019
4570246 EdgeTransport crashes with Event ID 1000 (exception code 0xc00000fd) in Exchange Server 2019
4570254 MSExchangeMapiMailboxAppPool causes prolonged 100% CPU in Exchange Server 2019
4563416 Can’t view Online user free/busy status in Exchange Server 2019
4576651 Can’t join Teams meetings from Surface Hub devices after installing Exchange Server 2019 CU5
4577352 Description of the security update for Microsoft Exchange Server 2019 and 2016: September 8, 2020
Exchange 2016 CU18 fixes:
4570248 Get-CASMailbox uses wrong LDAP filter for ECPEnabled in Exchange Server 2016
4570252 Intermittent poison messages due to NotInBagPropertyErrorException in Exchange Server 2016
4576649 System.InvalidCastException when you change passwords in Outlook on the web in Exchange Server 2016
4570251 Inbox rule applying a personal tag doesn’t stamp RetentionDate in Exchange Server 2016
4570245 ESEUtil /p fails if any long value (LV) is corrupted in Exchange Server 2016
4570255 NullReferenceException occurs when you run TestFederationTrust in Exchange Server 2016
4576650 Can’t add remote mailbox when setting email forwarding in Exchange Server 2016 Hybrid environment
4570253 CompletedWithErrors without details for mailbox migration batches in Exchange Server 2016
4570247 CSV log of Discovery export fails to properly escape target path field in Exchange Server 2016
4570246 EdgeTransport crashes with Event ID 1000 (exception code 0xc00000fd) in Exchange Server 2016
4570254 MSExchangeMapiMailboxAppPool causes prolonged 100% CPU in Exchange Server 2016
4563416 Can’t view Online user free/busy status in Exchange Server 2016
4576651 Can’t join Teams meetings from Surface Hub devices after installing Exchange Server 2016 CU16
4577352 Description of the security update for Microsoft Exchange Server 2019 and 2016: September 8, 2020
Notes:
These Cumulative Updates do not contain schema changes compared to their previous Cumulative Update.
There are Active Directory changes requiring you to run PrepareAD. Consult the Exchange schema versions page for object version numbers.
When upgrading from an n-2 or earlier version of Exchange, or an early version of the .NET Framework, consult Upgrade Paths for CU’s & .NET.
Don’t forget to put the Exchange server in maintenance mode prior to updating. Regardless, setup will put the server in server-wide offline mode post-analysis, before making actual changes.
When using Exchange hybrid deployments or Exchange Online Archiving (EOA), you are allowed to trail at most one version (n-1).
If you want to speed up the update process for systems without internet access, you can follow the procedure described here to disable publisher’s certificate revocation checking.
Cumulative Updates can be installed directly; no need to install RTM prior to installing Cumulative Updates.
Once installed, you can’t uninstall a Cumulative Update nor any of the installed Exchange server roles.
The order of installation shouldn’t matter with the “every server is an island” concept, yet recommended is to upgrade internet-facing, non-internet-facing servers first, followed by Edge Transports.
Caution:
As for any update, I recommend to thoroughly test updates in a test environment prior to implementing them in production. When you lack such facilities, hold out a few days and monitor the comments on the original publication or forums for any issues.
A quick blog on security updates for Exchange Server 2016 and Exchange Server 2019 released September 8th. These fixes address the following vulnerability:
CVE-2020-16875: Exchange Memory Corruption Vulnerability A remote code execution vulnerability exists in Microsoft Exchange server due to improper validation of cmdlet arguments. An attacker who successfully exploited the vulnerability could run arbitrary code in the context of the System user. Exploitation of the vulnerability requires an authenticated user in a certain Exchange role to be compromised. The security update addresses the vulnerability by correcting how Microsoft Exchange handles cmdlet arguments.
The exploits can be fixed by single security update, which you can find in the table below per current Exchange version.
Be advised that these security updates are Cumulative Update level specific. You cannot apply the update for Exchange 2016 CU17 to Exchange 2016 CU16. Also, the security update download has the same name for different Cumulative Updates, and I would suggest tagging the file name with the CU level, e.g. Exchange2016-CU17-KB4577352-x64-en.msp.
Also, run the Security Update from an elevated command prompt, to prevent issues during installation. And on a final note, as with any patch or update, I’d recommend to apply this in a acceptance environment first, prior to implementing it in production.
November 3rd, 2023: Updated SKU table with Clipchamp. Be advised, the MSCommerce module does not work with PowerShell 7 and requires compatibility mode (use Import-Module with -UseWindowsPowerShell).
It was in October 2019, that by means of Message Center bulletin MC163609 Microsoft announced that end users would receive the self-service purchasing option for Power Platform licenses (PowerBI, PowerApps and Flow). The announcement received quite some negative feedback, mostly because of the absence of administrative controls, with the risk of bypassing corporate purchasing as well as potential legal implications. Microsoft delayed the introduction, to launch it few months later in January this year, including the much requested controls.
Now MC220282 has appeared in the Office 365 Message Center, which announces the same self-service purchasing options for Microsoft Visio Plan 1 & 2 and Microsoft Project Plan 1 & 3. These purchasing options becomes available per September 15th.
The Message Center bulletin reads, “This change will not impact any existing settings you may have in place to manage self-service purchasing”. While true, administrators still might be faced with unexpected self-service capabilities for these new licenses. In this blog, I’ll talk you quickly through how to start managing these self-service capabilities, and how to modify these new ones.
Connecting and Managing
To start managing self-service capibilities, you need to install the MSCommerce PowerShell module, which Microsoft published at the PowerShell Gallery:
Install-Module MSCommerce
If you previously installed MSCommerce, update your module using:
Update-Module MSCommerce -Force
The current version of the module at the time of writing is 1.6. Now, to starting using the cmdlets provided in the module, you need to first connect to your Office 365 tenant:
Connect-MSCommerce
After completing login and succesfully completing any multi-factor authentication challenge, you can inspect the current self-service capabilities policy. First, there is a global policy named AllowSelfServicePurchase, which can be inspected using:
As you can see, the default option for purchasing options is set to Enabled. This value can’t be modified, which means every new product added to self-service purchasing will be enabled by default. This also means admins may need to monitor the message center for self-service purchasing changes, and when required proactively monitor and disable this capability for new products. To inspect the current setting for every current product, use:
As you can see, the self-service purchasing for the Power Platform products have been disabled. However, because the DefaultValue is Enabled, the purchase capabilities for these new products have been set to Enabled. If we want to disable this capability for these products, use the following cmdlet:
Note that self-service purchase capabilities are not available for Office 365 Government, Nonprofit, and Education tenants.
Approval Flow
In another bulletin (MC213897), Microsoft announced the roll-out of an feature for end users to request licenses trough a customizable message or workflow. This option should become available when self-service purchasing has been disabled. It allows admins assign requested licenses from the pool or make required purchases, and also report on these requests to track interest. The bulletin has been updated recently to mark completion of roll-out of the message part of this feature; the request part is scheduled for completion in September. Unfortunately, I haven’t been able to locate settings related to these features yet, but these might appear any time soon. The screenshot from the bulletin gives an indication of what to expect:
Windows 365 Per MC271483, Microsoft informed Office 365 customers in July 2021 end users are going to be able to buy Windows 365 (announcement) licenses through the self-purchase license mechanism as well. Windows 365 will come in 2 flavors: Windows 365 Enterprise and Windows 365 Business; the latter is aimed at smaller organizations, while the Enterprise edition will offer Cloud PCs with Endpoint Manager and Defender integration a.o. A third Hybrid Benefit license option is available if you already have Windows 11 Pro or Windows 10 Pro on a device.
The Windows 365 SKUs are mentioned in the table at the end of this article.
If you did not configure the self-service puchasing option as disabled by default, you can disable new Enabled options using the code example above, or if you need to block these individual Windows 365 options upfront, use the following processing the Windows 365 SKU’s:
Trials On December 17th, 2021, it was announced through the Message Center (MC306669) that per January 26th, 2022, self-service purchasing would also allow sign-up for Visio (Plan 1 or Plan 2) or Project (Plan 1 or Plan 3) trials. There are no separate SKUs for this, as it is managed by disabling the existing Visio or Project options.
SKU Overview The available self-service purchasing options per July 2022 are contained in the table below. However, these entries are subject to change and may vary depending on your region or type of tenant, i.e. commercial or government.
Update (3sep2022) Updated reflect Azure AD app roles.
Early June, Microsoft released a new PowerShell module for managing Exchange Online. This module got announced at Ignite 2019 already, but it took few months between going into preview end of last year before it finally reached Generally Available status. Usage of this module offers substantial improvements over the existing methods to connect to Exchange Online using Powershell, such as:
Leveraging the PowerShell module ecosystem to install and update the module. This as opposed to the click-to-run Microsoft Exchange Online Powershell Module or connecting through PowerShell remoting.
Support for Multi-Factor Authentication. This is something which the click-to-run module also offers but is not available when using PowerShell remoting.
Robustness. Existing sessions could easily timeout when you took a short break from the console. Or worse, your script could terminate in the middle of execution. This required you to reconnect or forced you to add resilience to your scripts by handling with these disconnects from the back end. The cmdlets of the EXOv2 module should be more robust and resilient.
Introduction of the Graph API support, which should show improvements in terms of speed. Microsoft indicated an 4-8 times improvement should be achievable, but your mileage may vary depending on the operation.
Support for PowerShell 6/7, core,and non-Windows operating systems is coming.
Exchange Online Management v2 module
The module has been baptized EXOv2 to indicate a major change compared to theclick-to-run module (hereafter referred to as EXOv1), and also because it uses Graph API, just like the AzureAD v2 module. The module is available in the PowerShell Gallery, and installation is straightforward. Open a PowerShell 5.1 or later session in elevated mode and run:
Install-Module ExchangeOnlineManagement
The EXOv2 cmdlets which are REST-basedand and leverage Graph API have their nouns prefixed with ‘EXO’, e.g. Get-EXOMailbox. Currently, there are 9 EXO cmdlets in the GA module, as well as few additional ones (more on those later). The regular commands such get Get-Mailbox are available as well after connecting to Exchange Online. This is similar behavior to the EXOV1 module, e.g.
Connect-ExchangeOnline [-UserPrincipalName <UPN>]
When required, satisfy the Multi-Factor Authentication logon process, and you are done. Be advised that the EXOv2 module also supports Delegated Access Permissions (DAP), allowing partners to connect to customer tenants by specifying -DelegatedOrganization <mycustomer.onmicrosoft.com> when connecting.
Also note that apart from the EXO cmdlets, the current module also offers few other interesting commands and helper functions apart from the ones for housekeeping:
Connect-IPPSSession to connect to Security & Compliance center or Exchange Online Protection, depending on licensing. This command was also available in EXOv1.
Get-UserBriefingConfig & Set-UserBriefingConfig. These are a bit out of context, as these commands allow you to enable or disable the Cortana Briefing for users.
IsCloudShellEnvironment indicates if you are running from PowerShell or Azure Cloud Shell, which might be useful in scripts to determine the current context.
The EXOv2 cmdlets and their regular equivalents are shown in the tablebelow:
EXO v1 or Remote PowerShell
EXO v2
Get-Mailbox
Get-EXOMailbox
Get-MailboxFolderPermission
Get-EXOMailboxFolderPermission
Get-CASMailbox
Get-EXOCASMailbox
Get-MailboxFolderStatistics
Get-EXOMailboxFolderStatistics
Get-MailboxPermission
Get-EXOMailboxPermission
Get-MobileDeviceStatistics
Get-EXOMobileDeviceStatistics
Get-Recipient
Get-EXORecipient
Get-RecipientPermission
Get-EXORecipientPermission
What you might notice is the absence of any Set-EXO* cmdlets. This is true, and there is no word yet on if and when Set cmdlets will be introduced. That said, the biggest speed gain is often in bulk retrieval of data, not so much in altering one or more attributes. Until then, do not despair though, as you can pipe output of the EXO cmdlets to their regular cmdlet, e.g.
Get-EXOMailbox michel | Set-Mailbox -EmailAddresses @{Add='michel@myexchangelabs.com'}
This construction will also provide the additional benefit of parallel processing of objects as they pass through the pipeline, but more on that later.
Now comes another thing you should be aware of, and that is that theseEXOv2 cmdlets might not use the same parameter sets as their v1 equivalent. Simply said, you cannot perform a simple Find and Replace operation in your script replacing Get-Mailbox with Get-EXOMailbox to start enjoying benefits of the new module.
When running a cmdlet like Get-EXOmailbox, you might notice that it returns only a subset of the attributes you might expect. Similar to what Properties does for ActiveDirectory module, the EXOv2 module requires you to specify the individual Properties to return. Alternatively, you can use PropertySets to select a predefined set of attributes. For example, Get-EXOMailboxsupports PropertySets such as All, Minimum (default), Policy, Quota and Retention to name a few. When needed, you can combine PropertySets, so something like the following is possible:
Get-EXOMailbox -Identity michel -PropertySets Quota,Policy
A small note on the PropertySet All: Just like Get-ADUser .. -Properties * is considered bad practice as you can impact resource usage and usually return more than what you need, using -PropertySets All for every call is also a bad idea. All is convenient, but make sure you only return the data you need. Be a good person.
To see which EXOv2 cmdlets support PropertySets, use:
Now, I suppose we want to get an indication of the performance enhancements by comparing EXOv2 and equivalent operation using v1 cmdlets. In this simple example we are returning quota information for some 50.000 mailboxes:
In this case, it is not the 4-8x improvement, but more than twice as fast is significant nonetheless. Especially if you are running interactively. To see the impact of parallel processing in the pipeline, we run the following:
As shown, there is a substantial increase in performance, but of course your mileage may vary depending on things like the number of objects, the attributes you require, and any filtering applied. Note that the PropertySet StatisticsSeed used in the example is a very minimal set of attributes which you can use if you only wish the refer to the objects, such as userPrincipalName, primarySmtpAddress and externalDirectoryObjectID.
Speaking of filtering, one would expect that server-side filtering (-Filter) would show an improvement in terms of speed over client-side filtering (Where), as filtering at the source is far more efficient in terms of result set and data to send over. However, it seems that due to the nature of a shared environment, sending superfluous data over the wire is less of a penalty than local filtering. Of course, your mileage may also vary here, so experiment what works best for your situation. Also, not every attribute is supported for filtering with these EXO cmdlets, which lies in how Graph exposes data. More information on that here.
When your session times out or disconnects, you will see that the module tries to reconnect your session; something which you would have to programmatically solve for the v1 module or regular remote PowerShell:
Certificate-based Authentication
Exchange administrators often have a requirement to run unattended scripts against Exchange Online, for example scheduled reports or as part of another process. In the past, this lead to setups where service accounts and stored credentials were used. Later this was improved by the ability to apply Conditional Access to limit these logons to on-premises infrastructure.
The problem with Multi-Factor Authentication is that it requires interaction with end-user to approve the sign-on. Of course, while your token is still valid, you can easily (re)connect to Exchange Online just by providing the Username Principal Name, which will reuse the token if it didn’t expire. But all in all, these solutions are high maintenance, and far from ideal from a security perspective.
Here comes certificate-based authentication, which is supported in version 2.0.3 and up of the EXOv2 module. In short, certificate-based authentication allows you to log on to Exchange Online using:
PowerShell
EXOv2 module
A (self-signed) certificate containing private key
Enterprise App registration in Azure Active Directory which contains the public key of this certificate, and proper assigned Azure AD role(s).
Note: Enterprise app registration may require Azure AD P1/P2 license.
To install the EXOv2 2.0.3 version of the module (preview at time of writing), use:
Note that it might complain if you have the GA version of the module installed, in which case you need to uninstall the GA module first, or you can install them side-by-side by specifying -Force.
Next, we need to create a self-signed certificate. To accomplish this, we can use the script published here. To create the certificate, simply use:
Note that you need to provide a password to protect the PFX file containing the private key. Also do not forget to import the PFX in your local certificate store. When importing, you can mark the certificate as non-exportable, which prevents admins to transfer the certificate to other systems.
After importing, you can check for the certificate’s presence using:
Get-ChildItem Cert:\CurrentUser\My | Where {$_.Subject -eq 'CN=EXOv2'}
The Subject should be the CommonName you used when generating the certificate. The thumbprint of our certificate is 49A4A73B4696718676770834BCD534DE35030D2C. We are going to use this later on to connect.
Now we need to set things up in Azure Active Directory:
Open up the Azure Active Directory Portal, and navigate to Active Directory.
Select App registrations, and click New registration.
Give the App a meaningful Name, and select Accounts in this organizational directory only. Set Redirect URI to Web and leave the URL blank. Then, click Register.
Note that our App has been assigned an Application (Client) ID. Make note of this value, as we will need it to connect later on.
Next, we need to configure the App permissions. Select API permissions. User.Read should show up as default. Click Add a permission, and locate Office 365 Exchange Online from the APIs my organization uses tab. Select Application permissions, and in the next screen expand Exchange and check Exchange.ManageAsApp. We are done here, so click Add permissions.
Only thing left now is to Grant admin consent, which can be done by clicking Grant admin consent for <tenant>. When done, the Status column for Exchange.ManageAsApp permission should have changed to Granted for <tenant>.
Now we need to associate this App with out certificate. Select Certificates & Secrets, and click Upload certificate. Pick the certificate file which we generated earlier, and select Add.
Laststep is to assign the App one of the built-in Azure AD roles.Go to the Azure Active Directory blade, and selectRoles andadministrators. To manage Exchange Online using PowerShell, you need to assign the Exchange Administrator role; for Security & Compliance, you can assign the Compliance Administrator role.
Select the desired role(s), and click Add assignments in theassignments overview screen. Note that when picking security principals, theApp might not show up initially, and typing its first few letters might help. Click Add to assign therole.
Note that the UserName mentioned in the overview is the Application ID.
Now we are done configuring the back end, we can look again at connecting. This should now be as simple as running:
CertificateThumbprint is the thumbprint of the self-signed certificate you created earlier.
AppID is the Application (Client) ID of the registered App.
<tenant>.onmicrosoft.com the initial domain name of your tenant.
Note that you can also connect specifying the CertificateFile instead of Thumbprint, but then you need to provide the password as well via CertificatePassword. Having the certificate in the certificate store of the administrator account or account running the task and just specifying the thumbprint is more convenient and requires zero interaction.
If all steps above were followed correctly, you should now be connected to Exchange Online, without anyMFA interaction.
A final note is that Connect-IPPSSession mentioned earlier does not support certificate-base authentication.
What about other Workloads
You can use the same certificate-based authentication to connect to several other workloads as well. That is, provided you have installed the required PowerShell module and the Azure AD role you assigned to the Application has adequate permissions. You can use the commands below to connect to these workloads. A small note that the commands to connect may use a different parameter names for AppId or Organization, e.g. AppId, ApplicationId or ClientId and Organization and TenantId are same things in the examples below.
The logons which are performed in the context of the Application are viewable in the Azure Sign-Ins at https://aka.ms/iam/rtsp
Note that this view is currently in preview, and there might be a slight delay before logon shows up.
Final Notes
It would be nice if there would be a way to incorporate Exchange granular Role-Based Access Control model into the permissions model. Granting Apps only the built-in Azure AD roles is somewhat limiting, and it would be nice to restrict accounts in only being able to run the cmdlets and parameters they need to use.
When running Exchange cmdlets, you will find these in the audit log but with the <tenant>\AppID as UserName. Therefore, best thing to do is to use a single App registration for each individual administrator or process, instead of using a single App registration and multiple certificates.
And finally, it would be nice if the various teams would align their cmdlet and parameter naming schemes for consistency.