The PowerShell Mess in the Microsoft Cloud

Office 365 with Teams

Thirteen years after Exchange 2007 was the first enterprise Microsoft server to use PowerShell for administration and eight years after the launch of Office 365, PowerShell in the cloud is an incoherent mess. Recent innovations like the Azure Cloud Shell (now supporting Exchange Online) or the DIY Cloud Shell for SharePoint Online don’t help either. Instead, they just add to the confusion.

So Many Modules

Today, an Office 365 administrator who wants to automate different aspects using PowerShell might have to load the following modules:

  • Exchange Online (includes Office 365 Groups).
  • Security and Compliance Center.
  • SharePoint Online.
  • Teams
  • Skype for Business Online (includes Teams policies).
  • Azure Active Directory (V1 and V2).
  • Azure Rights Management.

These modules are updated on an arbitrary basis by different engineering groups. Throw in the need to support multi-factor authentication for some but not all modules and there’s enough complexity involved for MVP to literally lose what little hair he has left in his efforts to maintain his well-respected script to connect to the modules you need in a PowerShell session.

Why Does This Mess Exist?

Three basic reasons drive the collection of PowerShell modules within Office 365. Heritage, or how the module came to be available. Technology, or how the module is built, and Ownership, the development group responsible for the maintenance of a module.


Some modules come from on-premises servers (Exchange, SharePoint, and Skype for Business Online). They’re not identical as many cmdlets are removed because they’re not needed in the cloud. Indeed, many cmdlets support different parameter sets in their cloud and on-premises variants, which creates another challenge for those writing against both platforms.

Other modules are cloud-only (Teams, Azure Active Directory, Security and Compliance Center), but might have some connection elsewhere. The cmdlets in the Azure Active Directory module obviously look somewhat like those used to interact with on-premises Active Directory, but they’re very different. Some of the cmdlets in the Security and Compliance Center module have the same name as Exchange Online cmdlets but return different information because they’re designed to be used differently.


Newer modules tend to have a closer relationship to the Microsoft Graph than older modules do. For example, the Teams module is built on top of the Graph, as are some of the cmdlets in the Azure Active Directory module. The Graph gives a common interface to many kinds of data in the Microsoft cloud, but the transition to using a Graph base for all modules will take time and care. Performance must be maintained (the Teams module is very slow at times) and Graph-based cmdlets must behave exactly like their earlier counterparts.

While Microsoft gears up to upgrade PowerShell modules to use the Graph, Office 365 tenants will continue to cope with a mixture of Graph and non-Graph technology. It would be good to have a common base because then you’d could write “real” code using the same Graph functions as used by PowerShell. A single unified interface to data and functionality across Office 365 – wouldn’t that be nice?


The last issue is purely due to Microsoft organization and politics. There is no PowerShell czar inside Office 365 and no one development group responsible for coordination of PowerShell across all development groups. Indeed, as one experienced Microsoft insider observed to me, “It can take up to 11 Vice Presidents to agree to something before something can happen inside Office 365.”

The result is that development groups do their own thing with PowerShell and ignore what’s happening elsewhere. Nothing else could explain the unique way that the Skype for Business Online module goes about its business, including its perfectly annoying habit of failing to maintain a connection to Office 365.

Error Handling is Just One Example of Inconsistency

Another bugbear is the inconsistency exhibited in cmdlet error handling. You’d imagine that a software development company would like to see a standard way of handling errors implemented ac across its software, but that doesn’t happen inside the Office 365 PowerShell modules.

For example, let’s look at three ways to return an Office 365 Group. In all cases, we look for a group that doesn’t exist. First, we use the Get-UnifiedGroup cmdlet in the Exchange Online module:

Get-UnifiedGroup -Identity "Not Exists" -ErrorAction SilentlyContinue

As expected, nothing is returned, but we can look in the $Error special variable to find out what happened:


In other words, Get-UnifiedGroup is a well-behaved cmdlet, much like the majority in the Exchange Online module.

Now let’s look at what happens with the Get-Team cmdlet. In this case, I use the Get-UnifiedGroup cmdlet to return the GUID of our non-existent group:

Get-Team -GroupId (Get-UnifiedGroup -Identity "Not Found").ExternalDirectoryObjectId -ErrorAction SilentlyContinue

The first problem is that the ErrorAction parameter doesn’t work and we get a pile of error text to deal with. If we examine $Error[0].CategoryInfo.Reason, it returns a value of ParameterBindingValidationException, which is OK to program a response to an error when getting a team.

Now let’s look at what happens when we run the Get-AzureADGroup cmdlet to fetch information using the Azure Active Directory module.

Get-AzureADGroup -ObjectId (Get-UnifiedGroup -Identity "Not Found").ExternalDirectoryObjectId -ErrorAction SilentlyContinue

Once again, the direction passed in the ErrorAction parameter is ignored. And like the Teams module, we get ParameterBindingValidationException returned in $Error[0].CategoryInfo.Reason.

The Azure Active Directory and Teams modules are consistent in returning the same error code for a group that doesn’t exist and equally (and badly) consistent in ignoring the ErrorAction parameter.

The upshot of the inconsistencies (and this is just a simple example) makes it more difficult for programmers to code and test against Office 365. Experience is great at finding where problems lie, but it would be better if cmdlets worked the same way across all modules.

What Needs to Happen

Apart from making cmdlets behave in a consistent manner, it would be good if Office 365 programmers had fewer modules to deal with. The Teams and the Skype for Business Online modules could be combined; the need for the V1 Active Directory module eliminated; and Exchange could look at why it has different endpoints for “normal” cmdlets and those used for the Security and Compliance Center. Oh, and it would be nice if all Office 365 apps supported PowerShell too (Planner is a prime example as is Yammer).

At the same time, any effort to consolidate and rationalize should move cmdlets to be based on the Microsoft Graph whenever possible. That is, if performance, reliability, and stability can be achieved when the transition happens. I can but dream.