Chapter 1. Users and Teams Management

If you use Microsoft Teams, you may have heard this phrase a few times: Teams is the hub for your teamwork. That’s usually meant to point out the different features and functions that Teams provides to make collaboration as easy and effective as possible. In the context of this book, that phrase reinforces the idea that Teams administration and management incorporate functionalities that used to be in separated silos. Teams is deeply integrated in the Microsoft 365 platform to deliver a seamless user experience.

The logical consequence of this integration is that managing users, groups, and teams often involves administrative tasks that are not specific to Teams. The Teams Admin Center (TAC) and the Teams PowerShell module won’t be the only tools you will use to manage Teams.

Note

Throughout the book, I will use Teams (with a capital T) when I refer to the app and platform, and teams (with a lowercase t) when talking about grouping people and giving them a channel to work together.

The TAC lets you manage many aspects of Teams within the browser. Policies can be created within the TAC, assigned to users, updated, and removed. The downside, however, is that the TAC does not provide detailed feedback, information, or error handling—especially when performing batch updates. PowerShell provides more details (regular PowerShell error handling and code) and is the best choice to manage multiple tenants or to leverage functions from Microsoft 365 and Microsoft Entra ID at the same time.

With regard to user and team management in Teams, this chapter will introduce some activities that are typically required on an almost daily basis, such as assessing what kinds of licenses you have and what those licenses enable in terms of features and access to different products.

When managing your tenant, there is also a considerable amount of work required to keep Teams manageable and compliant with company standards, including managing the lifecycle of teams and channels. Compliance involves tools that span Microsoft 365. We’ll discuss some of the required activities in this chapter, and we’ll explore compliance in more detail in Chapter 16.

Finally, one more aspect to consider in the administration of Teams is the interaction with external users accessing your Teams environment from other companies. They are not your users, strictly speaking, but they impact compliance and security, among other things. Chapter 11 explores this topic in detail.

1.1 Reporting the Assigned Office 365 License

Problem

You want to generate a report with a list of available and in-use licenses.

Solution

You will use the Microsoft Graph PowerShell SDK for this solution.

Note

If you have not installed the Graph PowerShell SDK, the Discussion section of this recipe gives you additional information and resources. The solution has been tested with Microsoft.Graph module version 1.21.0. You can check the installed version of the module (if any) using the following command:

Get-InstalledModule

As a first step, you must identify the required scopes (permissions) for the specific service you want to access. In this solution, the Get-MgSubscribedSku command   is used to get the list of commercial subscriptions that an organization has acquired:

Find-MgGraphCommand -Command Get-MgSubscribedSku | Select -First 1 `
-ExpandProperty Permissions

Figure 1-1 shows the output.

Figure 1-1. Permissions required for the Get-MgSubscribedSku command

For Get-MgSubscribedSku, the required permissions are Directory.Read.All, Directory.ReadWrite.All, Organization.Read.All, and Organization.Read⁠Write.​All.

Note

Graph users must sign in using the Connect-MgGraph command, and they must explicitly specify the permissions required for the API they are accessing when issuing the command. Find-MgGraphCommand will help you understand which permissions to ask for when you invoke the Connect-MgGraph command.

If a user fails to include required permissions when issuing the Connect-MgGraph command, and those permissions have not already been granted to the application or to the user, then subsequent commands that access the APIs authorized via those permissions will fail.

The next step is connecting the Graph PowerShell SDK with the correct scope. There could be a multi-factor authentication (MFA) prompt and a request to assign Microsoft Graph PowerShell permissions, depending on your tenant configuration. A successful connection will display the message “Welcome to Microsoft Graph!”

You can assign the scope by running the following command:

Connect-MgGraph -Scopes "Directory.Read.All","Directory.ReadWrite.All", `
"Organization.Read.All", "Organization.ReadWrite.All"

When you use the Connect-MgGraph cmdlet (a small, lightweight command used in PowerShell), the last tenant you signed into during a session will be used by default. If you want to force a specific tenant, the -TenantId parameter is required, as in the following example:

Connect-MgGraph -TenantId "eab48e2f-746a-4346-bf7f-xxxxxxxxx"

To see all the SKUs for a company, use the following command:

