Get-MgGroup – Office 365 for IT Pros https://office365itpros.com Mastering Office 365 and Microsoft 365 Fri, 31 May 2024 10:20:59 +0000 en-US hourly 1 https://i0.wp.com/office365itpros.com/wp-content/uploads/2024/06/cropped-Office-365-for-IT-Pros-2025-Edition-500-px.jpg?fit=32%2C32&ssl=1 Get-MgGroup – Office 365 for IT Pros https://office365itpros.com 32 32 150103932 Mastering the Foibles of the Microsoft Graph PowerShell SDK https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/?utm_source=rss&utm_medium=rss&utm_campaign=microsoft-graph-powershell-sdk-prob https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/#comments Mon, 13 Feb 2023 01:00:00 +0000 https://office365itpros.com/?p=59070
He looks happy, but he hasn't hit some of the Microsoft Graph PowerShell SDK foibles yet...
He looks happy, but he hasn’t hit some of the SDK foibles yet…

Translating Graph API Requests to PowerShell Cmdlets Sometimes Doesn’t Go So Well

The longer you work with a technology, the more you come to know about its strengths and weaknesses. I’ve been working with the Microsoft Graph PowerShell SDK for about two years now. I like the way that the SDK makes Graph APIs more accessible to people accustomed to developing PowerShell scripts, but I hate some of the SDK’s foibles.

This article describes the Microsoft Graph PowerShell SDK idiosyncrasies that cause me most heartburn. All are things to look out for when converting scripts from the Azure AD and MSOL modules before their deprecation (speaking of which, here’s an interesting tool that might help with this work).

No Respect for $Null

Sometimes you just don’t want to write something into a property and that’s what PowerShell’s $Null variable is for. But the Microsoft Graph PowerShell SDK cmdlets don’t like it when you use $Null. For example, let’s assume you want to create a new Azure AD user account. This code creates a hash table with the properties of the new account and then runs the New-MgUser cmdlet.

$NewUserProperties = @{
    GivenName = $FirstName
    Surname = $LastName
    DisplayName = $DisplayName
    JobTitle = $JobTitle
    Department = $Null
    MailNickname = $NickName
    Mail = $PrimarySmtpAddress
    UserPrincipalName = $UPN
    Country = $Country
    PasswordProfile = $NewPasswordProfile
    AccountEnabled = $true }
$NewGuestAccount = New-MgUser @NewUserProperties

New-MgUser fails because of an invalid value for the department property, even though $Null is a valid PowerShell value.

