Module Updates: What’s New?

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:

ModuleTest CmdletExport File

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

.\Compare-Cmdlets.ps1 -ReferenceCmds data\MicrosoftTeams-1.1.4.xml -DifferenceCmds data\MicrosoftTeams-1.1.5.xml

From the output, we see for example that:

  • 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.

Exchange Online Management using EXOv2 module


Update (22Nov2020) Updated API permissions to reflect removal of Exchange app permissions, replaced with Office 365 Exchange Online permissions.

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 the click-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-based and 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
(DAP), allowing partners to connect to customer tenants by
-DelegatedOrganization <>
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 table below:

EXO v1 or Remote
EXO v2

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
spair though, as you can pipe output of the EXO cmdlets to their regular cmdlet,

Get-EXOMailbox michel | Set-Mailbox -EmailAddresses @{Add=''}

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 these EXOv2 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 Active Directory 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-EXOMailbox supports 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:

(Get-Command -Noun EXO* -Module ExchangeOnlineManagement).Where{$_.Parameters.propertySets}


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
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:

Install-Module ExchangeOnlineManagement -AllowPrerelease

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:

.\Create-SelfSignedCertificate.ps1 -CommonName 'EXOv2' -StartDate 7/30/2020 -EndDate 7/30/2021

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.

Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath .\EXOv2.pfx -Password (Read-Host -AsSecureString)

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:

  1. Open up the Azure Active Directory Portal, and navigate to Active Directory.
  2. Select App registrations, and click New registration.
  3. 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.
  4. 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.
  5. 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>.

    API Permissions
  6. 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.

  7. Last step is to assign the App one of the built-in Azure AD roles. Go to the Azure Active Directory blade, and select Roles and administrators. Unfortunately, only the following built-in Azure AD roles are supported at this moment:

    Global Reader, Global Administrator
    Security Reader, Security Administrator
    Helpdesk Administrator, Compliance Administrator
    Exchange Administrator

    Select one of the roles, and click Add assignments in the assignments overview screen. Note that when picking security principals, the App might not show up initially, and typing its first few letters might help. Click Add to assign the role.

    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:

Connect-ExchangeOnline -CertificateThumbprint '49A4A73B4696718676770834BCD534DE35030D2C' -AppId '0d3f8f4c-34fb-4a22-8466-80fd7379593b' -Organization '<tenant>'


  • CertificateThumbprint is the thumbprint of the self-signed certificate you created earlier.
  • AppID is the Application (Client) ID of the registered App.
  • <tenant> 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 any MFA 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.

AzureAD (2.x or Preview)

Connect-AzureAD -CertificateThumbprint '49A4A73B4696718676770834BCD534DE35030D2C' -ApplicationId '0d3f8f4c-34fb-4a22-8466-80fd7379593b' -TenantId '<tenant>'

MicrosoftTeams (GA or Test)

Connect-Microsoftteams -CertificateThumbprint '49A4A73B4696718676770834BCD534DE35030D2C' -ApplicationId '0d3f8f4c-34fb-4a22-8466-80fd7379593b' -TenantId '<tenant>'

Microsoft Graph

Connect-Graph -CertificateThumbprint '49A4A73B4696718676770834BCD534DE35030D2C' -ClientId '0d3f8f4c-34fb-4a22-8466-80fd7379593b' -TenantId ''


The logons which are performed in the context of the Application are viewable in the Azure Sign-Ins at

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.


Exchange Certificate Reporting

powershellA quick tip on retrieving the expiration of certificates configured on your Exchange servers. While some certificate providers like DigiCert will proactively notify you when certificates are expiring in the near future, you may want to run such a report yourself. Or perhaps you want to verify configured certificates on all your Exchange servers are aligned.

To accomplish this, you could use readily available scripts, such as this one published by fellow MVP Paul Cunningham. But with some PowerShell you could easily construct yourself a one-liner which will perform the same task. We will first show the one-liner, after we will dissect and talk you through it. Note that being a lazy typist, I used several aliases to make the whole command a bit shorter, but not a lot.

A command to retrieve basic certificate reporting for Exchange servers in your environment is as follows (wrapped for readability):

$D=(Get-Date).AddDays(30); Get-ExchangeServer | %{$S=$_.Identity;$R=$_.ServerRole; Get-ExchangeCertificate -Server $S |
Sort NotAfter | Select @{n='Server';e={'{0} ({1})' -f $S,$R}},
@{n='CertSubject';e={($_.Subject -split '( , )*..=')[1]}},
@{n='Expires';e={'{0:MM/dd/yyyy}' -f $_.NotAfter}},
@{n='IssuedBy';e={($_.Issuer -split '(, )*..=')[1]}},
@{n='Domains';e={$_.CertificateDomains -join ','}},
@{n='Alert';e={' !'[(Get-Date $_.NotAfter) -le $D]}},*} |
ft -a Alert, CertSubject, Status, Expires, IsSelfsigned, IssuedBy,
Services, Thumbprint, Domains -GroupBy Server | Out-String -Width 8192

