Group-Based Policy Assignments Not Covered in the Previous Version
In November 2021, I wrote an article explaining how to generate an HTML report about the Teams policies assigned to user accounts. Life was simpler then and group-based policy assignments were relatively new. Showing its age, the ill-fated and never-successful Teams advanced communications license was the requirement required to build and assign custom policy packages to user accounts. Now Teams Premium is the necessary license to support custom policy packages. However, organizations can assign the standard policy packages with the base Teams license.
This explanation is a long-winded way of saying that the report I wrote about in November 2021 only reports direct policy assignments. Any policies assigned to users through group membership are blissfully ignored, a fact highlighted in a LinkedIn discussion that invoked my name.
This is a good example of the risk inherent in sample code: technical developments can render the example less valuable over time. It’s irritating for readers to find that an example that seems to meet their needs doesn’t work. I can assure you that it’s equally irksome for those who create example code when they discover that time and technology erodes the value of their efforts.
Coping with Three Types of Teams Policy Assignments
Something had to be done. Group-based management of objects like policies and licenses is an efficient way to ensure that user accounts with the same role receive a consistent configuration. Fixing the report script would also address other obvious flaws, like using the Exchange Online PowerShell module to get the organization name. I fired up Visual Studio Code and spent a couple of hours on a Saturday afternoon to figure out the best way of reporting the three kinds of policy assignments that exist in Teams:
- Default: No other type of policy assignment exists for an account, so user activity is governed by the default policy.
- Direct: An administrator assigns a specific policy to an account
- Group: A user inherits a policy from a policy package assigned to their account.
Figure 1 shows some of the policies assigned to a Teams user. We can see that 24 different policies are available and that two of the five visible policies are direct assignments while the other three are default assignments.
Reporting Teams Policy Assignments
The Get-CsOnlineUser cmdlet retrieves policy assignments along with a lot of other information about Teams users. The information returned by the cmdlet is sufficient to deal with default and direct policy assignments. If the property for a policy is blank, it means that the default policy is used. If the name of a policy is in the property, it is a direct assignment. In the extract shown below, there are three direct assignments and five instances where the default policy is used:
Get-CsOnlineUser -Identity 'Kim.Akers@office365itpros.com' | fl Teams*Policy* TeamsAppPermissionPolicy : Unrestricted App Access TeamsAppSetupPolicy : App Policy 2 TeamsAudioConferencingPolicy : TeamsCallHoldPolicy : TeamsCallParkPolicy : TeamsCallingPolicy : TeamsCarrierEmergencyCallRoutingPolicy : TeamsChannelsPolicy : PrivateTeamsPolicy (etc.)
Interestingly, the Get-CsOnlineUser cmdlet returns 44 Teams policies. Some of the policies that don’t show up in the Teams admin center are disused. Others might be used in the future.
The script described in the original article reported default and direct assignments, so generating the report is simply a matter of running down through each policy to check if a direct assignment exists and if not, report it as a default assignment. A different approach is needed to deal with group-based assignments. Take this code section that reports the meeting policy for a user:
# Meeting policy TeamsMeetingPolicy = $TenantDefaultString $CurrentAssignment = $null If ($User.TeamsMeetingPolicy) { $TeamsMeetingPolicy = $User.TeamsMeetingPolicy.Name } Else { [array]$PolicyAssignments = Get-CsUserPolicyAssignment -Identity $User.UserPrincipalName ` -PolicyType TeamsMeetingPolicy | Select-Object -ExpandProperty PolicySource If ($PolicyAssignments) { $CurrentAssignment = $PolicyAssignments[0] } If ($CurrentAssignment) { Switch ($CurrentAssignment.AssignmentType) { "Direct" { $TeamsMeetingPolicy = ("{0} (Direct)" -f $CurrentAssignment.PolicyName) } "Group" { $GroupName = (Get-GroupNameByRef -GroupId $CurrentAssignment.Reference).DisplayName $TeamsMeetingPolicy = ("{0} (Group: {1})" -f $CurrentAssignment.PolicyName, $GroupName) } } }
First, the script sets the variable that stores the name of the assigned policy to a default value. Next, it checks if the Get-CsOnlineUser cmdlet returned a policy name. If so, the meeting policy is a direct assignment, and the script doesn’t need to be probed further. The next step runs the Get-CsUserPolicyAssignment cmdlet to check if any assignments exist. The cmdlet returns details of group and direct assignments. A Switch command checks the first (most recent) assignment and updates the variable storing the name of the assigned policy with the policy and a prefix. The direct assignment check might be unnecessary because Get-CsOnlineUser returns this information, but I included it just in case.
If it’s a group assignment, the script calls a function to run the Get-MgGroup cmdlet (from the Microsoft Graph PowerShell SDK) to return the display name of the group used for the assignment. When processing policy assignments for all users, it’s likely that the same groups will be met many times. It would be wasteful to call Get-MgGroup each time, so the function uses a hash table to hold details of the groups it has already processed and only calls Get-MgGroup if the group hasn’t been seen before.
Figure 1 shows an example of the report output.
The folks who reported the problem have tested the updated script (available from GitHub) and say that it works. At least, it addresses the issue that they had and provides a better overview of the policy assignments for Teams users within a tenant. No doubt the code can be improved, but it’s PowerShell so that’s easily done.
Effort Required to Get the Right Results
The downside is that the amended script takes longer to run because of all the extra processing. The upside is that the report generated by the script is accurate because it includes group-based policy assignments. This goes to prove (once again) that achieving the right result takes effort. In this case, the effort filled a couple of hours on a wet Saturday and delivered a practical solution to a problem. That’s always nice.