2023-09-28 12:39:20 +05:30
<#
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2025-03-05 15:13:36 +05:30
Name : Audit Microsoft Teams meetings and export teams meeting attendance report using PowerShell
Version : 2.0
2023-09-28 12:39:20 +05:30
Description : This script exports Teams meeting report and attendance report into 2 CSV files .
website : o365reports . com
Script Highlights :
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
2025-03-05 15:13:36 +05:30
1 . Exports a list of Teams meetings .
2 . Exports Teams meeting attendance report .
2023-09-28 12:39:20 +05:30
3 . Exports report results to CSV file .
2025-03-05 15:13:36 +05:30
4 . The script can be executed with MFA enabled account too .
5 . Supports certificate-based authentication ( CBA ) too .
2023-09-28 12:39:20 +05:30
6 . Helps to generate reports for custom periods .
2025-03-05 15:13:36 +05:30
7 . Automatically installs the EXO PowerShell module ( if not installed already ) upon your confirmation .
8 . The script is scheduler-friendly .
2023-09-28 12:39:20 +05:30
For detailed Script execution : https : / / o365reports . com / 2021 / 12 / 08 / microsoft-teams -meeting -attendance -report
2025-03-05 15:13:36 +05:30
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
2023-09-28 12:39:20 +05:30
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
#>
2021-12-09 17:12:54 +05:30
Param
(
[ Parameter ( Mandatory = $false ) ]
[ Nullable[DateTime] ] $StartDate ,
[ Nullable[DateTime] ] $EndDate ,
[ string ] $UserName ,
2025-03-05 15:13:36 +05:30
[ string ] $Password ,
[ string ] $Organization ,
[ string ] $ClientId ,
[ string ] $CertificateThumbprint
2021-12-09 17:12:54 +05:30
)
2025-03-05 15:13:36 +05:30
#Getting StartDate and EndDate for Audit log
if ( ( ( $StartDate -eq $null ) -and ( $EndDate -ne $null ) ) -or ( ( $StartDate -ne $null ) -and ( $EndDate -eq $null ) ) )
{
Write-Host ` nPlease enter both StartDate and EndDate for Audit log collection -ForegroundColor Red
exit
}
elseif ( ( $StartDate -eq $null ) -and ( $EndDate -eq $null ) )
{
$StartDate = ( ( ( Get-Date ) . AddDays ( -180 ) ) ) . Date
$EndDate = Get-Date
}
else
2021-12-09 17:12:54 +05:30
{
2025-03-05 15:13:36 +05:30
$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
2021-12-09 17:12:54 +05:30
if ( $Module . count -eq 0 )
{
2025-03-05 15:13:36 +05:30
Write-Host Exchange Online PowerShell module is not available -ForegroundColor yellow
2021-12-09 17:12:54 +05:30
$Confirm = Read-Host Are you sure you want to install module ? [ Y] Yes [N ] No
if ( $Confirm -match " [yY] " )
{
Write-host " Installing Exchange Online PowerShell module "
2025-03-05 15:13:36 +05:30
Install-Module ExchangeOnlineManagement -Repository PSGallery -AllowClobber -Force -Scope CurrentUser
Import-Module ExchangeOnlineManagement
2021-12-09 17:12:54 +05:30
}
else
{
2025-03-05 15:13:36 +05:30
Write-Host EXO module is required to connect Exchange Online . Please install module using Install-Module ExchangeOnlineManagement cmdlet .
2021-12-09 17:12:54 +05:30
Exit
}
}
2025-03-05 15:13:36 +05:30
Write-Host Connecting to Exchange Online . . .
#Storing credential in script for scheduling purpose/ Passing credential as parameter - Authentication using non-MFA account
if ( ( $AdminName -ne " " ) -and ( $Password -ne " " ) )
{
$SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force
$Credential = New-Object System . Management . Automation . PSCredential $AdminName , $SecuredPassword
Connect-ExchangeOnline -Credential $Credential -ShowBanner: $false
2021-12-09 17:12:54 +05:30
}
2025-03-05 15:13:36 +05:30
elseif ( $Organization -ne " " -and $ClientId -ne " " -and $CertificateThumbprint -ne " " )
2021-12-09 17:12:54 +05:30
{
2025-03-05 15:13:36 +05:30
Connect-ExchangeOnline -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -Organization $Organization -ShowBanner: $false
2021-12-09 17:12:54 +05:30
}
else
{
2025-03-05 15:13:36 +05:30
Connect-ExchangeOnline -ShowBanner: $false
2021-12-09 17:12:54 +05:30
}
Function Get_TeamMeetings
{
$Result = " "
$Results = @ ( )
2025-03-05 15:13:36 +05:30
Write-Host ` nRetrieving Teams meeting details from $StartDate to $EndDate . . .
2021-12-09 17:12:54 +05:30
Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations " MeetingDetail " -ResultSize 5000 | ForEach-Object {
2025-03-05 15:13:36 +05:30
$Global:MeetingCount + +
2021-12-09 17:12:54 +05:30
Write-Progress -Activity " `n Retrieving Teams meetings data from $StartDate to $EndDate .. " ` n " Processed Teams meetings count: $Count "
$AuditData = $_ . AuditData | ConvertFrom-Json
$MeetingID = $AuditData . ID
$CreatedBy = $AuditData . UserId
$StartTime = ( Get-Date ( $AuditData . StartTime ) ) . ToLocalTime ( )
$EndTime = ( Get-Date ( $AuditData . EndTime ) ) . ToLocalTime ( )
$MeetingURL = $AuditData . MeetingURL
$MeetingType = $AuditData . ItemName
$Result = @ { 'Meeting id' = $MeetingID ; 'Created By' = $CreatedBy ; 'Start Time' = $Star = $StartTime ; 'End Time' = $EndTime ; 'Meeting Type' = $MeetingType ; 'Meeting Link' = $MeetingURL ; 'More Info' = $AuditData }
$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
}
2025-03-05 15:13:36 +05:30
if ( $MeetingCount -ne 0 )
{
Write-Host $Global:MeetingCount meetings details are exported .
}
else
{
Write-Host " No meetings found "
}
2021-12-09 17:12:54 +05:30
}
2025-03-05 15:13:36 +05:30
$MaxStartDate = ( ( Get-Date ) . AddDays ( -180 ) ) . Date
#Getting Teams meeting attendance report for past 180 days
2021-12-09 17:12:54 +05:30
if ( ( $StartDate -eq $null ) -and ( $EndDate -eq $null ) )
{
$EndDate = ( Get-Date ) #.Date
$StartDate = $MaxStartDate
}
#Getting start date to generate Teams meetings attendance report
While ( $true )
{
if ( $StartDate -eq $null )
{
2025-03-05 15:13:36 +05:30
$StartDate = Read-Host Enter start time for report generation '(Eg:11/23/2024)'
2021-12-09 17:12:54 +05:30
}
Try
{
$Date = [ DateTime ] $StartDate
if ( $Date -ge $MaxStartDate )
{
break
}
else
{
2025-03-05 15:13:36 +05:30
Write-Host ` nAudit can be retrieved only for past 180 days . Please select a date after $MaxStartDate -ForegroundColor Red
2021-12-09 17:12:54 +05:30
return
}
}
Catch
{
Write-Host ` nNot a valid date -ForegroundColor Red
}
}
#Getting end date for teams attendance report
While ( $true )
{
if ( $EndDate -eq $null )
{
2025-03-05 15:13:36 +05:30
$EndDate = Read-Host Enter End time for report generation '(Eg: 11/23/2024)'
2021-12-09 17:12:54 +05:30
}
Try
{
$Date = [ DateTime ] $EndDate
if ( $EndDate -lt ( $StartDate ) )
{
Write-Host End time should be later than start time -ForegroundColor Red
return
}
break
}
Catch
{
Write-Host ` nNot a valid date -ForegroundColor Red
}
}
$OutputCSV = " .\TeamsMeetingAttendanceReport_ $( ( Get-Date -format yyyy-MMM -dd -ddd ` hh-mm ` tt ) . ToString ( ) ) .csv "
$ExportCSV = " .\TeamsMeetingsReport_ $( ( Get-Date -format yyyy-MMM -dd -ddd ` hh-mm ` tt ) . ToString ( ) ) .csv "
$IntervalTimeInMinutes = 1440 #$IntervalTimeInMinutes=Read-Host Enter interval time period '(in minutes)'
$CurrentStart = $StartDate
$CurrentEnd = $CurrentStart . AddMinutes ( $IntervalTimeInMinutes )
#Check whether CurrentEnd exceeds EndDate
if ( $CurrentEnd -gt $EndDate )
{
$CurrentEnd = $EndDate
}
if ( $CurrentStart -eq $CurrentEnd )
{
Write-Host Start and end time are same . Please enter different time range -ForegroundColor Red
Exit
}
2025-03-05 15:13:36 +05:30
$Global:MeetingCount = 0
2021-12-09 17:12:54 +05:30
Get_TeamMeetings
2025-03-05 15:13:36 +05:30
#Get participants details if any meetings found
if ( $Global:MeetingCount -ne 0 )
{
$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
$Results = Search-UnifiedAuditLog -StartDate $CurrentStart -EndDate $CurrentEnd -Operations $RetriveOperation -SessionId s -SessionCommand ReturnLargeSet -ResultSize 5000
$ResultsCount = ( $Results | Measure-Object ) . count
foreach ( $Result in $Results )
2021-12-09 17:12:54 +05:30
{
2025-03-05 15:13:36 +05:30
$ProcessedAuditCount + +
$AuditData = $Result . AuditData | ConvertFrom-Json
$MeetingID = $AuditData . MeetingDetailId
$CreatedBy = $Result . UserIDs
$AttendeesInfo = ( $AuditData . Attendees )
$Attendees = $AttendeesInfo . userObjectId
$AttendeesType = $AttendeesInfo . RecipientType
if ( $AttendeesType -ne " User " )
{
$Attendees = $AttendeesInfo . DisplayName
}
else
{
$Attendees = ( Get-ExoRecipient -Identity $Attendees ) . DisplayName
}
$JoinTime = ( Get-Date ( $AuditData . JoinTime ) ) . ToLocalTime ( ) #Get-Date($AuditData.JoinTime) Uncomment to view the Activity Time in UTC
$LeaveTime = ( Get-Date ( $AuditData . LeaveTime ) ) . ToLocalTime ( )
2021-12-09 17:12:54 +05:30
2025-03-05 15:13:36 +05:30
#Export result to csv
$OutputEvents + +
$ExportResult = @ { 'Meeting id' = $MeetingID ; 'Created By' = $CreatedBy ; 'Attendees' = $Attendees ; 'Attendee Type' = $AttendeesType ; 'Joined Time' = $JoinTime ; 'Left Time' = $LeaveTime ; 'More Info' = $AuditData }
$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
}
$currentResultCount = $CurrentResultCount + $ResultsCount
2025-03-05 18:07:48 +05:30
Write-Progress -Activity " `n Retrieving audit log for $CurrentStart : $CurrentResultCount records " ` n " Total processed audit record count: $ProcessedAuditCount "
2025-03-05 15:13:36 +05:30
if ( ( $CurrentResultCount -eq 50000 ) -or ( $ResultsCount -lt 5000 ) )
2021-12-09 17:12:54 +05:30
{
2025-03-05 15:13:36 +05:30
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
if ( $Confirm -notmatch " [Y] " )
{
Write-Host Please rerun the script with reduced time interval -ForegroundColor Red
Exit
}
else
{
Write-Host Proceeding audit log collection with data loss
}
}
#Check for last iteration
if ( ( $CurrentEnd -eq $EndDate ) )
{
break
}
2021-12-09 17:12:54 +05:30
[ DateTime ] $CurrentStart = $CurrentEnd
2025-03-05 15:13:36 +05:30
#Break loop if start date exceeds current date(There will be no data)
if ( $CurrentStart -gt ( Get-Date ) )
{
break
}
2021-12-09 17:12:54 +05:30
[ DateTime ] $CurrentEnd = $CurrentStart . AddMinutes ( $IntervalTimeInMinutes )
if ( $CurrentEnd -gt $EndDate )
{
$CurrentEnd = $EndDate
}
2025-03-05 15:13:36 +05:30
$CurrentResultCount = 0
$CurrentResult = @ ( )
2021-12-09 17:12:54 +05:30
}
2025-03-05 15:13:36 +05:30
}
2021-12-09 17:12:54 +05:30
}
#Open output file after execution
2025-03-05 15:13:36 +05:30
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 " to get access to 1900+ Microsoft 365 reports. ~~ " -ForegroundColor Green ` n
2021-12-09 17:12:54 +05:30
if ( ( Test-Path -Path $OutputCSV ) -eq " True " )
{
2025-03-05 15:13:36 +05:30
Write-Host ` nThe Teams meeting attendance report contains $OutputEvents audit records
2023-09-28 12:39:20 +05:30
Write-Host ` n " The Teams meetings attendance report available in: " -NoNewline -ForegroundColor Yellow ; Write-Host " $OutputCSV "
2025-03-05 15:13:36 +05:30
2021-12-09 17:12:54 +05:30
$Prompt = New-Object -ComObject wscript . shell
$UserInput = $Prompt . popup ( " Do you want to open output file? " , `
0 , " Open Output File " , 4 )
If ( $UserInput -eq 6 )
{
Invoke-Item " $OutputCSV "
Invoke-Item " $ExportCSV "
}
}
2023-09-28 12:39:20 +05:30
2025-03-05 15:13:36 +05:30
#Disconnect Exchange Online session
Disconnect-ExchangeOnline -Confirm: $false -InformationAction Ignore -ErrorAction SilentlyContinue