Sample output


$D=(Get-Date).AddDays(30) | Get-ExchangeServer

First, we want get a visual indication of certificates expiring in the coming 30 days. The command is followed by a semi-colon, which can be used to separate commands on the same line. The first cmdlet in our pipeline is Get-ExchangeServer, which returns all Exchange server objects.

%{$S=$_.Identity;$R=$_.ServerRole; Get-ExchangeCertificate -Server $S | Sort NotAfter | Select @{n='Server';e={'{0} ({1})' -f $S,$R}}, @{n='CertSubject';e={($_.Subject -split '( , )*..=')[1]}}, @{n='Expires';e={'{0:MM/dd/yyyy}' -f $_.NotAfter}}, @{n='IssuedBy';e={($_.Issuer -split '(, )*..=')[1]}}, @{n='Domains';e={$_.CertificateDomains -join ','}},@{n='Alert';e={' !'[(Get-Date $_.NotAfter) -le $D]}},*}

We are passing every Exchange server object to ForEach (%). For each of these objects, we will perform the following tasks:

  • First, we store its current Identity ($S) and Serverrole ($R) property in variables for later usage. This, because if we create a calculated properties later on, we have no reference anymore to the Exchange object in the calculated field expression, as $_ will then contain the current object passed to Select (Select-Object).
  • Next, we retrieve all certificates from the Exchange server we are looking at using Get-ExchangeCertificate, and we pipe those certificate objects to sort to order them by expiration date.
  • We then create several calculated properties in the pipeline stream:
    • A property named Server will contain a formatted string consisting of the server Identity ($S) and its server roles ($R).
    • A property named CertSubject, containing the name of the subject, without the ‘CN=’ prefix.
    • A property expires with a formatted expiration string (NotAfter).
    • A property named Issues, containing the name of the issuer of the certificate, without the ‘CN=’ prefix.
    • A property Domains containing the SAN names of the certificate, separated by commas.
    • A property Alert, showing an exclamation mark when certificate expires (NotAfter) before the date determined earlier ($D).
    • All other certificate properties are also retained by finally selecting all properties (*).
ft -a Alert, CertSubject, Status, Expires, IsSelfsigned, IssuedBy, Services, Thumbprint, Domains -GroupBy Server | Out-String -Width 8192

Finally, we format the output by selecting and ordering properties using Format-Table (ft), auto-sizing (-a) columns. In addition to the previously added calculated properties, we also return the SelfSigned, Services and Thumbprint properties. Using the GroupBy parameter, we make Format-Table group the objects on a specific property, in this case Server. Because the output can be very wide we use Out-String, specifying a large width to generate output larger than the host session without wrapping or truncating output.

Blocking Mixed Exchange 2013/2016 DAG

Ex2013 LogoIn the RTM version of Exchange 2016, there’s an issue in that it is allows you to add Exchange 2016 Mailbox servers to Exchange 2013 Database Availability Groups, and vice-versa. As stated in the Release Notes (you do read those?), creating such a mixed version DAG is not supported. In theory, you could even jeopardize your Exchange data, as database structures from both versions are different. This action is also not prevented from the Exchange Admin Center, requiring organizations to have very strict procedures and knowledgeable Exchange administrators.

If you are worried about this situation and you want to prevent accidently adding Mailbox servers to an existing DAG consisting of members of a different Exchange version, there is a way (until this is blocked by the product itself, of course). Cmdlet Extension Agents to the rescue!

The Scripting Agent not only allows you to add additional instructions to existing Exchange cmdlets, but also to provide additional validation before cmdlets are executed. I did two short articles on Cmdlet Extension Agents’ Scripting Agent here and here, so I will skip introductions.

First you need to download a file named ScriptingAgentConfig.xml from the location below. If you already have Scripting Agents, you need to integrate the code in your existing ScriptingAgentConfig.xml files. The code checks if the server you want to add using the Add-DatabaseAvailabilityGroup cmdlet is of a different major version than one of the current DAG members.

Next, you need to copy this ScriptingAgentConfig.xml file to $ENV:ExInstallPath on every Exchange 2013 and Exchange 2016 server in your organization, e.g. C:\Program Files\Microsoft\Exchange Server\V15\Bin\CmdletExtensionAgents\ScriptingAgentConfig.xml.  To help your with this process, Exchange fellow Paul Cunningham made a small script to push this XML from the current folder to every Exchange server in your organization, PushScriptingAgentConfig.ps1.