Get-MgSubscribedSku | select ConsumedUnits,SkuId,SkuPartNumber

Figure 1-2 shows the output.

Figure 1-2. Output including available licensing plans for your organization

SKU stands for stock-keeping unit, but Microsoft uses it to define a specific business product that it sells.

Within each license type, there are also service plans (apps) provided by the license. To enable or disable a service plan, you need to use the ServicePlanId. You can view the service plans, included in an SKU, using the following commands:

$license = Get-MgSubscribedSku
$license[0].ServicePlans

Figure 1-3 shows the ServicePlanId view, after running the previous commands.

Figure 1-3. Service plans included in the product that corresponds to the first SKU

To visualize a list of assigned SKUs for a single user (for example, AllanD@M365x01033383.onmicrosoft.com), use the following command:

Get-MgUserLicenseDetail -UserId AllanD@M365x01033383.onmicrosoft.com | fl

The Get-* cmdlets return objects, which can contain properties that are arrays of values. When you use the pipe symbol (|) to forward those objects to the Format-List cmdlet, PowerShell only shows you the first four, by default.

Since the results that you see in the previous command are truncated, you can use the FormatEnumerationLimit variable to tell PowerShell how many occurrences to include in the formatted output. If you set the variable to -1, PowerShell displays all occurrences (as in the following command):

$FormatEnumerationLimit=-1

Discussion

If you work with the Graph PowerShell SDK often, it makes sense to have a script to automate the scope assignment for the commands you use most frequently. The following script is just an example; you can save it in a PowerShell script (PS1 file) to execute whenever you need to use the Graph PowerShell SDK:

Select-MgProfile -Name "beta"
$scopes = @(
"AuditLog.Read.All",
"Directory.Read.All",
"Directory.ReadWrite.All",
"Group.ReadWrite.All",
"GroupMember.ReadWrite.All",
"Organization.Read.All",
"Organization.ReadWrite.All",
"TeamsApp.ReadWrite.All",
"TeamsAppInstallation.ReadWriteForTeam",
"TeamsAppInstallation.ReadWriteSelfForTeam",
"TeamSettings.ReadWrite.All",
"TeamsTab.ReadWrite.All",
"TeamMember.ReadWrite.All",
"User.Read.All"
)
Connect-MgGraph -Scopes $scopes -TenantId "eab48e2f-746a-4346-bf7f-xxxxxxxxx"

You can save this script in a file called GraphConnect.ps1 and use it in every solution that requires connectivity to Graph.

Microsoft provides services from its cloud (such as Azure and Microsoft 365) based on the licenses that are purchased and assigned to a user. One of the routine activities for Microsoft 365 and Teams administrators is assigning and removing Microsoft licenses, especially because companies prefer to acquire (and pay for) as few licenses as possible and reusing existing ones is a common practice.

In the past, different Microsoft resources—Azure Active Directory (AD) (now Entra ID), Exchange Online (EXO), etc.—had different sets of APIs and were considered to be different endpoints. Microsoft is moving to a different approach, with the goal to connect everything. This new approach is based on Graph exposing REST APIs and client libraries to consume and manage all the different Microsoft cloud services.

Microsoft Graph offers access to:

  • Microsoft 365 core services

  • Enterprise Mobility + Security services

  • Windows services

  • Dynamics 365 Business Central services

The basic way to use Graph is via HTTP using https://graph.microsoft.com. It is possible to access the Graph REST APIs using an SDK, available in different languages, to simplify building applications that access Graph. One of the supported languages is PowerShell.

The Graph PowerShell SDK is a collection of PowerShell modules that contain commands for calling the Graph service, and it’s the one that we are going to use for our recipes.

To install and import the Graph SDK in Windows PowerShell, run the following commands:

Install-Module Microsoft.Graph -Scope CurrentUser
Import-Module Microsoft.Graph

The import command may produce the following error:

Import-Module: Function "XYZ" cannot be created because function capacity 4096 `
has been exceeded for this scope.

If that happens, use the following commands to increase the function count and the variable count, and run the Import-Module command again:

$MaximumFunctionCount = 8192
$MaximumVariableCount = 8192

The -Scope CurrentUser parameter in PowerShell specifies the current user environment as the target scope for a particular command. When this parameter is specified, the command will only operate on the user environment variables, registry settings, and other elements that are specific to the current user, as opposed to the system-wide environment. This is particularly useful when you do not have full administrative rights on the machine you are using. Throughout the book, the -Scope parameter will usually be omitted. The decision of whether to include it or not will depend on the level of access you have to the machine you are using and whether you intend to make changes on a machine-wide or user-specific level.

Prerequisites and additional details about the Graph PowerShell SDK installation are detailed in the Microsoft article “Get started with the Microsoft Graph PowerShell SDK”.

Microsoft licensing evolves with the solutions offered in Microsoft 365. Paired with the basic license (for example, an E3 or an E5), there are add-ons that may be required to access specific features, like Public Switched Telephone Network (PSTN) calling or conditional access. A clear understanding of what is included in each plan, what is required to deliver the necessary functionalities, and what you already have is extremely important.

The official Microsoft documentation provides a list of product names and service plans for licensing. The M365Maps website is also a useful (unofficial) guide to the different Microsoft licenses. You can use it as a starting point if you need an overview of the different options.

The information available from the Get-MgSubscribedSku command includes the following:

AccountObjectId

The unique ID of the account this SKU belongs to

AccountSkuId

The unique string ID of the account/SKU combination

ActiveUnits

The number of active licenses

ConsumedUnits

The number of licenses consumed

ServiceStatus

The provisioning status of individual services belonging to this SKU

SkuId

The unique ID for the SKU

SkuPartNumber

The part number of this SKU

SubscriptionIds

A list of all subscriptions associated with this SKU (for the purposes of assigning licenses, all subscriptions with the same SKU will be grouped into a single license pool)

SuspendedUnits

The number of suspended licenses (these licenses are not available for assignment)

1.2 Allocating and Removing User Licenses

Problem

You want to assign and remove Office 365 licenses for a Microsoft 365 group.

Solution

In this recipe, you will assign or remove Office 365 E5 licenses to/from a Microsoft 365 group named “Users with Teams Voice.”

You can leverage the SkuPartNumber information gathered with the cmdlets used in the previous recipe to define which licenses to assign or remove. The license you want to add is ENTERPRISEPREMIUM. The license you want to remove is VISIOCLIENT.

You must first connect to the Graph PowerShell SDK module:

.\GraphConnect.ps1

The license information needs to be saved in variables:

$ENTERPRISEPREMIUM = Get-MgSubscribedSku -All | where SkuPartNumber -eq `
	"ENTERPRISEPREMIUM"

$VISIOCLIENT = Get-MgSubscribedSku -All | where SkuPartNumber -eq "VISIOCLIENT"

A quick check of one of the variables (for example, $VISIOCLIENT) will show the information is correctly stored (see Figure 1-4).

Figure 1-4. Gathering license details

You now need to find the GroupId for the group “Users with Teams Voice.” The following command searches for the group using a filter (DisplayName starting with “Users with Teams Voice”) and puts the results in a variable:

$Group = Get-MgGroup -Filter "DisplayName eq 'Users with Teams Voice'" `
	-CountVariable CountVar -Top 1 -Sort "DisplayName" -ConsistencyLevel `
	eventual

The GroupId makes it possible to create a list of the group members. In our case, the parameter we’re interested in is the user’s email address:

Get-MgGroupMember -GroupId $Group.Id | select AdditionalProperties | `
foreach {get-mguser -userid $_.AdditionalProperties.mail}
Note

In the previous command, I’m assuming that the user principal name (UPN) and email address are the same. If this is not the case, you must edit the command to match your tenant’s naming standard for users.

You can assign a license to all the users in the group with the following command:

Get-MgGroupMember -GroupId $Group.Id | select AdditionalProperties | `
foreach {Set-MgUserLicense -UserId $_.AdditionalProperties.mail `
-AddLicenses @{SkuId = $ENTERPRISEPREMIUM.SkuId} -RemoveLicenses @()}

You can use the following command to quickly check that the licenses have been assigned:

Get-MgGroupMember -GroupId $Group.Id | select AdditionalProperties | foreach `
{Get-MgUserLicenseDetail -UserId $_.AdditionalProperties.mail}

The output will look like Figure 1-5.

Figure 1-5. Licenses applied to a user group

In a similar way, you can remove licenses:

Get-MgGroupMember -GroupId $Group.Id | select AdditionalProperties | foreach `
{Set-MgUserLicense -UserId $_.AdditionalProperties.mail -AddLicenses @() `
-RemoveLicenses $VISIOCLIENT.SkuId}

