Audit Teams Meetings and Export Meeting Attendance Report

Audit Teams Meetings and Export Meeting Attendance Report
This commit is contained in:
AdminDroid 2025-03-05 15:13:36 +05:30
parent 3f706c5db1
commit 208dd665a4

View File

@ -1,21 +1,32 @@
<# <#
============================================================================================= =============================================================================================
Name: Audit Microsoft Teams meetings using PowerShell Name: Audit Microsoft Teams meetings and export teams meeting attendance report using PowerShell
Version: 2.0
Description: This script exports Teams meeting report and attendance report into 2 CSV files. Description: This script exports Teams meeting report and attendance report into 2 CSV files.
website: o365reports.com website: o365reports.com
Script Highlights: Script Highlights:
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
1. The script uses modern authentication to connect to Exchange Online. 1. Exports a list of Teams meetings.
2. The script can be executed with MFA enabled account too. 2. Exports Teams meeting attendance report.
3. Exports report results to CSV file. 3. Exports report results to CSV file.
4. Allows you to generate a Teams meetings audit log. 4. The script can be executed with MFA enabled account too.
5. Exports Teams meeting attendance report. 5. Supports certificate-based authentication (CBA) too.
6. Helps to generate reports for custom periods. 6. Helps to generate reports for custom periods.
7. Automatically installs the EXO V2 and AzureAD module (if not installed already) upon your confirmation. 7. Automatically installs the EXO PowerShell module (if not installed already) upon your confirmation.
8. The script is scheduler-friendly. I.e., Credential can be passed as a parameter instead of saving inside the script. 8. The script is scheduler-friendly.
For detailed Script execution: https://o365reports.com/2021/12/08/microsoft-teams-meeting-attendance-report For detailed Script execution: https://o365reports.com/2021/12/08/microsoft-teams-meeting-attendance-report
Change Log:
~~~~~~~~~~~
V1.0 (Dec 09, 2021) - File created
V1.1 (Sep 28, 2023) - Minor usability improvements.
V2.0 (Mar 05, 2025) - Added certificate-based authentication support to enhance scheduling capability.
Removed Azure AD PowerShell module dependency.
Increased audit log retrieval range from 90 to 180 days
Usability improvements
============================================================================================ ============================================================================================
#> #>
@ -24,83 +35,82 @@ Param
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[Nullable[DateTime]]$StartDate, [Nullable[DateTime]]$StartDate,
[Nullable[DateTime]]$EndDate, [Nullable[DateTime]]$EndDate,
[switch]$NoMFA,
[string]$UserName, [string]$UserName,
[string]$Password [string]$Password,
[string]$Organization,
[string]$ClientId,
[string]$CertificateThumbprint
) )
Function Connect_Modules #Getting StartDate and EndDate for Audit log
if ((($StartDate -eq $null) -and ($EndDate -ne $null)) -or (($StartDate -ne $null) -and ($EndDate -eq $null)))
{ {
#Check for EXO v2 module inatallation Write-Host `nPlease enter both StartDate and EndDate for Audit log collection -ForegroundColor Red
$Module = Get-Module ExchangeOnlineManagement -ListAvailable exit
}
elseif(($StartDate -eq $null) -and ($EndDate -eq $null))
{
$StartDate=(((Get-Date).AddDays(-180))).Date
$EndDate=Get-Date
}
else
{
$StartDate=[DateTime]$StartDate
$EndDate=[DateTime]$EndDate
if($StartDate -lt ((Get-Date).AddDays(-180)))
{
Write-Host `nAudit log can be retrieved only for past 180 days. Please select a date after (Get-Date).AddDays(-180) -ForegroundColor Red
Exit
}
if($EndDate -lt ($StartDate))
{
Write-Host `nEnd time should be later than start time -ForegroundColor Red
Exit
}
}
#Check for EXO module inatallation
$Module = Get-Module ExchangeOnlineManagement -ListAvailable
if($Module.count -eq 0) if($Module.count -eq 0)
{ {
Write-Host Exchange Online PowerShell V2 module is not available -ForegroundColor yellow Write-Host Exchange Online PowerShell module is not available -ForegroundColor yellow
$Confirm= Read-Host Are you sure you want to install module? [Y] Yes [N] No $Confirm= Read-Host Are you sure you want to install module? [Y] Yes [N] No
if($Confirm -match "[yY]") if($Confirm -match "[yY]")
{ {
Write-host "Installing Exchange Online PowerShell module" Write-host "Installing Exchange Online PowerShell module"
Install-Module ExchangeOnlineManagement -Repository PSGallery -AllowClobber -Force Install-Module ExchangeOnlineManagement -Repository PSGallery -AllowClobber -Force -Scope CurrentUser
Import-Module ExchangeOnlineManagement
} }
else else
{ {
Write-Host EXO V2 module is required to connect Exchange Online.Please install module using Install-Module ExchangeOnlineManagement cmdlet. Write-Host EXO module is required to connect Exchange Online.Please install module using Install-Module ExchangeOnlineManagement cmdlet.
Exit Exit
} }
} }
#Check for Azure AD module Write-Host Connecting to Exchange Online...
$Module = Get-Module AzureAD -ListAvailable #Storing credential in script for scheduling purpose/ Passing credential as parameter - Authentication using non-MFA account
if($Module.count -eq 0) if(($AdminName -ne "") -and ($Password -ne ""))
{
Write-Host Azure AD module is not available -ForegroundColor yellow
$Confirm= Read-Host Are you sure you want to install the module? [Y] Yes [N] No
if($Confirm -match "[yY]")
{
Write-host "Installing AzureAD PowerShell module"
Install-Module AzureAD -Repository PSGallery -AllowClobber -Force
}
else
{
Write-Host AzureAD module is required to generate the report.Please install module using Install-Module AzureAD cmdlet.
Exit
}
}
#Authentication using non-MFA
if($NoMFA.IsPresent)
{
#Storing credential in script for scheduling purpose/ Passing credential as parameter
if(($UserName -ne "") -and ($Password -ne ""))
{ {
$SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force $SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force
$Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword $Credential = New-Object System.Management.Automation.PSCredential $AdminName,$SecuredPassword
Connect-ExchangeOnline -Credential $Credential -ShowBanner:$false
}
elseif($Organization -ne "" -and $ClientId -ne "" -and $CertificateThumbprint -ne "")
{
Connect-ExchangeOnline -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -Organization $Organization -ShowBanner:$false
} }
else else
{ {
$Credential=Get-Credential -Credential $null Connect-ExchangeOnline -ShowBanner:$false
} }
Write-Host "Connecting Azure AD..."
Connect-AzureAD -Credential $Credential | Out-Null
Write-Host "Connecting Exchange Online PowerShell..."
Connect-ExchangeOnline -Credential $Credential
}
#Connect to Exchange Online and AzureAD module using MFA
else
{
Write-Host "Connecting Exchange Online PowerShell..."
Connect-ExchangeOnline
Write-Host "Connecting Azure AD..."
Connect-AzureAD | Out-Null
}
}
Function Get_TeamMeetings Function Get_TeamMeetings
{ {
$Result="" $Result=""
$Results=@() $Results=@()
$Count=0 Write-Host `nRetrieving Teams meeting details from $StartDate to $EndDate...
Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations "MeetingDetail" -ResultSize 5000 | ForEach-Object { Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations "MeetingDetail" -ResultSize 5000 | ForEach-Object {
$Count++ $Global:MeetingCount++
Write-Progress -Activity "`n Retrieving Teams meetings data from $StartDate to $EndDate.."`n" Processed Teams meetings count: $Count" Write-Progress -Activity "`n Retrieving Teams meetings data from $StartDate to $EndDate.."`n" Processed Teams meetings count: $Count"
$AuditData=$_.AuditData | ConvertFrom-Json $AuditData=$_.AuditData | ConvertFrom-Json
$MeetingID=$AuditData.ID $MeetingID=$AuditData.ID
@ -113,11 +123,18 @@ Function Get_TeamMeetings
$Results= New-Object PSObject -Property $Result $Results= New-Object PSObject -Property $Result
$Results | Select-Object 'Meeting id','Created By','Start Time','End Time','Meeting Type','Meeting Link','More Info' | Export-Csv -Path $ExportCSV -Notype -Append $Results | Select-Object 'Meeting id','Created By','Start Time','End Time','Meeting Type','Meeting Link','More Info' | Export-Csv -Path $ExportCSV -Notype -Append
} }
Write-Host $Count meetings details are exported. -ForegroundColor Green if($MeetingCount -ne 0)
{
Write-Host $Global:MeetingCount meetings details are exported.
}
else
{
Write-Host "No meetings found"
}
} }
$MaxStartDate=((Get-Date).AddDays(-89)).Date $MaxStartDate=((Get-Date).AddDays(-180)).Date
#Getting Teams meeting attendance report for past 90 days #Getting Teams meeting attendance report for past 180 days
if(($StartDate -eq $null) -and ($EndDate -eq $null)) if(($StartDate -eq $null) -and ($EndDate -eq $null))
{ {
$EndDate=(Get-Date)#.Date $EndDate=(Get-Date)#.Date
@ -128,7 +145,7 @@ While($true)
{ {
if ($StartDate -eq $null) if ($StartDate -eq $null)
{ {
$StartDate=Read-Host Enter start time for report generation '(Eg:11/23/2021)' $StartDate=Read-Host Enter start time for report generation '(Eg:11/23/2024)'
} }
Try Try
{ {
@ -139,7 +156,7 @@ While($true)
} }
else else
{ {
Write-Host `nAudit can be retrieved only for past 90 days. Please select a date after $MaxStartDate -ForegroundColor Red Write-Host `nAudit can be retrieved only for past 180 days. Please select a date after $MaxStartDate -ForegroundColor Red
return return
} }
} }
@ -155,7 +172,7 @@ While($true)
{ {
if ($EndDate -eq $null) if ($EndDate -eq $null)
{ {
$EndDate=Read-Host Enter End time for report generation '(Eg: 11/23/2021)' $EndDate=Read-Host Enter End time for report generation '(Eg: 11/23/2024)'
} }
Try Try
{ {
@ -190,27 +207,32 @@ if($CurrentStart -eq $CurrentEnd)
Write-Host Start and end time are same.Please enter different time range -ForegroundColor Red Write-Host Start and end time are same.Please enter different time range -ForegroundColor Red
Exit Exit
} }
$Global:MeetingCount=0
Connect_Modules
Get_TeamMeetings Get_TeamMeetings
$CurrentResultCount=0
$AggregateResultCount=0 #Get participants details if any meetings found
Write-Host `nGenerating Teams meeting attendance report from $StartDate to $EndDate... if($Global:MeetingCount -ne 0)
$ProcessedAuditCount=0
$OutputEvents=0
$ExportResult=""
$ExportResults=@()
$RetriveOperation="MeetingParticipantDetail"
while($true)
{ {
$CurrentResultCount=0
$AggregateResultCount=0
Write-Host `nGenerating Teams meeting attendance report from $StartDate to $EndDate...
$ProcessedAuditCount=0
$OutputEvents=0
$ExportResult=""
$ExportResults=@()
$RetriveOperation="MeetingParticipantDetail"
while($true)
{
#Getting Teams meeting participants report for the given time range #Getting Teams meeting participants report for the given time range
Search-UnifiedAuditLog -StartDate $CurrentStart -EndDate $CurrentEnd -Operations $RetriveOperation -SessionId s -SessionCommand ReturnLargeSet -ResultSize 5000 | ForEach-Object { $Results=Search-UnifiedAuditLog -StartDate $CurrentStart -EndDate $CurrentEnd -Operations $RetriveOperation -SessionId s -SessionCommand ReturnLargeSet -ResultSize 5000
$ResultCount++ $ResultsCount=($Results|Measure-Object).count
foreach($Result in $Results)
{
$ProcessedAuditCount++ $ProcessedAuditCount++
Write-Progress -Activity "`n Retrieving Team meeting participant report..."`n" Processed audit record count: $ProcessedAuditCount" Write-Progress -Activity "`n Retrieving Team meeting participant report..."`n" Processed audit record count: $ProcessedAuditCount"
$AuditData=$_.AuditData | ConvertFrom-Json $AuditData=$Result.AuditData | ConvertFrom-Json
$MeetingID=$AuditData.MeetingDetailId $MeetingID=$AuditData.MeetingDetailId
$CreatedBy=$_.UserIDs $CreatedBy=$Result.UserIDs
$AttendeesInfo=($AuditData.Attendees) $AttendeesInfo=($AuditData.Attendees)
$Attendees=$AttendeesInfo.userObjectId $Attendees=$AttendeesInfo.userObjectId
$AttendeesType=$AttendeesInfo.RecipientType $AttendeesType=$AttendeesInfo.RecipientType
@ -220,7 +242,7 @@ while($true)
} }
else else
{ {
$Attendees=(Get-AzureADUser -ObjectId $Attendees).UserPrincipalName $Attendees=(Get-ExoRecipient -Identity $Attendees).DisplayName
} }
$JoinTime=(Get-Date($AuditData.JoinTime)).ToLocalTime() #Get-Date($AuditData.JoinTime) Uncomment to view the Activity Time in UTC $JoinTime=(Get-Date($AuditData.JoinTime)).ToLocalTime() #Get-Date($AuditData.JoinTime) Uncomment to view the Activity Time in UTC
$LeaveTime=(Get-Date($AuditData.LeaveTime)).ToLocalTime() $LeaveTime=(Get-Date($AuditData.LeaveTime)).ToLocalTime()
@ -232,65 +254,57 @@ while($true)
$ExportResults= New-Object PSObject -Property $ExportResult $ExportResults= New-Object PSObject -Property $ExportResult
$ExportResults | Select-Object 'Meeting id','Created By','Attendees','Attendee Type','Joined Time','Left Time','More Info' | Export-Csv -Path $OutputCSV -Notype -Append $ExportResults | Select-Object 'Meeting id','Created By','Attendees','Attendee Type','Joined Time','Left Time','More Info' | Export-Csv -Path $OutputCSV -Notype -Append
} }
$currentResultCount=$CurrentResultCount+$ResultsCount
$currentResultCount=$currentResultCount+$ResultCount Write-Progress -Activity "`n Retrieving audit log for $CurrentStart : $CurrentResultCount records"`n" Total processed audit record count: $AggregateResults"
if($CurrentResultCount -ge 50000) if(($CurrentResultCount -eq 50000) -or ($ResultsCount -lt 5000))
{ {
Write-Host Retrieved max record for current range.Proceeding further may cause data loss or rerun the script with reduced time interval. -ForegroundColor Red if($CurrentResultCount -eq 50000)
{
Write-Host Retrieved max record for the current range.Proceeding further may cause data loss or rerun the script with reduced time interval. -ForegroundColor Red
$Confirm=Read-Host `nAre you sure you want to continue? [Y] Yes [N] No $Confirm=Read-Host `nAre you sure you want to continue? [Y] Yes [N] No
if($Confirm -match "[Y]") if($Confirm -notmatch "[Y]")
{
Write-Host Proceeding audit log collection with data loss
[DateTime]$CurrentStart=$CurrentEnd
[DateTime]$CurrentEnd=$CurrentStart.AddMinutes($IntervalTimeInMinutes)
$CurrentResultCount=0
if($CurrentEnd -gt $EndDate)
{
$CurrentEnd=$EndDate
}
}
else
{ {
Write-Host Please rerun the script with reduced time interval -ForegroundColor Red Write-Host Please rerun the script with reduced time interval -ForegroundColor Red
Exit Exit
} }
} else
if($ResultCount -lt 5000)
{ {
if($CurrentEnd -eq $EndDate) Write-Host Proceeding audit log collection with data loss
}
}
#Check for last iteration
if(($CurrentEnd -eq $EndDate))
{ {
break break
} }
$CurrentStart=$CurrentEnd [DateTime]$CurrentStart=$CurrentEnd
#Break loop if start date exceeds current date(There will be no data)
if($CurrentStart -gt (Get-Date)) if($CurrentStart -gt (Get-Date))
{ {
break break
} }
$CurrentEnd=$CurrentStart.AddMinutes($IntervalTimeInMinutes) [DateTime]$CurrentEnd=$CurrentStart.AddMinutes($IntervalTimeInMinutes)
$CurrentResultCount=0
if($CurrentEnd -gt $EndDate) if($CurrentEnd -gt $EndDate)
{ {
$CurrentEnd=$EndDate $CurrentEnd=$EndDate
} }
$CurrentResultCount=0
$CurrentResult = @()
}
} }
$ResultCount=0
} }
#Open output file after execution #Open output file after execution
If($OutputEvents -eq 0)
{
Write-Host No records found
}
else
{
Write-Host `nThe Teams meeting attendance report contains $OutputEvents audit records -ForegroundColor Green
if((Test-Path -Path $OutputCSV) -eq "True")
{
Write-Host `n" The Teams meetings attendance report available in: " -NoNewline -ForegroundColor Yellow; Write-Host "$OutputCSV"
Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green
Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline;
Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n Write-Host " to get access to 1900+ Microsoft 365 reports. ~~" -ForegroundColor Green `n
if((Test-Path -Path $OutputCSV) -eq "True")
{
Write-Host `nThe Teams meeting attendance report contains $OutputEvents audit records
Write-Host `n" The Teams meetings attendance report available in: " -NoNewline -ForegroundColor Yellow; Write-Host "$OutputCSV"
$Prompt = New-Object -ComObject wscript.shell $Prompt = New-Object -ComObject wscript.shell
$UserInput = $Prompt.popup("Do you want to open output file?",` $UserInput = $Prompt.popup("Do you want to open output file?",`
0,"Open Output File",4) 0,"Open Output File",4)
@ -300,9 +314,7 @@ else
Invoke-Item "$ExportCSV" Invoke-Item "$ExportCSV"
} }
} }
}
#Disconnect Exchange Online session #Disconnect Exchange Online session
Disconnect-ExchangeOnline -Confirm:$false -InformationAction Ignore -ErrorAction SilentlyContinue Disconnect-ExchangeOnline -Confirm:$false -InformationAction Ignore -ErrorAction SilentlyContinue