Last step is to enable the Scripting Agent using:

Enable-CmdletExtensionAgent ‘Scripting Agent’

After distributing the scripting agent file and enabling the scripting agent, when you try to add an Exchange 2016 (version 15.1) server to an Database Availability Group consisting of Exchange 2013 Mailbox servers, using Add-DatabaseAvailabilityGroupServer, you will receive an error message:


This also works vice-versa, thus when you inadvertently try to add Exchange 2013 servers to an Exchange 2016 Database Availability Group, provided you distributed the XML on the Exchange 2013 servers as well. The error is also thrown when you try to perform this action using the Exchange Admin Console.

You can download the ScriptingAgentConfig.XML for blocking Mixed Exchange 2013/2016 DAGs from the TechNet here.

Connecting to Office 365/Exchange


Last update: Version 2.29, June 3rd, 2020

Almost 3 years ago, I wrote an article on how to enhance the PowerShell Integrated Scripting Environment, or ISE. That seemed adequate for the Exchange admin back then, who would mostly connect PowerShell sessions to their on-premises environment, and occasionally a bit of Exchange Online.

Fast forward to 2015, most modern Exchange administrators not only require a connection – if any – to their Exchange on-premises environment, but likely to one or more of the Office 365 services as well, including Exchange On-Premises, Azure Active Directory, Exchange Online Protection, Microsoft Teams, Skype for Business Online, SharePoint Online, Azure Information Protection or Compliance Center.

All these services use a different PowerShell session, use a different endpoint FQDN, and in some cases require a locally installed PowerShell module. Likely common denominator is the credential used to access each of these services. So, tired of re-entering my credentials every time when switching from Exchange Online to Exchange Online Protection, I created a script with a set of functions to allow me connect to each individual Office 365 service or Exchange Online:

  • Connect-AzureActiveDirectory: Connects to Azure Active Directory
  • Connect-AIP: Connects to Azure Information Protection
  • Connect-ExchangeOnline: Connects to Exchange Online
  • Connect-ExchangeOnlinev2: Connects to Exchange Online using REST module
  • Connect-SkypeOnline: Connects to Skype for Business Online
  • Connect-EOP: Connects to Exchange Online Protection
  • Connect-ComplianceCenter: Connects to Compliance Center
  • Connect-SharePointOnline: Connects to SharePoint Online
  • Connect-MSTeams: Connects to Microsoft Teams
  • Get-Office365Credentials: Gets Office 365 credentials
  • Connect-ExchangeOnPremises: Connects to Exchange On-Premises
  • Get-OnPremisesCredentials: Gets On-Premises credentials
  • Get-ExchangeOnPremisesFQDN: Gets FQDN for Exchange On-Premises
  • Get-Office365Tenant: Gets Office 365 tenant name (SharePoint)
  • Set-Office365Environment: Configures Uri’s and region to use
  • Get-TenantID: Returns TenantID using previously used credentials
  • Update-Office365Modules: Updates supported Office 365 modules
  • Report-Office365Modules: Report on known vs online module versions

Note that functions and credentials used in the script are global, and in principle only need to be entered once per shell or ISE session. If you need different credentials, call Get-Office365Credentials again. User interaction is a very basic (Read-Host), but it does the job. The script will also detect if  any PowerShell module supporting Multi-Factor Authentication is installed. If so, you will be prompted if for using MFA when authenticating to workloads such as Exchange Online, Azure Active Directory, Microsoft Teams, Skype for Business Online or SharePoint Online.

During initialization, the script will detect the modules which are required for certain Office 365 services. When not installed, it will notify you, and provide a link where to obtain the PowerShell module. The related Connect function will not be made available. PowerShell is required to run this script, which is tested against version 5.1 (but should work with lower versions down to version 3).

The functions are contained in a script called Connect-Office365Services.ps1. You can call this script manually from your PowerShell session to make the functions available. However, more convenient may be to have them always available in every PowerShell or ISE session. To achieve this, you need to edit your $profile, which is a script which always starts when you start a PowerShell or ISE session. By default this file does not exist and you need to create it, including the path. Also note that the files for PowerShell and ISE are different, Microsoft.PowerShell_profile.ps1
and Microsoft.PowerShellISE_profile.ps1 respectively.