Discussion

Managing licenses and applications based on the joiners, movers, and leavers (JML) process within a company is a task that happens on an almost daily basis. To streamline this process and reduce the risk of errors, you can make use of user groups and PowerShell.

Entra ID is the underlying infrastructure that supports identity management for all Microsoft cloud services. Entra ID stores information about license assignment states for users and automatically manages license modifications when group membership changes. Some key takeaways about this problem’s solution are as follows:

  • Licenses can be assigned to any security group in Entra ID.

  • You can disable one or more service plans inside a specific SKU.

  • All Microsoft cloud services that require user-level licensing are supported.

  • Users who receive licenses from different groups they are members of will get all the products and services assigned to these different groups.

1.3 Scripting the Creation of Teams

Problem

You want to standardize and streamline the tasks related to teams and channels.

Solution

Create a new team with the following parameters:

Then, disable Giphy usage, add a channel, and add members to the team using the “Users with Teams Voice” user group.

Note

Giphy is a third-party source. Its content is not controlled by Microsoft, and there is a risk that inappropriate content may appear. Animated GIFs also add complexity to message compliance controls.

Let’s start by connecting to Teams:

Import-Module MicrosoftTeams
Connect-MicrosoftTeams

The following command will create the team with the required parameters:

New-Team -DisplayName "Teams Cookbook" -Description `
"Teams Cookbook Information Sharing" -Visibility Public -Owner `
admin@M365x01033383.onmicrosoft.com

You can check the results with the following commands:

$team = Get-Team -DisplayName "Teams Cookbook"
$team | fl

The output is shown in Figure 1-6.

Figure 1-6. Checking the new team created with PowerShell

Next, disable Giphy usage inside the channel:

Set-Team -GroupId $team.GroupId -AllowGiphy $false

And add a channel to the team:

New-TeamChannel -DisplayName "Teams Cookbook Private Channel" `
-GroupId $team.GroupId -MembershipType Private

Connect to the Graph PowerShell SDK and get the GroupId using a variable, as in Solution 1.2:

.\GraphConnect.ps1

$Group = Get-MgGroup -Filter "startswith(displayName, `
	'Users with Teams Voice')" -CountVariable CountVar -Top 1 -Sort `
	"displayName" -ConsistencyLevel eventual

Then list the group members and export the UPN in a comma-separated values (CSV) file:

Get-MgGroupMember -GroupId $Group.Id | select AdditionalProperties | foreach `
{Get-MgUser -UserId $_.AdditionalProperties.mail} | Export-Csv `
c:\temp\users.csv -notypeinformation
Warning

The parameter -NoTypeInformation is required to remove the first line that is automatically created when exporting to a CSV file. In our case, that looks like #TYPE Microsoft.Graph.PowerShell.​Mod⁠els.MicrosoftGraphUser1.

The result you want instead must show the properties as columns, as shown in Figure 1-7.

Figure 1-7. CSV export correctly formatted

Finally, bulk import the users with the following command:

Import-Csv -Path c:\temp\users.csv | foreach {Add-TeamUser -GroupId `
$team.GroupId -User $_.UserPrincipalName}

Discussion

The solutions in this book have been tested using Microsoft Teams PowerShell module version 4.9.3.

You could also use Graph to create a team, using the New-MgTeam command. The basic format of the command requires a Template and a DisplayName. The IDs of the different templates are available inside the TAC (on the Teams tab, under “Team templates,” as you can see in Figure 1-8).

Figure 1-8. Team templates listed in the TAC

Each template has a template ID that is visible in the details of the template. For example, Incident Response has the following template ID:

com.microsoft.teams.template.CoordinateIncidentResponse

So to create a new team called “Incident Response Team” using the Incident Response template, you could use the following command (in a PS1 file):

Using Namespace Microsoft.Graph.PowerShell.Models
[MicrosoftGraphTeam1]@{
Template = [MicrosoftGraphTeamsTemplate]@{
Id = 'com.microsoft.teams.template.CoordinateIncidentResponse'
}
DisplayName = "Incident Response Team"
Description = "Incident Response Team"
} | New-MgTeam

1.4 Teams: Creating a Team with Dynamic Membership

Problem

You want to assign membership to a team using a dynamic Microsoft 365 group.

Solution

You can create a team that uses an Entra ID group with dynamic membership to define its own members list. The Discussion section of this recipe talks more about dynamic membership groups. Focusing on the Teams administration part of the solution, you must first log in to a Teams client with your administrative account and select “Join or create a team,” and then select “Create a team.” Click “From a group or team,” as shown in Figure 1-9.

Figure 1-9. Creating a team from a group

Then, choose “Microsoft 365 group,” as shown in Figure 1-10.

Figure 1-10. Using a Microsoft 365 Group

In our example, the group is called “DynamicGroupTeams” (Figure 1-11).

Figure 1-11. Selecting the dynamic membership group

Discussion

Microsoft Teams supports teams associated with Microsoft 365 groups with dynamic membership. Dynamic membership for Microsoft 365 groups means that the list of the users included in the group (and, as a consequence, in the team) will be created and updated based on one or more rules that check for certain user attributes in Entra ID. Users are automatically added to or removed from the correct groups as user attributes change or as users join and leave the tenant.

Let’s quickly define a group with dynamic membership. Our query filters users that have street addresses containing “205” and a state equal to “WA.”

First, sign in to the Microsoft Entra admin center with an account that has a role as Global Administrator, Intune Administrator, or User Administrator in the organization.

Search for and select Groups, as shown in Figure 1-12.

Figure 1-12. Opening the groups management screen in Entra ID

Select “All groups,” and then click “New group,” as shown in Figure 1-13.

Figure 1-13. Defining a new group in Entra ID

After you select “New group,” “Group name” will be DynamicGroupTeams and “Membership type” will be Dynamic User, as shown in Figure 1-14.

Figure 1-14. Parameters for the dynamic membership group

At the bottom, click “Add dynamic query.”

As Figure 1-15 shows, our query will be (user.streetAddress -contains "205") and (user.state -eq "WA").

Figure 1-15. Defining the dynamic membership rule

Select Validate Rules to test the query on a few users, as shown in Figure 1-16.

Figure 1-16. Testing the dynamic membership query

Save the query and select Create. For a team with dynamic membership, the capability to add members manually will not be available. Figure 1-17 shows the limited number of options you have for teams with dynamic membership.

Figure 1-17. Options available for a team with dynamic membership

When you open the Members tab (see Figure 1-18), you will see a disclaimer that the membership settings prevent you from adding or removing members.

Figure 1-18. Dynamic membership banner on the Members tab

Owners will not be able to add or remove users as members of the team, since members are defined by dynamic membership rules.

Note

The Graph PowerShell SDK offers a command that you can use to create a dynamic Microsoft 365 group: New-MgGroup. For example, if you want to create a new dynamic group called “Dynamic_Group_Created_with_Graph” with the same membership rules that we used earlier, you can use the following command:

New-MgGroup -DisplayName "Dynamic_Group_Created_with_Graph" `
-Description "Dynamic Group Created with Graph" `
-MailEnabled:$True -SecurityEnabled:$True -MailNickname `
DynamicGroupGraph -GroupTypes "DynamicMembership", "Unified" `
-MembershipRule "(user.streetAddress -contains ""205"") and `
(user.state -eq ""WA"")" -MembershipRuleProcessingState "On"

1.5 Managing Apps in Teams and Channels

Problem

You want to generate a report about the apps installed in a specific team.

Solution

You want to list all the apps installed in a specific team (Sales and Marketing) and gather some additional information about them.

The TAC has tools to manage the permissions related to app installation. However, we cannot see which apps are installed in a specific team. The Teams module for PowerShell does not have a cmdlet to return information about the installed apps, so the best solution is to use the Graph PowerShell SDK.

First, connect to Teams with the usual command:

Connect-MicrosoftTeams

Then get the team’s GroupId using the following command:

Get-Team -DisplayName "Sales and Marketing"

Figure 1-19 shows the output.

Figure 1-19. Gathering team information

Next, connect to the Graph PowerShell SDK module, retrieve the information, and save it in a variable (expanding the properties that we need):

.\GraphConnect.ps1

$app_info = Get-MgTeamInstalledApp  -TeamId `
e5ac9743-4586-426f-a36b-bbe4a72b5802 -ExpandProperty TeamsApp,TeamsAppDefinition

You can export the list of installed apps in a CSV file:

$app_info.TeamsApp | Export-Csv c:\temp\app_info.csv -NoTypeInformation

The result will look like Figure 1-20.

Figure 1-20. Installed apps in a team

Additional information about the apps is available if we export the app definitions using a command like this one:

$app_info.TeamsAppDefinition | Export-Csv c:\temp\TeamsAppDefinition.csv `
-NoTypeInformation

We can see an example of an export in Figure 1-21.

Figure 1-21. Installed apps in a team, including app descriptions

Discussion

The Graph PowerShell SDK is a collection of PowerShell modules that contain commands for calling the Graph service.

The Graph PowerShell SDK is organized into modules that contain related commands and functions. Each module focuses on a specific aspect of Microsoft 365 administration, such as users, groups, SharePoint, or Teams. Modularity allows administrators to load and use only the modules they need, ensuring a lighter and more customized user experience.

The Graph PowerShell SDK complies with Microsoft’s security and compliance standards. Administrators can ensure that Microsoft 365 services are secure by using built-in security features and controls. PowerShell scripts and credentials can be secured with MFA, application permissions, and best practices. This helps administrators comply with organizational requirements and maintain a robust security posture.

1.6 Creating User Reports: Active Users and Channels

Problem

You want to export information about all the teams deployed in your organization.

Solution

The script you’ll use exports the names of all the teams, as well as the following information for each team: team object ID, team owners, team member count, list of all the team members, number of channels in the team, channel names, SharePoint site, access type, and team guests.

Connect to Teams:

Connect-MicrosoftTeams

Connect to Exchange Online:

Connect-ExchangeOnline -UserPrincipalName admin@domain.com

Run the following script (save it in a PS1 file):

$AllTeamsInOrg = (Get-Team).GroupID
$TeamList = @()

Foreach ($Team in $AllTeamsInOrg)
{      
        $TeamGUID = $Team.ToString()
        $TeamGroup = Get-UnifiedGroup -Identity $Team.ToString()
        $TeamName = (Get-Team | ?{$_.GroupID -eq $Team}).DisplayName
        $TeamOwner = (Get-TeamUser -GroupId $Team | ?{$_.Role -eq 'Owner'}).User
        $TeamMembers = (Get-TeamUser -GroupId $Team | ?{$_.Role -eq `
		'Member'}).User
        $TeamUserCount = ((Get-TeamUser -GroupId $Team).UserId).Count
        $TeamGuest = (Get-UnifiedGroupLinks -LinkType Members -Identity $Team `
	| ?{$_.Name -Match "#EXT#"}).Name
        	    if ($TeamGuest -eq $null)
            	{
                	$TeamGuest = "No Guests in Team"
            	}
        $TeamChannels = (Get-TeamChannel -GroupId $Team).DisplayName
		$ChannelCount = (Get-TeamChannel -GroupId $Team).ID.Count
        $TeamList = $TeamList + [PSCustomObject]@{TeamName = $TeamName; `
		TeamObjectID = $TeamGUID; TeamOwners = $TeamOwner -join ', '; `
		TeamMemberCount = $TeamUserCount;TeamMembers = "$TeamMembers"; `
		NoOfChannels = $ChannelCount; ChannelNames = $TeamChannels `
		-join ', '; SharePointSite = $TeamGroup.SharePointSiteURL; `
		AccessType = $TeamGroup.AccessType; TeamGuests = $TeamGuest `
		-join ','}
}

$TeamList | Export-Csv c:\temp\TeamsDatav2.csv -NoTypeInformation

Discussion

Using the TAC to generate reports on channels in different teams, team members, team owners, and so on is complex. The Microsoft Teams PowerShell module can display and export the relevant information.

The original script for this solution was published on Microsoft’s TechCommunity website. I have modified it slightly to export a list of all members in a team.

You can add any information you want to the export by simply adding a line with the property and an additional parameter to the $TeamList variable. For example, let’s say you want to see whether a team is archived or not. You can add a line to the following script (I added it after line 9):

$TeamArchived = (Get-Team | ?{$_.GroupID -eq $Team}).Archived

Then, modify the $TeamList variable (the modification is in bold):

$TeamList = $TeamList + [PSCustomObject]@{TeamName = $TeamName; `
TeamArchived = $TeamArchived; TeamObjectID = $TeamGUID; `
TeamOwners = $TeamOwner -join ', '; TeamMemberCount = $TeamUserCount; `
TeamMembers = "$TeamMembers";  NoOfChannels = $ChannelCount; ChannelNames = `
$TeamChannels -join ', '; SharePointSite = $TeamGroup.SharePointSiteURL; `
AccessType = $TeamGroup.AccessType; TeamGuests = $TeamGuest -join ','

1.7 Reporting Teams User Policies

Problem

You need to verify that the right policies have been applied to your Teams users.

Solution

This script exports a report of all policies for all users who have accounts on Teams.

Start by connecting to Teams:

Connect-MicrosoftTeams

Then, run the following script (and save it in a PS1 file):

$TeamsUsers = Get-CsOnlineUser

$TeamsReport = @()

Foreach ($User in $TeamsUsers) {
    $Info = "" | Select "DisplayName","ObjectId","UserPrincipalName", `
	"SipAddress","Enabled","LineURI","WindowsEmailAddress", `
	"HostedVoiceMail","OnPremEnterpriseVoiceEnabled","OnPremLineURI", `
	"SipProxyAddress","OnlineDialinConferencingPolicy", `
	"TeamsUpgradeEffectiveMode","TeamsUpgradePolicy", `
	"HostingProvider","VoicePolicy","MeetingPolicy",`
	"TeamsMeetingPolicy","TeamsMessagingPolicy","TeamsAppSetupPolicy", `
	"TeamsCallingPolicy","VoicePolicySource","MeetingPolicySource", `
	"TeamsMeetingPolicySource","TeamsMessagingPolicySource", `
	"TeamsAppSetupPolicySource","TeamsCallingPolicySource"

    Write-Host "Querying policy information for" $User.DisplayName `
	-ForegroundColor Green

    $UserPolicies = Get-CsUserPolicyAssignment -Identity $User.SipAddress

    $Info.DisplayName = $User.DisplayName
    $Info.ObjectId = $User.ObjectId
    $Info.UserPrincipalName = $User.UserPrincipalName
    $Info.SipAddress = $User.SipAddress
    $Info.Enabled = $User.Enabled
    $Info.LineURI = $User.LineURI
    $Info.WindowsEmailAddress = $User.WindowsEmailAddress
    $Info.HostedVoiceMail = $User.HostedVoiceMail
    $Info.OnPremEnterpriseVoiceEnabled = $User.OnPremEnterpriseVoiceEnabled
    $Info.OnPremLineURI = $User.OnPremLineURI
    $Info.SipProxyAddress = $User.SipProxyAddress
    $Info.OnlineDialinConferencingPolicy = $User.OnlineDialinConferencingPolicy
    $Info.TeamsUpgradeEffectiveMode = $User.TeamsUpgradeEffectiveMode
    $Info.TeamsUpgradePolicy = $User.TeamsUpgradePolicy
    $Info.HostingProvider = $User.HostingProvider
    $Info.VoicePolicy = ($UserPolicies | Where-Object {$_.PolicyType -eq `
	"VoicePolicy"}).PolicyName
    $Info.VoicePolicy = (($UserPolicies | Where-Object {$_.PolicyType -eq `
	"VoicePolicy"}).PolicySource).AssignmentType
    $Info.MeetingPolicy = ($UserPolicies | Where-Object {$_.PolicyType -eq `
	"MeetingPolicy"}).PolicyName
    $Info.MeetingPolicySource = (($UserPolicies | Where-Object `
	{$_.PolicyType -eq "MeetingPolicy"}).PolicySource).AssignmentType
    $Info.TeamsMeetingPolicy = ($UserPolicies | Where-Object {$_.PolicyType `
	-eq "TeamsMeetingPolicy"}).PolicyName
    $Info.TeamsMeetingPolicySource = (($UserPolicies | Where-Object `
	{$_.PolicyType -eq "TeamsMeetingPolicy"}).PolicySource).AssignmentType
    $Info.TeamsMessagingPolicy = ($UserPolicies | Where-Object `
	{$_.PolicyType -eq "TeamsMessagingPolicy"}).PolicyName
    $Info.TeamsMessagingPolicySource = (($UserPolicies | Where-Object `
	{$_.PolicyType -eq "TeamsMessagingPolicy"}).PolicySource).AssignmentType
    $Info.TeamsAppSetupPolicy = ($UserPolicies | Where-Object `
	{$_.PolicyType -eq "TeamsAppSetupPolicy"}).PolicyName
    $Info.TeamsAppSetupPolicySource = (($UserPolicies | Where-Object `
	{$_.PolicyType -eq "TeamsAppSetupPolicy"}).PolicySource).AssignmentType
    $Info.TeamsCallingPolicy = ($UserPolicies | Where-Object {$_.PolicyType `
	-eq "TeamsCallingPolicy"}).PolicyName
    $Info.TeamsCallingPolicySource = (($UserPolicies | Where-Object `
	{$_.PolicyType -eq "TeamsCallingPolicy"}).PolicySource).AssignmentType

    $TeamsReport += $Info
    $Info = $null
    }

$TeamsReport | Export-Csv .\TeamsReport.csv -NoTypeInformation

Discussion

The basic script (which I have slightly modified) was published on the Microsoft TechCommunity website. Removing one or more of the $info.<parameter> lines will give you a shorter report if you are focused on a specific group of policies.

1.8 Bulk Assignment of Teams User Policies

Problem

You need to deploy Teams user policies to your organization’s users in batches.

Solution

Automating this kind of operation with PowerShell reduces administrative effort and risk of errors. We’ll assign a Teams meeting policy (RestrictedAnonymousNoRecording) to an Entra ID group whose display name is “Design.”

Connect to Teams:

Connect-MicrosoftTeams

Then connect to the Graph PowerShell SDK and run the following command to get the group information:

.\GraphConnect.ps1

$Group = Get-MgGroup -Filter "startswith(displayName, 'Design')"

Apply the Teams meeting policy to the group:

New-CsGroupPolicyAssignment -GroupId $group.id -PolicyType TeamsMeetingPolicy `
-PolicyName "RestrictedAnonymousNoRecording" -Rank 1

Check the result for policies assigned to the group:

Get-CsGroupPolicyAssignment -GroupId $group.id

Discussion

Assigning policies to Azure AD users and groups is a common task for whoever manages Teams administration. Using Azure AD groups reduces the required maintenance and the risk of assigning incorrect policies to a user. You can also execute this operation from the TAC, but it can be time-consuming.

Additionally, you can check which groups you have assigned a specific policy:

Get-CsGroupPolicyAssignment | Where-Object {$_.PolicyName -eq `
"RestrictedAnonymousNoRecording"}

You can also remove an assigned policy from a group:

Remove-CsGroupPolicyAssignment -PolicyType TeamsMeetingPolicy -GroupId $group.id

Group policy assignment supports all policy types used in Teams except:

  • Teams App Permission Policy

  • Teams Network Roaming Policy

  • Teams Emergency Call Routing Policy

  • Teams Voice Applications Policy

  • Teams Upgrade Policy

1.9 Summary

This chapter provided insight into the various tools and methods that can be used to simplify the daily administration and management of Microsoft Teams. These tools include PowerShell, the Graph PowerShell SDK, and various administrative panels. The scripts and steps discussed in various scenarios are versatile and can be easily adapted or reused to fulfill other administrative needs. In the next chapter, the focus shifts to the security of Teams, highlighting the essential tools and concepts that are necessary to ensure the safety and productivity of Teams users.

Get Microsoft Teams Administration Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.