New-MgUser : Invalid value specified for property 'department' of resource 'User'.
At line:1 char:2
+  $NewGuestAccount = New-MgUser @NewUserProperties
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: ({ body = Micros...oftGraphUser1 }:<>f__AnonymousType64`1) [New-MgUser
   _CreateExpanded], RestException`1
    + FullyQualifiedErrorId : Request_BadRequest,Microsoft.Graph.PowerShell.Cmdlets.NewMgUser_CreateExpanded

One solution is to use a variable that holds a single space. Another is to pass $Null by running the equivalent Graph request using the Invoke-MgGraphRequest cmdlet. Neither are good answers to what should not happen (and we haven’t even mentioned the inability to filter on null values).

Ignoring the Pipeline

The pipeline is a fundamental building block of PowerShell. It allows objects retrieve by a cmdlet to pass to another cmdlet for processing. But despite the usefulness of the pipeline, the SDK cmdlets don’t support it and the pipeline stops stone dead whenever an SDK cmdlet is asked to process incoming objects. For example:

Get-MgUser -Filter "userType eq 'Guest'" -All | Update-MgUser -Department "Guest Accounts"
Update-MgUser : The pipeline has been stopped

Why does this happen? The cmdlet that receives objects must be able to distinguish between the different objects before it can work on them. In this instance, Get-MgUser delivers a set of guest accounts, but the Update-MgUser cmdlet does not know how to process each object because it identifies an object is through the UserId parameter whereas the inbound objects offer an identity in the Id property.

The workaround is to store the set of objects in an array and then process the objects with a ForEach loop.

Property Casing and Fetching Data

I’ve used DisplayName to refer to the display name of objects since I started to use PowerShell with Exchange Server 2007. I never had a problem with uppercasing the D and N in the property name until the Microsoft Graph PowerShell SDK came along only to find that sometimes SDK cmdlets insist on a specific form of casing for property names. Fail to comply, and you don’t get your data.

What’s irritating is that the restriction is inconsistent. For instance, both these commands work:

Get-MgGroup -Filter "DisplayName eq 'Ultra Fans'"
Get-MgGroup -Filter "displayName eq 'Ultra Fans'"

But let’s say that I want to find the group members with the Get-MgGroupMember cmdlet:

[array]$GroupMembers = Get-MgGroupMember -GroupId (Get-MgGroup -Filter "DisplayName eq 'Ultra Fans'" | Select-Object -ExpandProperty Id)

This works, but I end up with a set of identifiers pointing to individual group members. Then I remember from experience gained from building scripts to report group membership that Get-MgGroupMember (like other cmdlets dealing with membership like Get-MgAdministrationUnitMember) returns a property called AdditionalProperties holding extra information about members. So I try:

$GroupMembers.AdditionalProperties.DisplayName

Nope! But if I change the formatting to displayName, I get the member names:

$GroupMembers.AdditionalProperties.displayName
Tony Redmond
Kim Akers
James Ryan
Ben James
John C. Adams
Chris Bishop

Talk about frustrating confusion! It’s not just display names. Reference to any property in AdditionalProperties must use the same casing as used the output, like userPrincipalName and assignedLicenses.

Another example is when looking for sign-in logs. This command works because the format of the user principal name is the same way as stored in the sign-in log data:

[array]$Logs = Get-MgAuditLogSignIn -Filter "UserPrincipalName eq 'james.ryan@office365itpros.com'" -All

Uppercasing part of the user principal name causes the command to return zero hits:

[array]$Logs = Get-MgAuditLogSignIn -Filter "UserPrincipalName eq 'James.Ryan@office365itpros.com'" -All

Two SDK foibles are on show here. First, the way that cmdlets return sets of identifiers and stuff information into AdditionalProperties (something often overlooked by developers who don’t expect this to be the case). Second, the inconsistent insistence by cmdlets on exact matching for property casing.

I’m told that this is all due to the way Graph APIs work. My response is that it’s not beyond the ability of software engineering to hide complexities from end users by ironing out these kinds of issues.

GUIDs and User Principal Names

Object identification for Graph requests depends on globally unique identifiers (GUIDs). Everything has a GUID. Both Graph requests and SDK cmdlets use GUIDs to find information. But some SDK cmdlets can pass user principal names instead of GUIDs when looking for user accounts. For instance, this works:

Get-MgUser -UserId Tony.Redmond@office365itpros.com

Unless you want to include the latest sign-in activity date for the account.

Get-MgUser -UserId Tony.Redmond@office365itpros.com -Property signInActivity
Get-MgUser :
{"@odata.context":"http://reportingservice.activedirectory.windowsazure.com/$metadata#Edm.String","value":"Get By Key
only supports UserId and the key has to be a valid Guid"}

The reason is that the sign-in data comes from a different source which requires a GUID to lookup the sign-in activity for the account, so we must pass the object identifier for the account for the command to work:

Get-MgUser -UserId "eff4cd58-1bb8-4899-94de-795f656b4a18" -Property signInActivity

It’s safer to use GUIDs everywhere. Don’t depend on user principal names because a cmdlet might object – and user principal names can change.

No Fix for Problems in V2 of the Microsoft Graph PowerShell SDK

V2.0 of the Microsoft Graph PowerShell SDK is now in preview. The good news is that V2.0 delivers some nice advances. The bad news is that it does nothing to cure the weaknesses outlined here. I’ve expressed a strong opinion that Microsoft should fix the fundamental problems in the SDK before doing anything else.

I’m told that the root cause of many of the issues is the AutoRest process Microsoft uses to generate the Microsoft Graph PowerShell SDK cmdlets from Graph API metadata. It looks like we’re stuck between a rock and a hard place. We benefit enormously by having the SDK cmdlets but the process that makes the cmdlets available introduces its own issues. Let’s hope that Microsoft gets to fix (or replace) AutoRest and deliver an SDK that’s better aligned with PowerShell standards before our remaining hair falls out due to the frustration of dealing with unpredictable cmdlet behavior.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/feed/ 10 59070
Fetching Group Membership Information for an Entra ID User Account https://office365itpros.com/2023/01/30/group-membership-with-the-graph/?utm_source=rss&utm_medium=rss&utm_campaign=group-membership-with-the-graph https://office365itpros.com/2023/01/30/group-membership-with-the-graph/#comments Mon, 30 Jan 2023 01:00:00 +0000 https://office365itpros.com/?p=58839

Discover Group Membership with the Graph SDK

Group membership with the Graph SDK

I’ve updated some scripts recently to remove dependencies on the Azure AD and Microsoft Online Services (MSOL) modules, which are due for deprecation on June 30, 2023 (retirement happens at the end of March for the license management cmdlets). In most cases, the natural replacement is cmdlets from the Microsoft Graph PowerShell SDK.

One example is when retrieving the groups a user account belongs to. This is an easy task when dealing with the membership of individual groups using cmdlets like:

  • Get-DistributionGroupMember (fetch distribution list members).
  • Get-DynamicDistributionGroupMember (fetch dynamic distribution group members).
  • Get-UnifiedGroupLinks (fetch members of a Microsoft 365 group).
  • Get-MgGroupMember (fetch members of an Entra ID group).

Things are a little more complex when answering a question like “find all the groups that Sean Landy belongs to.” Let’s see how we can answer the request.

The Exchange Online Approach

One method of attacking the problem often found in Exchange scripts is to use the Get-Recipient cmdlet with a filter based on the distinguished name of the mailbox belonging to an account: For example, this code reports a user’s membership of Microsoft 365 groups:

$User = Get-EXOMailbox -Identity Sean.Landy
$DN = $User.DistinguishedName
$Groups = (Get-Recipient -ResultSize Unlimited -RecipientTypeDetails GroupMailbox -Filter "Members -eq '$DN'" )
Write-Host (“User is a member of {0} groups” -f $Groups.count)

The method works if the distinguished name doesn’t include special characters like apostrophes for users with names like Linda O’Shea. In these cases, extra escaping is required to make PowerShell handle the name correctly. This problem will reduce when Microsoft switches the naming mechanism for Exchange Online objects to be based on the object identifier instead of mailbox display name. However, there’s still many objects out there with distinguished names based on display names.

The Graph API Request

As I go through scripts, I check if I can remove cmdlets from other modules to make future maintenance easier. Using Get-Recipient means that a script must connect to the Exchange Online management module, so let’s remove that need by using a Graph API request. Here’s what we can do, using the Invoke-MgGraphRequest cmdlet to run the request:

$UserId = $User.ExternalDirectoryObjectId
$Uri = ("https://graph.microsoft.com/V1.0/users/{0}/memberOf/microsoft.graph.group?`$filter=groupTypes/any(a:a eq 'unified')&`$top=200&$`orderby=displayName&`$count=true" -f $UserId)
[array]$Data = Invoke-MgGraphRequest -Uri $Uri
[array]$Groups = $Data.Value
Write-Host (“User is a member of {0} groups” -f $Groups.count) 

We get the same result (always good) and the Graph request runs about twice as fast as Get-Recipient does.

Because the call is limited to Microsoft 365 groups, I don’t have to worry about transitive membership. If I did, then I’d use the group transitive memberOf API.

Using the SDK Get-MgUserMemberOf Cmdlet

The Microsoft Graph PowerShell SDK contains cmdlets based on Graph requests. The equivalent cmdlet is Get-MgUserMemberOf. This returns memberships of all group types known to Entra ID, so it includes distribution lists and security groups. To return the set of Microsoft 365 groups, apply a filter after retrieving the group information from the Graph.

[array]$Groups = Get-MgUserMemberOf -UserId $UserId -All | Where-Object {$_.AdditionalProperties["groupTypes"] -eq "Unified"}
Write-Host (“User is a member of {0} groups” -f $Groups.count) 

Notice that the filter looks for a specific type of group in a value in the AdditionalProperties property of each group. If you run Get-MgUserMemberOf without any other processing. the cmdlet appears to return a simple list of group identifiers. For example:

$Groups

Id                                   DeletedDateTime
--                                   ---------------
b62b4985-bcc3-42a6-98b6-8205279a0383
64d314bb-ea0c-46de-9044-ae8a61612a6a
87b6079d-ddd4-496f-bff6-28c8d02e9f8e
82ae842d-61a6-4776-b60d-e131e2d5749c

However, the AdditionalProperties property is also available for each group. This property contains a hash table holding other group properties that can be interrogated. For instance, here’s how to find out whether the group supports private or public access:

$Groups[0].AdditionalProperties['visibility']
Private

When looking up a property in the hash table, remember to use the exact form of the key. For instance, this works to find the display name of a group:

$Groups[0].AdditionalProperties['displayName']

But this doesn’t because the uppercase D creates a value not found in the hash table:

$Groups[0].AdditionalProperties['DisplayName']

People starting with the Microsoft Graph PowerShell SDK are often confused when they see just the group identifiers apparently returned by cmdlets like Get-MgUserMemberOf, Get-MgGroup, and Get-MgGroupMember because they don’t see or grasp the importance of the AdditionalProperties property. It literally contains the additional properties for the group excepting the group identifier.

Here’s another example of using information from AdditionalProperties. The details provided for a group don’t include its owners. To fetch the owner information for a group, run the Get-MgGroupOwner cmdlet like this:

$Group = $Groups[15]
[array]$Owners = Get-MgGroupOwner -GroupId $Group.Id | Select-Object -ExpandProperty AdditionalProperties
$OwnersOutput = $Owners.displayName -join ", "
Write-Host (“The owners of the {0} group are {1}” -f $Group.AdditionalProperties[‘displayName’], $OwnersOutput)

If necessary, use the Get-MgGroupTransitiveMember cmdlet to fetch transitive memberships of groups.

The Graph SDK Should be More Intelligent

It would be nice if the Microsoft Graph PowerShell SDK didn’t hide so much valuable information in AdditionalProperties and wasn’t quite so picky about the exact format of property names. Apparently, the SDK cmdlets behave in this manner because it’s how Graph API requests work when they return sets of objects. That assertion might well be true, but it would be nice if the SDK applied some extra intelligence in the way it handles data.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2023/01/30/group-membership-with-the-graph/feed/ 4 58839
Reporting Group Membership for Azure AD Guest Accounts with the Microsoft Graph PowerShell SDK https://office365itpros.com/2023/01/18/old-azure-ad-guest-accounts/?utm_source=rss&utm_medium=rss&utm_campaign=old-azure-ad-guest-accounts https://office365itpros.com/2023/01/18/old-azure-ad-guest-accounts/#comments Wed, 18 Jan 2023 01:00:00 +0000 https://office365itpros.com/?p=58742

Finding Azure AD Guest Accounts in Microsoft 365 Groups

The article explaining how to report old guest accounts and their membership of Microsoft 365 Groups (and teams) in a tenant is very popular and many people use its accompanying script. The idea is to find guest accounts above a certain age (365 days – configurable in the script) and report the groups these guests are members of. Any old guest accounts that aren’t in any groups are candidates for removal.

The script uses an old technique featuring the distinguished name of guest accounts to scan for group memberships using the Get-Recipient cmdlet. The approach works, but the variation of values that can exist in distinguished names due to the inclusion of characters like apostrophes and vertical lines means that some special processing is needed to make sure that lookups work. Achieving consistency in distinguished names might be one of the reasons for Microsoft’s plan to make Exchange Online mailbox identification more effective.

In any case, time moves on and code degrades. I wanted to investigate how to use the Microsoft Graph PowerShell SDK to replace Get-Recipient. The script already uses the SDK to find Azure AD guest accounts with the Get-MgUser cmdlet.

The Graph Foundation

Graph APIs provide the foundation for all SDK cmdlets. Graph APIs provide the foundation for all SDK cmdlets. The first thing to find is an appropriate API to find group membership. I started off with getMemberGroups. The PowerShell example for the API suggests that the Get-MgDirectoryObjectMemberGroup cmdlet is the one to use. For example:

$UserId = (Get-MgUser -UserId Terry.Hegarty@Office365itpros.com).id 
[array]$Groups = Get-MgDirectoryObjectMemberGroup  -DirectoryObjectId $UserId -SecurityEnabledOnly:$False

The cmdlet works and returns a list of group identifiers that can be used to retrieve information about the groups that the user belongs to. For example:

Get-MgGroup -GroupId $Groups[0] | Format-Table DisplayName, Id, GroupTypes

DisplayName                     Id                                   GroupTypes
-----------                     --                                   ----------
All Tenant Member User Accounts 05ecf033-b39a-422c-8d30-0605965e29da {DynamicMembership, Unified}

However, because Get-MgDirectoryObjectMemberGroup returns a simple list of group identifiers, the developer must do extra work to call Get-MgGroup for each group to retrieve group properties. Not only is this extra work, calling Get-MgGroup repeatedly becomes very inefficient as the number of guests and their membership in groups increase.

Looking Behind the Scenes with Graph X-Ray

The Azure AD admin center (and the Entra admin center) both list the groups that user accounts (tenant and guests) belong to. Performance is snappy and it seemed unlikely that the code used was making multiple calls to retrieve the properties for each group. Many of the sections in these admin centers use Graph API requests to fetch information, and the Graph X-Ray tool reveals those requests. Looking at the output, it’s interesting to see that the admin center uses the beta Graph endpoint with the groups memberOf API (Figure 1).

Using the Graph X-Ray tool to find the Graph API for group membership

Azure AD Guest Accounts
Figure 1: Using the Graph X-Ray tool to find the Graph API for group membership

We can reuse the call used by the Azure AD center to create the query (containing the object identifier for the user account) and run the query using the SDK Invoke-MgGraphRequest cmdlet. One change made to the command is to include a filter to select only Microsoft 365 groups. If you omit the filter, the Graph returns all the groups a user belongs to, including security groups and distribution lists. The group information is in an array that’s in the Value property returned by the Graph request. For convenience, we put the data into a separate array.

$Uri = ("https://graph.microsoft.com/beta/users/{0}/memberOf/microsoft.graph.group?`$filter=groupTypes/any(a:a eq 'unified')&`$top=200&$`orderby=displayName&`$count=true" -f $Guest.Id)
[array]$Data = Invoke-MgGraphRequest -Uri $Uri
[array]$GuestGroups = $Data.Value

Using the Get-MgUserMemberOf Cmdlet

The equivalent SDK cmdlet is Get-MgUserMemberOf. To return the set of groups an account belongs to, the command is:

[array]$Data = Get-MgUserMemberOf -UserId $Guest.Id -All
[array]$GuestGroups = $Data.AdditionalProperties

The format of returned data marks a big difference between the SDK cmdlet and the Graph API request. The cmdlet returns group information in a hash table in the AdditionalProperties array while the Graph API request returns a simple array called Value. To retrieve group properties from the hash table, we must enumerate through its values. For instance, to return the names of the Microsoft 365 groups in the hash table, we do something like this:

[Array]$GroupNames = $Null
ForEach ($Item in $GuestGroups.GetEnumerator() ) {
   If ($Item.groupTypes -eq "unified") { $GroupNames+= $Item.displayName }
}
$GroupNames= $GroupNames -join ", "

SDK cmdlets can be inconsistent in how they return data. It’s just one of the charms of working with cmdlets that are automatically generated from code. Hopefully, Microsoft will do a better job of ironing out inconsistencies when they release V2.0 of the SDK sometime later in 2023.

A Get-MgUserTransitiveMemberOf cmdlet is also available to return the membership of nested groups. We don’t need to do this because we’re only interested in Microsoft 365 groups, which don’t support nesting. The cmdlet works in much the same way:

[array]$TransitiveData = Get-MgUserTransitiveMemberOf -UserId Kim.Akers@office365itpros.com -All

The Script Based on the SDK

Because of the extra complexity in accessing group properties, I decided to use a modified version of the Graph API request from the Azure AD admin center. It’s executed using the Invoke-MgGraphRequest cmdlet, so I think the decision is justified.

When revising the script, I made some other improvements, including adding a basic assessment of whether a guest account is stale or very stale. The assessment is intended to highlight if I should consider removing these accounts because they’re obviously not being used. Figure 2 shows the output of the report.

Report highlighting potentially obsolete guest accounts
Figure 2: Report highlighting potentially obsolete Azure AD guest accounts

You can download a copy of the script from GitHub.

Cleaning up Obsolete Azure AD Guest Accounts

Reporting obsolete Azure AD guest accounts is nice. Cleaning up old junk from Azure AD is even better. The script generates a PowerShell list with details of all guests over a certain age and the groups they belong to. To generate a list of the very stale guest accounts, filter the list:

[array]$DeleteAccounts = $Report | Where-Object {$_.StaleNess -eq "Very Stale"}

To complete the job and remove the obsolete guest accounts, a simple loop to call Remove-MgUser to process each account:

ForEach ($Account in $DeleteAccounts) {
   Write-Host ("Removing guest account for {0} with UPN {1}" -f $Account.Name, $Account.UPN) 
   Remove-MgUser -UserId $Account.Id }

Obsolete or stale guest accounts are not harmful, but their presence slows down processing like PowerShell scripts. For that reason, it’s a good idea to clean out unwanted guests periodically.


Learn about mastering the Microsoft Graph PowerShell SDK and the Microsoft 365 PowerShell modules by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s important and how best to protect your tenant.

]]>
https://office365itpros.com/2023/01/18/old-azure-ad-guest-accounts/feed/ 2 58742
Basic Entra ID Group Management with the Microsoft Graph PowerShell SDK https://office365itpros.com/2022/03/29/create-entra-id-group/?utm_source=rss&utm_medium=rss&utm_campaign=create-entra-id-group https://office365itpros.com/2022/03/29/create-entra-id-group/#comments Tue, 29 Mar 2022 01:00:00 +0000 https://office365itpros.com/?p=54278