Now, of course you can copy and paste the functions from the script file to your own $profile. Better is to call the script from your $profile, as this allows you to overwrite the Connect-Office365Services.ps1 with updates. To achieve this, assume you copied the Connect-Office365Services.ps1 in the same location as your $profile, for example C:\Users\Michel\Documents\WindowsPowerShell. You can then make PowerShell and ISE call this script by adding the following line to the $profile scripts:

& “$PSScriptRoot\Connect-Office365Services.ps1”

Now when you start a PowerShell session, you might see the following:


This shows the default environment is targeted (AzureCloud), the Exchange Modern Authentication PowerShell module as well as other modules mentioned in the example are installed. When online version checking is enabled (OnlineModuleVersionChecks variable), a check will be performed against the online repository, e.g. PSGallery, and outdated modules will be reported, like the Skype for Business Online module in the example. It’s also possible to automatically update modules setting the variable OnlineModuleAutoUpdate.

When you load the script from ISE, it will show something similar. However, it will also detect ISE and make connect functions available through the Add-On menu:


Customize this script to your liking. For example, by default the script will not perform version checking of installed modules as it slows down loading. In this case, module versions are checked against a built-in ‘last known version’ table. If you want to perform online version checking, look up the line $local:OnlineModuleVersionChecks = $false in the script, and change $false to $true. Automatic updates can be toggled setting local:OnlineModuleAutoUpdate to $true. Note that for updating modules, you need to have administrator permissions on the local system.

You can download the script from GitHub.

Revision History
Revision information is embedded in the source.

Feedback is welcomed through the comments. If you got scripting suggestions, questions or want to report bugs, you can do this through GitHub or by using the contact form.

Multi-Factor Authentication in Office 365 (Part 2)

wp_ss_20140521_0001Multifactor Authentication is a must-have for services based in the cloud, especially for accounts with administrative purposes. We have already covered what Office 365 Multifactor Authentication is and how to configure it in Office 365 tenants with the Office 365 admin center, and we briefly showed the end user experience. Now we will look at how we can use the Azure Active Directory Module for Windows PowerShell to configure Office 365 authentication with MFA.

Azure Active Directory Module for Windows PowerShell (AADMPS) enables organizations to not only configure MFA for existing end users who use PowerShell, but also enhance their current provisioning process with MFA options. By pre-configuring MFA, administrators can prevent end users from having to go through the initial MFA setup process and use their currently configured mobile phone or office number for verification.

Read the full article over on SearchExchange

Multi-Factor Authentication in Office 365 (Part 1)

Multi-Factor AuthenticationMulti-Factor Authentication identifies an end user with more than one factor. Authentication is based on something you know, such as your password; something you have, such as a security token or smart card; or something that’s a physical characteristic of who you are, such as biometrics. By creating an additional factor on top of the password, identity is better protected. Multi-Factor Authentication is seen as a must-have for cloud-based services, especially for administrative types of accounts.

In this first tip on SearchExchange, I explain how you can configure Multi-Factor Authentication in Office 365, discuss the so-called contact methods, explain app passwords for non-MFA applications as well as show the MFA end user experience.

Read the full article over on SearchExchange

Script Updates

powershellA small heads-up for those not following me on Twitter of one of the other social media channels. Last week I made updates to the following three scripts:

Install-Exchange2013.ps1, version 1.72

  • Added CU5 support
  • Added KB2971467 (CU5 Disable Shared Cache Service Managed Availability probes)

Remove-DuplicateItems.ps1, version 1.3

  • Changed parameter Mailbox, you can now use an e-mail address as well.
  • Added parameter Credentials.
  • Added item class and size for certain duplication checks.
  • Changed item removal process
  • Remove items after, not while processing folder. Avoids asynchronous deletion issues.
  • Works against Office 365.

Remove-MessageClassItems.ps1, version 1.3

  • Changed parameter Mailbox, you can now use an e-mail address as well
  • Added parameter Credentials
  • Added parameter PartialMatching for partial class name matching.
  • Changed item removal process. Remove items after, not while processing folder. Avoids asynchronous deletion issues.
  • Works against Office 365.
  • Deleted Items folder will be processed, unless MoveToDeletedItems is used.
  • Changed EWS DLL loading, can now be in current folder as well.

Be advised I keep am overview of the scripts and their current versions with publish dates here.



powershellLast update: Version 1.02, November 21st, 2014.

Those leveraging quota settings to manage their Exchange environments, you are probably periodically running some sort of script or set of cmdlets to retrieve information on mailbox sizes, quota settings and if any mailbox is above any of the quota thresholds. For a quick indication of the current size in relation to the quota settings, StorageLimitStatus may contain one of the following indicators depending on the quota settings on the mailbox or mailbox database hosting the mailbox:

  • BelowLimit – Speaks for itself
  • IssueWarning – Mailbox size above Issue Warning limit
  • ProhibitSend – Mailbox size above Prohibit Send limit
  • NoChecking – No quota checking
  • MailboxDisabled – Mailbox size above Prohibit Send and Receive quota limit

