5 min read
Tip #3: Utilizing external config data in PowerShell (1/4)
Tobias & Aleksandar's tip #3:
The two very well-known PowerShell experts have teamed up to share their best and most...
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.
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.
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 MicrosoftTeams
Get-Module MicrosoftTeams
Again, compare the version number again what is available (here).
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.
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
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:
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 count
Get-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).Count
Get-Team |
Foreach-Object {$Channels = (Get-TeamChannel -GroupID $_.GroupID).Count;$TeamsChannelCount=$TotalChannels+$Channels}
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).Count
Get-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 change
If ($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.csv
Compare-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.csv
Rename-Item CurrentTeams.csv HistoricalTeams.csv
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-CsTeamsGuestCallingConfiguration
Get-CsTeamsGuestMeetingConfiguration
Get-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.
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:
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.csv
Compare-Object $CSV1 $CSV2
If a difference is detected, we should see something like this:
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.csv
Compare-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'.
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.
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,AllowTranscription
07.2023,True,SingleApplication,True,DisabledUserOverride,False
08.2023,True,SingleApplication,False,DisabledUserOverride,False
09.2023,True,SingleApplication,False,DisabledUserOverride,False
10.2023,True,SingleApplication,True,DisabledUserOverride,False
11.2023,True,SingleApplication,True,DisabledUserOverride,False
12.2023,True,SingleApplication,False,DisabledUserOverride,False
01.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.
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 events
Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams
# Teams channels added
Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams -Operations ChannelAdded
# Sensitivity Label changes
Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType MicrosoftTeams -Operations SensitivityLabelChanged
# Teams deleted
Search-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:
TeamsSessionStarted
MeetingParticipantDetail
MessageReadReceiptReceived
ReactedToMessage
MeetingDetail
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.
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.
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!
Sep 4, 2024 by Dr. Tobias Weltner and Aleksandar Nikolić
The two very well-known PowerShell experts have teamed up to share their best and most...
Sep 4, 2024 by Aleksandar Nikolić and Dr. Tobias Weltner
The two very well-known PowerShell experts have teamed up to share their best and most...
Sep 4, 2024 by Dr. Tobias Weltner and Aleksandar Nikolić
The two very well-known PowerShell experts have teamed up to share their best and most...
Damian Scoles is a ten-time Microsoft MVP specializing in Exchange, Office 365 and PowerShell who has 25 years of IT industry experience. He is based in the Chicago area and started out managing Exchange 5.5 and Windows NT. Over the years he has worked with Office 365 since BPOS and his experience has grown to include Azure AD, Security and Compliance Admin Centers, and Exchange Online. His community outreach includes contributing to TechNet forums, creating PowerShell scripts that can be found on his blogs, writing in-depth PowerShell / Office365 / Exchange blog articles, tweeting, and creating PowerShell videos on YouTube. He has written five PowerShell books and is also actively working on the book "Microsoft 365 Security for IT Pros".