Create Entra ID Groups with SDK Cmdlets

Updated 28 December 2023

Last week, I discussed how to perform basic Entra ID user account management operations using cmdlets from the Microsoft Graph PowerShell SDK. Now it’s time to discuss the management of group objects.

Use the Entra ID admin center to create Entra ID groups and manage them afterward.
Figure 1: Use the Entra ID admin center to create Entra ID groups and manage them afterward

To work with the cmdlets discussed here, you should connect to the Microsoft Graph with the Group.ReadWrite.All and GroupMember.ReadWrite.All permissions:

$RequiredScopes = @("Group.ReadWrite.All", "GroupMember.ReadWrite.All", "User.ReadWrite.All")
Connect-MgGraph -Scopes $RequiredScopes -NoWelcome

See this post for more information about connecting to the Graph and permissions.

Creating Different Kinds of Entra ID Groups

Entra ID groups include:

  • Microsoft 365 groups/Teams (including groups used by Yammer).
  • Security groups.
  • Dynamic groups (including those used by Microsoft 365 groups/Teams).
  • Distribution lists.
  • Mail-enabled security groups.

The New-MgGroup cmdlet can create Microsoft 365 groups, dynamic groups, and security groups. It can’t create distribution lists or mail-enabled security groups., nor can Graph API requests or Graph SDK cmdlets update the membership of distribution lists or mail-enabled security groups because these group types are essentially Exchange Online rather than Entra ID objects.

