Teams – Reports with PowerShell – Updated

It is important to know about the current state of your Teams rollout and this is one of those which can easily get out of control in a blink of an eye. I wanted to understand and determine the current Teams state in a tenant I manage and I had to create reports to present.

The portal does give a few options to export the data but I decided to take a look at the option the Teams PowerShell module offers. I spent some time on creating a script that will output these five reports,

  • All Teams data with Channel type, Channel count, Channel count with types, Teams member count and owners count
  • Teams users data with role information
  • Channel information for each Teams with Channel types
  • Channel user information with user information and role
  • Permissions on each Teams

This report can also be scheduled to run if you already use a mechanism to store your credentials securely and pass it on to your PS scripts.

I use the ImportExcel PowerShell module for this script,

Install-Module -Name ImportExcel

Before proceeding further, make sure you have the Teams PowerShell module installed. You’ll need to run this script with Teams Administrator role.

$TeamsCred = Get-Credential
Connect-MicrosoftTeams -credential $TeamsCred

$xlsxPath = ".\Teams-Report_$((Get-Date).ToString("MMddyyyy")).xlsx"

Get-Team | Select GroupId,DisplayName,MailNickName,Archived,Visibility,Description | foreach {
        $ID = $_.GroupId
        $TeamName = $_.DisplayName
        $NickName = $_.MailNickName
        $Archived = $_.Archived
        $visibility = $_.Visibility
        $Description = $_.Description
        $ch = Get-TeamChannel -GroupId $ID
        $ChannelCount = $ch.count
        $TeamUser = Get-TeamUser -GroupId $ID
        $TeamMemberCount = $TeamUser.Count
        $TeamOwnerCount = ($TeamUser | ?{$_.role -eq "owner"}).count
        $stdchannelCount = ($ch | ?{$_.MembershipType -eq "Standard"}).count
        $privchannelCount = ($ch | ?{$_.MembershipType -eq "Private"}).count

        [PSCustomObject]@{
                  'Teams Name'=$TeamName;
                  'Teams MailNickName'=$NickName;
                  'Teams Type'=$Visibility;
                  'Description'=$Description;
                  'Archived?'=$Archived;
                  'Channel Count'=$ChannelCount;
                  'Standard Channel Count'=$stdchannelCount;
                  'Private Channel Count'=$privchannelCount;
                  'Team Members Count'=$TeamMemberCount;
                  'Team Owners Count'=$TeamOwnerCount} | Export-Excel -Path $xlsxPath -WorksheetName "All Teams Report" -TableStyle Medium16 -AutoSize -Append
}

Get-Team | foreach {
    $ID = $_.GroupId;
    $TeamName = $_.DisplayName;
    $NickName = $_.MailNickName;
    Get-TeamUser -GroupId $ID | Select User,Name,Role |
    Foreach {
		[PSCustomObject]@{
			'Teams ID' = $ID;
			'Teams Name' = $TeamName;
			'Teams MailNickName' = $NickName;
                        'User UPN' = $_.User;
			'User DisplayName' = $_.Name;
			'Role' = $_.Role
		}
    }
} | Export-Excel -Path $xlsxPath -WorksheetName "Teams_users" -TableStyle Medium16 -AutoSize

Get-Team | Foreach {
    $ID = $_.GroupId;
    $TeamName = $_.DisplayName;
    $NickName = $_.MailNickName;
    Get-TeamChannel -GroupId $ID | Select Id, DisplayName, MembershipType |
    Foreach {
		[PSCustomObject]@{
			'Teams ID' = $ID;
			'Teams Name' = $TeamName;
			'Teams MailNickName' = $NickName;
			'Channel Name' = $_.DisplayName;
			'Channel Type' = $_.MembershipType
		}
	}
} | Export-Excel -Path $xlsxPath -WorksheetName "Channels" -TableStyle Medium16  -AutoSize

Get-Team | Foreach {
    $ID = $_.GroupId;
    $TeamName = $_.DisplayName;
    $NickName = $_.MailNickName;
    Get-TeamChannel -GroupId $ID | Select DisplayName | 
            Foreach {
            $chName = $_.DisplayName;
                Get-TeamChannelUser -GroupId $ID -DisplayName $chName | Select User,Name,Role |
                    Foreach {
		                [PSCustomObject]@{
			                'Teams Name' = $TeamName;
			                'Channel Name' = $chName;
                                        'User UPN' = $_.User;
                                        'User DisplayName' = $_.Name;
                                        'User Role' = $_.Role
		    }
        }
    }
} | Export-Excel -Path $xlsxPath -WorksheetName "Channel_Users" -TableStyle Medium16  -AutoSize

