Do you want to learn more about monitoring Microsoft Teams with PowerShell? Read how to check settings over time to check for changes. Prevent (or solve) configuration drift.
Where to start
Scope creep, configuration creep and Teams sprawl all have a common root problem lack of monitoring or controls to prevent these changes. In this article, we will discuss how we can use PowerShell to monitor Teams, their configurations and determine changes that have occurred. Due to the size and complexity of Teams, we will not cover each and every configuration aspect but set administrators of for success by walking through some examples of how to use PowerShell. An inspiration for this is situated in the Security center known as the Configuration Analyzer which has a Drift Analysis and History tab.
Starting point
As with any PowerShell script or tool usage, make sure that your Microsoft Teams PowerShell module is running the latest version:
Update-Module MicrosoftTeams
Then close your PowerShell session as this ensures the next time the PowerShell module is loaded, it will be the latest version.
Validate your version is the latest:
Import-Module MicrosoftTeamsGet-Module MicrosoftTeams
Again, compare the version number again what is available (here).
What cmdlets to use
Building a custom script to audit a Microsoft Teams configuration can be done purely with Get-* cmdlets as these are discovery PowerShell cmdlets, querying settings and objects and providing needed details. A quick list of these can be found with this one-liner (once connected to Microsoft Teams PowerShell endpoint):
Get-Command Get-* | Where Source -eq MicrosoftTeams | Sort Name | ft Name
We won't review each and every one of the cmdlets available as there are over 150 of them, but we will review some select examples to assist in discovering important items in Teams for an 'audit' of settings.
Microsoft Teams and Teams channels
List of teams
Keeping track of the number and types of teams is an important aspect of auditing a Teams configuration as Teams can sprawl beyond what an organization intended to create or maintain. By validating team counts we can see the growth and usage before the numbers get out of hand. First, querying the number of teams:
(Get-Team).Count
Which should display a similar result:
Get-Team | ft DisplayName,Visibility,Archived,GroupId
List of team channels
Each team could have one or more channels to keep track of:
Get-Team | ForEach-Object {Write-Host $_.DisplayName -ForegroundColor Yellow ;Get-TeamChannel -GroupID $_.GroupID | ft DisplayName,Membership*,Description}
Using the previous PowerShell cmdlets above, we were able to gather a lot of data and settings about our Teams environment. How do we use this data to truly audit an environment? For one, we can monitor Teams and Teams Channel sprawl. In the below example, we check Teams and Teams channels every month to see what has changed:
Example
When the number of Teams and Channels is first checked there may be 35 teams and 56 channels and then when the cmdlets are run again, perhaps a month later, these numbers may increase to 40 and 65. We can pull the initial counts like so:
# Team count(Get-Team).Count# Teams channel countGet-Team | Foreach-Object {$Channels = (Get-TeamChannel -GroupID $_.GroupID).Count;$TeamsChannelCount=$TotalChannels+$Channels}
Later, on a scheduled basis, we can programmatically check these numbers again:
$TeamsCount = (Get-Team).CountGet-Team | Foreach-Object {$Channels = (Get-TeamChannel -GroupID $_.GroupID).Count;$TeamsChannelCount=$TotalChannels+$Channels}
Report real world example
When we want to start auditing the number of Teams and Teams Channels, we need a way to store counts and compare these counts against the current numbers in Teams.
First Run:
# Date column$CurrentMonth = Get-Date -Format "MM.yyyy"# File header"Date,TeamsCount,TeamsChannelCount" | Out-File 'TeamsAndChannelCounts.csv'# First month counts$TeamsCount = (Get-Team).CountGet-Team | Foreach-Object {$Channels = (Get-TeamChannel -GroupID $_.GroupID).Count;$TeamsChannelCount=$TotalChannels+$Channels}$Output = "$CurrentMonth,$TeamsCount,$TeamsChannelCount" | Out-File 'TeamsAndChannelCounts.csv' -Append
Scheduled runs where we have data lines which can be examined for differences:
# Import data$TeamsData = Import-Csv .\TeamsAndChannelCounts.csv# Last line$CurrentMonth = $TeamsData[-1]# Next to last line to compare$LastMonth = $TeamsData[-2]# Check each Teams data point for changeIf ($CurrentMonth.TeamsCount -gt $LastMonth.TeamsCount) {Write-Host 'There are new Teams present.'}If ($CurrentMonth.TeamsChannelCount -gt $LastMonth.TeamsChannelCount) {Write-Host 'There are new Teams Channels present.'}
If we need to dig into the weeds a bit, we can also check which teams are new since the last time as well. In order to do that, we can run this the first time:
Get-Team | select DisplayName,Visibility,GroupId,Archived,Description | Export-Csv -Path HistoricalTeams.csv
Each month we can check for changes:
Get-Team | select DisplayName,Visibility,GroupId,Archived,Description | Export-Csv -Path CurrentTeams.csv
Then we can compare these two files with Compare-Object:
$Historical = Get-Content .\HistoricalTeams.csv$Current = Get-Content .\CurrentTeams.csvCompare-Object -ReferenceObject $Historical -DifferenceObject $Current
We see in this example that there are two different groups, which are new as the SideIndicator value is pointing to the file data in the $Current variable.
One more step we can perform with PowerShell and that to replace the Historical Teams files with the current one so that we have the latest counts to compare at all times:
Remote-Item HistoricalTeams.csvRename-Item CurrentTeams.csv HistoricalTeams.csv
Guest access
Within teams there are controls for what your Guest has access to, and we can control these settings with PowerShell. Monitoring Teams configuration aspects for Guest users is an import aspect of security for your Teams configuration in general. As such we can explore this with PowerShell to see what we can discover and monitor.
First the PowerShell cmdlets:
Get-Command Get-*team*guest*
Which provides these cmdlets:
Get-CsTeamsGuestCallingConfigurationGet-CsTeamsGuestMeetingConfigurationGet-CsTeamsGuestMessagingConfiguration
Each one of these will reveal a different aspect of the configuration. Using PowerShell, we can document and then compare the settings for changes, just like we did with Teams and Channels.
Guest calling configuration
First, let's break down the basic Guest Calling Configuration:
$CurrentMonth = Get-Date -Format "MM.yyyy"$GuestCallingCfg = (Get-CsTeamsGuestCallingConfiguration).AllowPrivateCalling$FileHeader = "Date,AllowPrivateCalling" | Out-File 'GuestCallingConfiguration.csv'$Output = "$CurrentMonth,$GuestCallingCfg" | Out-File 'GuestCallingConfiguration.csv' -append
There is only one value of importance here. We can verify this has no changes over time and we would have to run this monthly, quarterly or so on:
$CurrentMonth = Get-Date -Format "MM.yyyy"$GuestCallingCfg = (Get-CsTeamsGuestCallingConfiguration).AllowPrivateCalling$FileHeader = "Date,AllowPrivateCalling" | Out-File 'GuestCallingConfiguration.csv'$Output = "$CurrentMonth,$GuestCallingCfg" | Out-File 'GuestCallingConfiguration-current.csv' -append
Then we can compare the two output files like so:
$Historical = Import-Csv GuestCallingConfiguration.csv$Current = Import-Csv GuestCallingConfiguration-current.csv$Historical.AllowPrivateCalling -eq $Current.AllowPrivateCalling
If a change occurs, the result would look like so:
Guest meeting configuration
Next up, we have the Guest Meeting Configuration which contains a few more settings to manage. First, we need to create a baseline or discovery what we have configured today:
First Run:
# Discover current configuration $GuestMeeting = Get-CsTeamsGuestMeetingConfiguration | Select AllowIPVideo,ScreenSharingMode,AllowMeetNow,LiveCaptionsEnabledType,AllowTranscription# Create a Hash table output to be exported $Hashtable = New-Object System.Collections.Hashtable$Hashtable['AllowIPVideo'] = $GuestMeeting.AllowIPVideo$Hashtable['ScreenSharingMode'] = $GuestMeeting.ScreenSharingMode$Hashtable['AllowMeetNow'] = $GuestMeeting.AllowMeetNow$Hashtable['LiveCaptionsEnabledType'] = $GuestMeeting.LiveCaptionsEnabledType$Hashtable['AllowTranscription'] = $GuestMeeting.AllowTranscription# Export $Hashtable variable to a CSV for comparison $HashTable.GetEnumerator() | Select-Object -Property Key,Value | Export-Csv -NoTypeInformation -Path GuestMeeting.csv
Then, over time, we can make subsequent Runs [Monthly, Quarterly, Yearly] which will use the same code above, but change the output file to 'GuestMeeting-Current.csv':
$HashTable.GetEnumerator() | Select-Object -Property Key,Value | Export-Csv -NoTypeInformation -Path GuestMeeting-Current.csv
Comparing the two configurations:
# Compare the original and current CSV files$CSV1 = Get-Content .\GuestMeeting.csv$CSV2 = Get-Content .\GuestMeeting-Current.csvCompare-Object $CSV1 $CSV2
If a difference is detected, we should see something like this:
Guest messaging configuration
The same process can also be performed with the Guest Messaging Configuration. For our first run, we can use this code:
# Discover current configuration $GuestMessaging = Get-CsTeamsGuestMessagingConfiguration | Select AllowUserEditMessage,AllowUserDeleteMessage,AllowUserDeleteChat,AllowUserChat,AllowGiphy,GiphyRatingType,AllowMemes,AllowImmersiveReader,AllowStickers# Create a Hash table output to be exported$Hashtable = New-Object System.Collections.Hashtable$Hashtable['AllowUserEditMessage'] = $GuestMessaging.AllowUserEditMessage$Hashtable['AllowUserDeleteMessage'] = $GuestMessaging.AllowUserDeleteMessage$Hashtable['AllowUserDeleteChat'] = $GuestMessaging.AllowUserDeleteChat$Hashtable['AllowUserChat'] = $GuestMessaging.AllowUserChat$Hashtable['AllowGiphy'] = $GuestMessaging.AllowGiphy$Hashtable['GiphyRatingType'] = $GuestMessaging.GiphyRatingType$Hashtable['AllowMemes'] = $GuestMessaging.AllowMemes$Hashtable['AllowImmersiveReader'] = $GuestMessaging.AllowImmersiveReader$Hashtable['AllowStickers'] = $GuestMessaging.AllowStickers# Export $Hashtable variable to a CSV for comparison $HashTable.GetEnumerator() | Select-Object -Property Key,Value | Export-Csv -NoTypeInformation -Path GuestMessaging.csv
Subsequent Runs: [Monthy, Quarterly, Yearly]
Use the same code above, but change the output file to 'GuestMessaging-Current.csv':
$HashTable.GetEnumerator() | Select-Object -Property Key,Value | Export-Csv -NoTypeInformation -Path GuestMessaging-Current.csv
Comparison:
# Compare the original and current CSV files $CSV1 = Get-Content .\GuestMessaging.csv$CSV2 = Get-Content .\GuestMessaging-Current.csvCompare-Object $CSV1 $CSV2
If a difference is detected, we should see something like this:
Note in the above comparison, two values have changed: AllowMemes and AllowStickers, and by the indicators (==>), the new configuration is now 'True' for both settings whereas the original configuration (<==) had both set to 'False'.
Comments so far
With any auditing process, organizations need to decide what is important to keep an eye on and this is true of Teams Configurations. While we could cover all settings in this blog article, that is not the intent and instead we've provided the tools you need to build your own audit process. A complete coverage of all settings would make this article too long and would mostly be a repetitive process of covering the settings, an entirely unnecessary process.
Additional tasks historical data
If there is a need to track the configuration over time, we could also create historical charts for review. The way to do this is to export the current settings and record a date/timestamp for when this data set was true. A sample file could look something like this:
Month,AllowIPVideo,ScreenSharingMode,AllowMeetNow,LiveCaptionsEnabledType,AllowTranscription07.2023,True,SingleApplication,True,DisabledUserOverride,False08.2023,True,SingleApplication,False,DisabledUserOverride,False09.2023,True,SingleApplication,False,DisabledUserOverride,False10.2023,True,SingleApplication,True,DisabledUserOverride,False11.2023,True,SingleApplication,True,DisabledUserOverride,False12.2023,True,SingleApplication,False,DisabledUserOverride,False01.2024,True,SingleApplication,True,DisabledUserOverride,False
Depending on an organizations internal process, this could be placed on a central IT share, SharePoint, or perhaps emails to a Distribution List for awareness.
Teams action auditing
A common task administrators perform is task auditing, which involves looking at logs and other data to determine a root cause for an issue and fix the issue. Additionally, auditing can also be used to query an activity that Microsoft Teams performs. This may assist in either determining general activity levels, or possibly when a particular service was used. In the background, Microsoft utilizes a Unified Log that stores information on actions taken on the Teams platform and there are two ways to retrieve this information: PowerShell and the Microsoft Defender interface.
Why do this in PowerShell? PowerShell provides flexibility that you may not get in the GUI. We can schedule this within either a Jumpbox that runs PowerShell scripts on-premises, or we can do this in an Azure Runbook to minimize assets on premises. Or we can do this within ScriptRunner as part of a set of management tasks.
# First, connect to Exchange Online PowerShell (v3)Connect-ExchangeOnline# Retrieve Teams Events - stored in the Unified Audit Log# Timeframe: Past 48 hours$EndDate = Get-Date$StartDate = $EndDate.AddDays(-2)# All Teams eventsSearch-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams# Teams channels addedSearch-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams -Operations ChannelAdded# Sensitivity Label changesSearch-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams -Operations SensitivityLabelChanged# Teams deletedSearch-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams -Operations TeamDeleted
For reference (Microsoft Learn pages): Teams activities
When we run these cmdlets, we should see output like so:
Note that this is only one of many entries and happens to be a logged start of a Teams session.
Beyond these operations, we also have additional Teams operations we call pull data for:
TeamsSessionStartedMeetingParticipantDetailMessageReadReceiptReceivedReactedToMessageMeetingDetail
Conclusion
PowerShell provides administrators with the capability of monitoring their own environments when other tools do not exist. In this case, we were able to use PowerShell to check settings over time to check for changes, making sure there is no configuration drift, or if there is, being able to take a rough date stamp to see when the change was made. With the proper coding, all configuration aspects of Microsoft Teams can be monitored over time with PowerShell which allows administrators to concentrate on other tasks.
Microsoft Teams cheat sheet
Simplify and automate your Microsoft Teams management with our 8-page PowerShell cheat sheet for Microsoft Teams. This handy guide provides you with quick-reference cmdlets and code snippets to manage users, channels, policies and more with ease. Ideal for both beginners and seasoned administrators.
Your ultimate PowerShell Cheat Sheet
Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.The poster is available for download and in paper form.
Get your PowerShell goodies here!
And do you know our webinars?
Transform your Microsoft Teams management with PowerShell
Managing Teams can be pretty time consuming. Have you ever wondered how you could optimize and reduce the arising workload?The PowerShell module for Microsoft Teams is your key: It allows you to standardize and automate many repetitive tasks. For example:
- Pushing messages to Teams channels
- Finding teams with no owners
- Bulk creation/deletion/archiving of teams
- Adding/removing channels/users in all/selected teams
- Managing security settings in all/selected teams
- Creating reports of your current Teams infrastructure
This webinar is aimed at administrators, IT and DevOps professionals, PowerShell developers and IT managers.
We'll show you in this webinar:
- The options of the current Teams PowerShell module
- How ScriptRunner allows you to save time by delegating tasks to helpdesk teams and even end-users
- Take a look at our ready-to-use PowerShell scripts
- The centralized management of all PowerShell components like scripts, modules, credentials
- How you can automatically turn every script into an easy-to-use web form
We look forward to welcoming you as a webinar participant!
Click here for the Teams webinar!
Related links
- Get your essential Microsoft Teams PowerShell Cheat Sheet here
- Get your PowerShell goodies here. Accelerate your IT automation journey.
- Our webinars recurringly include Teams: Transform your Microsoft Teams management with PowerShell
- For Reference, Microsoft | Learn pages: Teams activities
- Security center, Configuration Analyzer
- ScriptRunner: Try out our software for free and download the trial version