Although New-MgGroup can create groups of different types, it is often better to use the dedicated cmdlet for a particular type of group to ensure that Microsoft 365 performs all the necessary provisioning, like New-UnifiedGroup or New-Team.

Here’s an example of using New-MgGroup to create a Microsoft 365 group (the key point is to set the GroupTypes parameter to be “Unified”):

New-MgGroup -DisplayName "Sales Operations Team" -GroupTypes Unified -MailNickName Sales.Operations -MailEnabled:$True -SecurityEnabled:$False -Description "A group for Sales Operation management"

It’s a good idea to capture the result of the cmdlet in a variable. If the command is successful, you’ll then have a variable containing properties of the new group including its identifier. As we’ll see, you’ll need the identifier to interact with the group using other SDK cmdlets.

The downside of creating a Microsoft 365 group using the New-MgGroup cmdlet is that you will probably end up fixing up some of the group’s properties afterwards. For instance, New-MgGroup adds the signed-in account as the group owner, which you might not want. In addition, you can’t update properties like group privacy or assign a sensitivity label, so these must be set afterwards.

Creating a Dynamic Microsoft 365 Group

One scenario where New-MgGroup scores is where you want to create a dynamic Microsoft 365 Group, as this cannot be done using the New-UnifiedGroup cmdlet. This command creates a group using a membership rule to find people whose usage location (for Microsoft 365 services) is the U.S:

$Group = New-MgGroup -DisplayName "U.S. Based Employees" -Description "Dynamic group containing U.S. Based Employees" -MailEnabled:$True -SecurityEnabled:$False -MailNickname US.Employees -GroupTypes "DynamicMembership", "Unified" -MembershipRule "(User.usagelocation -eq ""US"")" -MembershipRuleProcessingState "On"

Update Group Properties

PowerShell modules like Exchange Online and Azure AD usually include Set- cmdlets to update the properties of objects. The SDK uses Update- cmdlets, so to update a group, you run the Update-MgGroup cmdlet. For example, this command updates a group’s description:

Update-MgGroup -GroupId dc9e6f8b-6734-4180-af25-aa40fae79280 -Description "People lucky enough to have Office 365 E5 licenses"

Currently, the Microsoft Graph Groups API treats a group’s proxyAddresses property as read-only, which means that you can’t add or remove a proxy address using the Update-MgGroup cmdlet. Use an Exchange Online cmdlet like Set-UnifiedGroup instead.

Updating Group Membership

The New-MgGroupMember cmdlet populates the group membership. In this example, we get the group identifier, use Get-MgUser to find a set of suitable group members, and finally add them to the group:

$GroupId = (Get-MgGroup -Filter "displayName eq 'Sales Operations Team'").Id
[array]$Users = Get-MgUser -Filter "department eq 'Sales'"
ForEach ($User in $Users) {
  New-MgGroupMember -GroupId $GroupId -DirectoryObjectId $User.Id }