Get-Team | foreach {
   $nickName = $_.MailNickName
   Get-Team -MailNickName $nickName | Select -Property * |
	Foreach {
		[PSCustomObject]@{
			'Teams ID' = $_.GroupId;
			'Teams Display Name' = $_.DisplayName;
                        'Teams MailNickName' = $nickName;
                        'Giphy Allowed?' = $_.AllowGiphy;
                        'Giphy Content Rating' = $_.GiphyContentRating;
                        'Allow Stickers And Memes' = $_.AllowStickersAndMemes;
                        'Allow Custom Memes' = $_.AllowCustomMemes;
                        'Allow Guest to Create & Update Channels' = $_.AllowGuestCreateUpdateChannels;
                        'Allow Guest to Delete Channels' = $_.AllowGuestDeleteChannels;
                        'Allow Members to Create & Update Channels' = $_.AllowCreateUpdateChannels;
                        'Allow Members to Create Private Channels' = $_.AllowCreatePrivateChannels;
                        'Allow Members to Delete Channels' = $_.AllowDeleteChannels;
                        'Allow Members to Add & Remove Apps'= $_.AllowAddRemoveApps;
                        'Allow Members to Create Update Remove tabs' = $_.AllowCreateUpdateRemoveTabs;
                        'Allow Members to Create Update Remove Connectors' = $_.AllowCreateUpdateRemoveConnectors;
                        'Allow Members to Edit Messages' = $_.AllowUserEditMessages;
                        'Allow Members to Delete Messages' = $_.AllowUserDeleteMessages;
                        'Allow Owner to Delete Messages' = $_.AllowOwnerDeleteMessages;
                        'Allow Team Mentions' = $_.AllowTeamMentions;
                        'Allow Channel Mentions' = $_.AllowChannelMentions;
                        'Show In Teams Search & Suggestions' = $_.ShowInTeamsSearchAndSuggestions
		}
    }
} | Export-Excel -Path $xlsxPath -WorksheetName "Teams_permissions" -TableStyle Medium16 -AutoSize

Hope this script was helpful in determining the current state of your Teams deployment.

Thank you for stopping by. ✌

M365 – Manage Group Creation Permission

All users can create M365 groups, this is the option enabled by default. Microsoft probably took this approach so as to make sure users can collaborate without any IT assistance.

This is good but when it comes to start managing Teams and the related resources that get created, it can easily become an IT data governance nightmare. If your organization is in its initial phases of Teams rollout, IMO it is better to disable group creation ability for the masses and preferable do a phased approach.

When we disable M365 group creation, it affects all services that rely on groups for access, including:

  • Outlook
  • SharePoint
  • Microsoft Teams
  • Microsoft Stream
  • Yammer
  • Planner
  • Power BI (classic)
  • Project for the web

To have a solution that is sort of a best of both worlds scenario, we can designate an Azure AD group with specific users who have the permissions to create M365 groups.

Create an Azure AD Group

To create a new Azure AD group, the New-AzureADGroup cmdlet can be used or can also be created from the Azure AD admin portal. I’m naming the group ‘M365 – Group Creators’

New-AzureADGroup -DisplayName "M365 - Group Creators" -Description "Group that allows users to create M365 groups" -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet"
New Azure AD group

Keep in mind this doesn’t prevent users with Azure AD admin roles which has group creation capabilities from creating new groups.

Set Group Creators

The following needs to be run from PowerShell. Make sure AzureADPreview is installed and connected.

Install-module AzureADPreview
Import-Module AzureADPreview
Connect-AzureAD

Run the following commands,

$Template = Get-AzureADDirectorySettingTemplate | where {$_.DisplayName -eq 'Group.Unified'}
$Setting = $Template.CreateDirectorySetting()
New-AzureADDirectorySetting -DirectorySetting $Setting
‘..already exists’ error

If you get an ‘..already exists’ error, that means your tenant already this setting defined. Proceed with the next steps below,

$Setting = Get-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id
$Setting["EnableGroupCreation"] = $False
$Setting["GroupCreationAllowedGroupId"] = (Get-AzureADGroup -SearchString "<Name of your security group>").objectid
EnableGroupCreation and GroupCreationAllowedGroupId