So, to get a list of all mailboxes with any over-quota status, you can use:

Get-MailboxDatabase | Get-MailboxStatistics | Where {$_.StorageLimitStatus -match 'IssueWarning|ProhibitSend|MailboxDisabled'} | Select DisplayName, ItemCount, TotalItemSize, StorageLimitStatus, LastLogonTime

Unfortunately, in Exchange 2013 the StorageLimitStatus gets no longer populated:


As KB2819389 explains, this is by design. In Exchange 2013, mailbox quotas are no longer cached. By not being cached, retrieving quota information may result in poor performance as it queries Active Directory for quota related attributes. The argument is a bit puzzling, considering there is a NoADLookup switch which directs the cmdlet to retrieve information from the mailbox database (cache) instead of Active Directory. Perhaps a better workaround would have been to make NoADLookup a parameter, make it $true by default and leave StorageLimitStatus unpopulated when NoADLookup is $true.

Of course, that does not help customers who want a quick quota report. For this purpose I have created two things in 1 script:

  1. A helper function Get-StorageLimitStatus() which will take a mailbox statistics object and return a StorageLimitStatus object.
  2. A script Get-MyMailboxStatistics.ps1, a proxy function for Get-MailboxStatistics which will use the Get-StorageLimitStatus helper function to populate the StorageLimitStatus.

When you want to use the helper function, extract it and include it in your quota reporting script or PowerShell profile (making it available when firing up a shell). To use the helper function in the cmdlet shown earlier, use:

Get-MailboxDatabase | Get-MailboxStatistics | Select -ExcludeProperty StorageLimitStatus DisplayName, ItemCount, TotalItemSize, @{n="StorageLimitStatus"; e={ Get-StorageLimitStatus $_}}, LastLogonTime | Where {$_.StorageLimitStatus -match 'IssueWarning|ProhibitSend|MailboxDisabled'}

This will remove StorageLimitStatus from the output and add a calculated field bearing the same the name, calling the Get-StorageLimitStatus helper function with the current mailbox statistics object to set its value.

This is a proxy function for the Exchange Management Shell cmdlet Get-MailboxStatistics. This means that the current, original cmdlet was used to create a wrapper which will call the original cmdlet. Having a wrapper allows you to restrict or enhance the original cmdlet and tailor it to your needs.

A quick tip on how to create a proxy script in the clipboard (more information on creating proxy commands here):

$data= New-Object System.Management.Automation.CommandMetaData (Get-Command Get-MailboxStatistics) 
[System.Management.Automation.ProxyCommand]::create($data) | clip.exe

Downside is that future changes to the Get-MailboxStatistics cmdlet will not be automatically incorporated in the wrapper. Feeding it objects also doesn’t work, but you can work around that by temporary storing the objects in a variable and passing that to the script (see examples below).

To populate the StorageLimitStatus, we will post-process each object in the output of Get-MailboxStatistics, using Add-Member to overwrite (-Force) its current value and –PassThru to pass it along in the pipeline. Being a proxy command, the parameter options are identical to the original Get-MailboxStatistics. Some examples:

.\Get-MailboxStatistics.ps1 -Database MDB2
$m= Get-Mailbox –Database MDB2 
$m | .\Get-MailboxStatistics.ps1 | ft –AutoSize DisplayName,TotalItemSize,StorageLimitStatus


Do be aware that this will incur Active Directory queries and thus performance of the script may not seem fast. However, in previous versions of Exchange you got immediate results as all the quota information was readily available from the cache. On the plus side, the status you see will be non-cached, current information.

On a final note and maybe needless to say that in order to use this you need to run it from the Exchange Management Shell and since it’s an unsigned script you need to set ExecutionPolicy to Unrestricted.

Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

You can download the script from the TechNet Gallery here.

Revision History
See Technet Gallery page.

Why Exchange Admins should learn PowerShell

techtarget_logoAs some of you may or may not be aware of, recently I have joined the ranks of Exchange and The UC Architects fellows Michael van Horenbeeck and Steve Goodman and started writing for the Exchange section at TechTarget.

The first two articles are a call to action for Exchange admins to start working on their PowerShell skills if they have not already done so. Learning can take the more academic approach, starting from scratch and learning the basics topic by topic, or a more practical one by reading and getting to understand existing scripts which may suit the more time-constrained admin.

Read the full articles here and the follow-up article here.

If you got topic suggestions, use the contact form or send me an e-mail.