In the past, checking that the right members are present afterwards is not as simple as it should be. Instead of Get-MgGroupMember returning a list of group members, you must pipe the output to the Get-MgUser cmdlet:

Get-MgGroupMember -GroupId $GroupId -All | ForEach {Get-MgUser -UserId $_.Id}

You can look at the AdditionalProperties property retuned by Get-MgGroup to find information about the group members. For example, this command returns a list of display names for group members:

$GroupData = Get-MgGroupmember -GroupId $GroupId
$GroupData.AdditionalProperties.displayName

Adding a group owner is a little complicated because the owner is stored by reference to its object rather than as a simple property. The New-MgGroupOwnerByRef cmdlet requires the identifier for the owner’s account to be passed in a hash table:

New-MgGroupOwnerByRef -GroupId $GroupId -AdditionalProperties @{"@odata.id"="https://graph.microsoft.com/v1.0/users/2a3b60f2-b36b-4758-8533-77180031f3d4"}

To remove a member from a Microsoft 365 group, use the Remove-MgGroupMemberByRef cmdlet. This cmdlet doesn’t work with distribution lists or mail-enabled security groups. This command removes the user object pointed to by the GUID from a target group identified by the $GroupId variable.

Remove-MgGroupMemberByRef -DirectoryObjectId 08dda855-5dc3-4fdc-8458-cbc494a5a774 -GroupId $GroupId

Removing Groups

The Remove-MgGroup cmdlet removes a Microsoft 365 group or security group. For example:

Remove-MgGroup -GroupId f6dd8a3e-d50c-4af2-a9cf-f4adf71ec82b

The cmdlet can’t remove distribution lists or mail-enabled security groups. You must do this with the Exchange Online cmdlets.

Restore Deleted Groups

Deleted groups remain in a soft-deleted state for 30 days following their deletion. During this time, it’s possible to restore the group using the Restore-MgGroup cmdlet. To find the set of soft-deleted groups awaiting permanent removal, take the code to find soft-deleted users in this article and amend the Get-MgDirectoryDeletedItem cmdlet to look for microsoft.graph.group objects instead of microsoft.graph.user.

The report lists the set of soft-deleted groups, including their identifiers. To restore a group, run the Restore-MgDirectoryDeletedItem-MgGroup cmdlet and pass the identifier of the group:

Restore-MgDirectoryDeletedItem -DirectoryObjectId 4e9393c3-67e9-4f95-a0df-70103a667c0a

Finding Group Objects

The Get-MgGroup cmdlet fetches details of Entra ID groups. To retrieve a single group, use its display name as a filter:

Get-MgGroup -Filter "DisplayName eq 'Leadership Team'"

You can also search using the StartsWith filter:

 Get-MgGroup -Filter "startsWith(displayname, 'Leadership')"

If you add the All parameter, you’ll get all the groups in the tenant.

[array]$Groups = Get-MgGroup -All

The command returns groups of all types. To filter out the various types of groups, we can check different properties to identify each type of group. Table 1 lists useful properties to check.

PropertyUsed by
MailEnabled = TrueDistribution lists
Microsoft 365 groups
Mail-enabled security groups
SecurityEnabled = TrueSecurity groups
Mail-enabled security groups
GroupTypes = UnifiedMicrosoft 365 groups
GroupTypes = DynamicMembershipDynamic groups
GroupTypes = Unified, DynamicMembershipDynamic Microsoft 365 groups
ResourceProvisioningOptions = TeamTeam-enabled Microsoft 365 groups
Table 1: Filters for different types of Entra ID groups

The simplest filters are those which find groups based on a property. For example, to find all security-enabled groups:

Get-MgGroup -Filter “securityEnabled eq true” -All

Find all mail-enabled groups:

Get-MgGroup -Filter “mailEnabled eq true” -All

The GroupTypes and ResourceProvisioningOptions properties require complex filters with Lambda operators. For example, to find the set of Microsoft 365 groups in the tenant:

[array]$M365Groups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'unified')" -All 

To find the set of dynamic Microsoft 365 Groups:

Get-MgGroup -Filter "groupTypes/any(c:c eq 'dynamicmembership') and groupTypes/any(x:x eq 'unified')" -All

To find the set of Microsoft 365 groups enabled for Teams:

[array]$Teams = Get-MgGroup -Filter "resourceProvisioningOptions/Any(x:x eq 'Team')" -All

In addition, client-side filter can refine the results returned by the server. For instance, after fetching all security-enabled groups, we use a client-side filter to find the set with dynamic membership:

[array]$SecurityGroups = Get-MgGroup -Filter “securityEnabled eq true” -All 
[array]$DynamicSecurityGroups = $SecurityGroups | ? {$_.GroupTypes -eq “DynamicMembership”}

Filter Speed

The filters used by Get-MgGroup are server-side, meaning that the data is filtered when the server returns it to PowerShell. Because they’re Graph-based and return fewer properties than cmdlets like Get-UnifiedGroup, these commands are very fast, which makes them worth considering if you have scripts which fetch subsets of groups for processing.

As an example, I replaced calls to the Get-UnifiedGroup and Get-AzureADMSGroup cmdlets in a script to report Microsoft 365 groups under the control of the Groups expiration policy with Get-MgGroup and processing time fell from 30 seconds to 1.9 seconds.

Complex Queries Against Entra ID Groups

Get-MgGroup supports complex queries against Entra ID. Essentially, a complex query is one that the Microsoft Graph resolves against a separate property store. It’s not always obvious when a complex query is necessary. Microsoft could hide this need in code instead of forcing PowerShell coders to remember when they must add the ConsistencyLevel parameter to mark a query as complex. Searching the display name of groups for a term is an example of a complex query.

[array]$Groups = Get-MgGroup -Search '"displayname:Office 365"' -ConsistencyLevel Eventual

Another example is to use the Filter parameter to find groups which start with a value. For instance, we might want to find groups whose display name starts with Office:

[array]$Groups = Get-MgGroup -Filter "startsWith(DisplayName, 'Office')" -ConsistencyLevel Eventual

An Evolving Story

Microsoft’s documentation for migration of Azure AD cmdlets admits “There is currently no tool to automatically converts scripts in Azure AD PowerShell to Microsoft Graph PowerShell.” I don’t anticipate that such a tool will appear. As described here, its Graph foundation mean that the ways of performing actions against Entra ID groups with the Microsoft Graph PowerShell SDK differ from how things work with the Azure AD module. It will take time and effort to master the details. Hopefully, the advice contained here helps that evolution.


Keep up to date with developments like the migration from the cmdlets in the Azure AD module to the Microsoft Graph SDK for PowerShell by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers understand the most important changes happening across Office 365.

]]>
https://office365itpros.com/2022/03/29/create-entra-id-group/feed/ 4 54278