Use the Set-AzureADDirectorySetting below to set the value in the $Setting variable which has the object ID of the Azure AD group.

Set-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id -DirectorySetting $Setting

To determine if the group is allowed to create to groups,

(Get-AzureADDirectorySetting).Values
verify settings

Only one group can be used to control the ability to create Microsoft 365 Groups. But, other groups can be nested as members of this group.

In case your organization wants to revert back this setting in the future, you can do so by changing $AllowGroupCreation to “True” and the group value to “”

$Setting = Get-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id
$Setting["EnableGroupCreation"] = $true
$Setting["GroupCreationAllowedGroupId"] = ""
Set-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id -DirectorySetting $Setting
(Get-AzureADDirectorySetting).Values
Enable group creation

Usually the settings takes 30ish minutes to take effect. You can verify this by trying to create a group with a user who is a non-member of the allowed Azure AD group.

If a user who is part of the group creators can’t create a M365 group, it’s worth checking the OWA policy. The Get-OwaMailboxPolicy can be used to check this,

Get-OwaMailboxPolicy | Select GroupCreationEnabled

If the above output shows ‘False’, you can enable this by using the Set-OwaMailboxPolicy cmdlet,

Set-OwaMailboxPolicy -Identity "Name of your OWA Policy" -GroupCreationEnabled $true

Hope this helped you in setting up the policies to disable M365 group creation.

Thank you for stopping by.✌

O365 – Create Distribution Groups using PowerShell

In this post, I’ll go through the steps to create distribution groups in O365 using PowerShell.

Before proceeding further make sure you are connected to Exchange Online,

$o365cred = Get-Credential
Connect-ExchangeOnline -credential $o365cred

To create a mail-enabled security group named Managers without specifying any members:

$Name = Read-Host "Enter a name for the DistributionGroup"
New-DistributionGroup -Type "Security" -Name $Name -DisplayName $Name -Alias $Name

To create a mail-enabled security group named Managers with members:

Note: -Member is a ‘MultiValuedProperty’ and as we input users comma seperated, we need to split the (comma-separated) string to get an actual array.

$Name = Read-Host "Enter a name for the DistributionGroup"
$Members = Read-Host "Enter email addresses seperated by comma"
$members = $members -split ' *, *'
New-DistributionGroup -Type "Security" -Name $Name -DisplayName $Name -Alias $Name -Members $Members

To add multiple members to an existing Distribution Group:

$Name = Read-Host "Enter DistributionGroup name to add members"
$Members = "user01@domain.onmicrosoft.com","user01@domain.onmicrosoft.com"
$Members | ForEach-Object { Add-DistributionGroupMember -Identity $Name -Member $_}

To import members from a csv and add to an existing Distribution Group:

$Name = Read-Host "Enter DistributionGroup name to add members"
Import-csv "C:\tmp\members.csv" | ForEach-Object {
Add-DistributionGroupMember -Identity $Name -Member $_.member
}

To determine existing distribution group members for a distribution group:

To set distribution group to accept messages from authenticated (internal) and unauthenticated (external) senders.

Note: If you don’t specify this parameter while creating the distribution group, the default value is set to ‘true’ meaning messages from unauthenticated (external) senders are rejected.

$Name = Read-Host "Enter DistributionGroup's name to allow external senders"
Set-DistributionGroup -Identity $Name -RequireSenderAuthenticationEnabled $false

To change an existing distribution group’s name:

$Name = Read-Host "Enter name of existing group to be renamed" 
$NewName = Read-Host "Enter new name" 
Set-DistributionGroup -Identity $Name -Name $NewName -DisplayName $NewName -Alias $NewName

Thank you for stopping by. ✌

O365 – Prevent Users from Signing Up for Trials and Purchasing Their Own Pro license

I’ve updated this post with the newer MSCommerce PowerShell module

Self-service is a great idea in several instances but in my opinion when it comes to users signing up for trials and purchasing licenses, it seems to be causing unexpected issues especially while answering to the purchasing team.

Most organizations these days have procurement processes in place to meet regulatory, security, compliance and governance needs. And the need to ensure that all licenses are approved and managed according to defined processes is very important. And when you take expenses, privacy or security into consideration, it is a good idea to disable self-service sign-up and purchases.

To disable all self-service sign-ups

This can be achieved using the MSOL PowerShell module. Type below command to check current settings:

Get-MsolCompanyInformation | fl AllowAdHocSubscriptions
current settings

To disable all self-service sign-ups,

Set-MsolCompanySettings -AllowAdHocSubscriptions:$false

When users try to sign-up, they’ll see the below message,

Your IT department has turned off signup

Prevent users from purchasing their own Pro license

To install and connect the MSCommerce module, start PowerShell as an administrator to install the module,

Install-Module -Name MSCommerce
Import-Module -Name MSCommerce
Connect-MSCommerce
Get-Command *-mscommerce*

To determine current setting,

Get-MSCommercePolicy -PolicyId AllowSelfServicePurchase
Get-MSCommercePolicy

View a list of all available self-service purchase products and the status,

Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase
Get-MSCommerceProductPolicies

Disable the policy setting for a specific product, Below example: ‘Power BI Pro’

Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId CFQ7TTC0L3PB -Enabled $False

Below is an example script on how your can disable AllowSelfServicePurchase by getting the ProductID for ‘Power BI Pro’

$p = Read-Host "Enter product name"
$product = Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase | where {$_.ProductName -match $p }
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $product.ProductID -Enabled $false
Update-MSCommerceProductPolicy

In scenarios where there are more than one values for the product, Below example: ‘Power Automate’

$p = Read-Host "Enter product name"
$product = Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase | where {$_.ProductName -match $p }
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $product[0].ProductID -Enabled $false
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $product[1].ProductID -Enabled $false
Update-MSCommerceProductPolicy # more than one value

To disable AllowSelfServicePurchase for all products,

Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase | ? {$_.PolicyValue -eq "Enabled" } | ForEach {Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId $_.ProductId -Enabled $False }
To disable for all products

Thanks for stopping by.✌

O365 – Determine Mailbox Folder Size from Exchange Online using PowerShell

I’m sure all Exchange Online environments have users who are data hoarders in their email environment. Most tenants have policies to limit how big the mailbox can be for various reasons. Users may come back with increasing mailbox sizes so they can hoard more of those outdated data. It is good practice to maintain the folder items. Get-MailboxFolderStatistics helps retrieve information about folders in mailboxes, including number and size of items in the folder and other information.

Before proceeding, make sure you are connected to Exchange Online module,

$credential = Get-Credential
Connect-ExchangeOnline -credential $credential

To determine a folders from a user’s mailbox, the folder size and the number of items in the folders,

$EmailId = Read-Host "Enter user's email address"
Get-MailboxFolderStatistics -Identity $EmailId | Select Name,FolderSize,ItemsinFolder

To determine individual folders and subfolder sizes of a specific user:

$EmailId = Read-Host "Enter user's email address"
Get-MailboxFolderStatistics -Identity $EmailId | Select Name,FolderAndSubfolderSize,ItemsInFolderAndSubfolders

To determine inbox folders statistics of a specific user:

$EmailId = Read-Host "Enter user's email address"
Get-MailboxFolderStatistics -Identity $EmailId -FolderScope Inbox | Format-Table Identity,ItemsInFolderAndSubfolders,FolderAndSubfolderSize -AutoSize

To determine and display inbox folder sizes for all mailboxes in the organization

$All = Get-Mailbox -ResultSize Unlimited
$All | foreach {Get-MailboxFolderStatistics -Identity $_.Identity -FolderScope Inbox} | Select Identity,ItemsInFolderAndSubfolders,FolderAndSubfolderSize | ft -AutoSize

and export to CSV file:

$Allmbx = Get-Mailbox -ResultSize Unlimited
$Allmbx | foreach {Get-MailboxFolderStatistics -Identity $_.Identity -FolderScope Inbox | Select Identity,ItemsInFolderAndSubfolders,FolderAndSubfolderSize | Export-Csv "C:\tmp\Inbox_data.csv" -NoTypeInformation -Append}

Get-MailboxFolderStatistics returns IPM subtree folders. This folder structure consists of messages between recipients(Inbox, Sent Items). In the Exchange Online, the Non-IMP subtree is quite larger, as different O365 applications have been using mailboxes to store and process data. Teams, Delve, MyAnalytics, all have their own folders or folder trees inside the Non-IPM root.

To determine Non-IPM subtree folders and their sizes:

$EmailId = Read-Host "Enter user's email address"
Get-MailboxFolderStatistics -Identity $EmailId -FolderScope NonIpmRoot | Format-Table Identity,ItemsInFolderAndSubfolders,FolderAndSubfolderSize -AutoSize

Thank you for stopping by. ✌