After extensively covering the scenario of generating a report of group membership in Microsoft 365, let’s quickly talk about a similar topic – generating a report of all Group Owners. In other words, generate a list of groups and for each group, list any owners. This is basically a refresh of a five year old article first posted here, and the corresponding script.
To start with, let’s cover what’s changed in the past few years. As with the most of our recent script samples, we’re moving away from the old Azure AD PowerShell module and the use of Exchange Online’s Remote PowerShell cmdlets, and replacing them with the Graph SDK for PowerShell and the ExO V3 module cmdlets, respectively. Once we have the relevant cmdlets, the rest of the script is quite straightforward. Nevertheless, few surprises popped out while upgrading the script, such as the fact that Exchange Online Dynamic Distribution Groups apparently have an ExternalDirectoryObjectId values now (you still cannot query those with Graph though). Another surprising behavior was that the rules governing Microsoft 365 Groups’ membership and ownership seem to not be set in stone, but we will cover this in a separate article.
Now, for the script itself, it’s actually quite simple. First, we check for the presence of the relevant PowerShell modules, via a couple of #Requires statements. Next, the script will try to detect and establish a connection to the Graph, as needed. The permissions required are Directory.Read.All, and if the Graph API context object doesn’t not list them, you will be prompted to re-consent. Afterwards, we issue a simple Get-MgGroup query to fetch all the groups, their relevant properties and leverage the ability of the Graph API to also include/expand information about any owners in the output, all packed up in a single line of code. The output is then sorted by the DisplayName property and spilled out into the PowerShell console. If you want to export it to CSV file instead, remove the comment mark from line 64.
The script features a single parameter, –IncludeExchangeManagedBy, which you can leverage in order to include Exchange Online specific objects in the output. More importantly, the data fetched from Exchange side will feature the ManagedBy property, which continues to represent a separate set of objects compared to the Owners property in Azure AD. Thus, if you want to get the full picture, it’s best to query data from both ExODS and Azure AD.
If you run the script with the -IncludeExchangeManagedBy parameter, it will try to detect an existing Exchange Online connection and if not, create a new one. The script will only leverage a single cmdlet, Get-ExORecipient, thus if a new session is created, we try to minimize the footprint by only loading it (actually, the REST cmdlets are always loaded, but we still need to specify at least one non-REST one, which is why in this case we use Get-Recipient). After that, a single query is issued to fetch the set of Exchange Online groups (“traditional” Distribution groups, Mail-enabled security groups, Dynamic distribution groups and Room lists), along with their properties. The output is then enriched with the Owners value from Azure AD, so you have a direct comparison between it and the ManagedBy property, as reported from ExODS. As already noted above, do not expect these values to match!
As with the Azure AD data, output is then spilled out to the console. If you would like to export it to CSV file directly, remove the comment mark from line 69. For both the Azure AD and ExODS scenarios, output will also be stored in a global variable, $varOwners and $varOwnersExchange, respectively. This allows you to play with the output before deciding what data to export, and is a useful comparison tool. The examples below showcase the differences between the data we can obtain from both sources, and serve as evidence as to why you might want to tinker with the output, before exporting it.
On the first screenshot, we can see the data for the same object, as obtained from Azure AD and Exchange Online, respectively. In this case, a “traditional” distribution group is examined, and while it has a representation within Azure AD, we can see that only the data fetched from Exchange Online reveals the “owner” details (via the ManagedBy property, that is). Similarly, the second screenshot highlights a scenario where the data from Azure AD is in disagreement with the data from ExODS, the difference being that this time the Owners value in Azure AD is non-empty (but still not-matching what ExoDS returns). In other words, you need both sets of data for the full picture.
And that’s pretty much all there is to this script. Go grab your copy from GitHub, and run it: