The inevitable question then arose: how can administrators know when someone records a Teams meeting? The book answer is that neither Teams nor Office 365 offer an obvious way to know when a new recording happens. Recording is, after all, a personal action decided by the meeting organizer or a presenter (or invoked automatically by a meeting setting). Once the meeting policy assigned to the organizer or presenter allows them to use “cloud recording,” Teams records the meeting and captures the MP4 file in OneDrive for Business (personal meetings) or SharePoint Online (channel meetings).
Recording a meeting is not an auditable activity so it’s not captured in the Office 365 audit log. However, the creation of a new file in OneDrive for Business or SharePoint Online is auditable, so we can examine those events to see if we can track new recordings.
Audit Events for a Teams Meeting Recording
First, let’s consider what happens on the creation of a new Teams meeting recording. Three events appear in the audit log:
Creation of a temporary MP4 file in a FileUploaded event.
Modification of the temporary file in a FileModified event. This is likely when Teams updates the properties of the file to add the programmatic identifiers Media and Meeting which mark the file as a Teams meeting recording.
Renaming the file name of the temporary file to its final form. This is also in a FileModified event. The temporary file has a prefix of “~tmp.” Teams removes the prefix to create the final file name in the form meeting name, date, and “meeting recording.” For example, the file called H2 FY2021 Review 20210615_140058-Meeting Recording.mp4 is for a Teams meeting on June 15, 2021, starting at 14:00:58 (local).
Equipped with this knowledge, we can search the audit log with the Search-UnifiedAuditLog cmdlet and parse the audit events to extract data for analysis.
PowerShell Script to Find Creation of Teams Meeting Recordings
The PowerShell code is straightforward:
Use the Search-UnifiedAuditLog cmdlet to retrieve the set of audit events for FileModified and FileUploaded actions over a set period (I used 14 days).
Parse the AuditData content in each audit event to figure out if it’s a Teams recording. The basic test used is that the file is an MP4 stored in the /Recordings folder.
Extract information about the recording and write to a PowerShell list. For instance, it’s good to know the title of the meeting.
Finally, extract the records for the temporary file to create a set for the final creation of Teams meeting recordings. Output this data to the screen with Out-GridView and to a CSV file.
Here’s the main processing loop:
[array]$Records = (Search-UnifiedAuditLog -Operations FileUploaded, FileModified -StartDate $StartDate -EndDate $EndDate -Formatted -ResultSize 5000)
If (!($Records)) {Write-Host "No audit records found - exiting!"; break}
$TaggedRecordings = [System.Collections.Generic.List[Object]]::new()
ForEach ($Rec in $Records) {
$AuditData = $Rec.AuditData | ConvertFrom-Json
If (($AuditData.SourceFileExtension -eq "mp4") -and ($AuditData.SourceRelativeUrl -like "*/Recordings")) {
$RecordingFileName = $AuditData.SourceFileName
$DateLoc = $RecordingFileName.IndexOf("-202")
If ($DateLoc -eq -1) {$Topic = $RecordingFileName} Else {$Topic = $RecordingFileName.SubString(0,$DateLoc)}
$DataLine = [PSCustomObject] @{
Workload = $AuditData.Workload
Date = $Rec.CreationDate
User = $Rec.UserIds
Recording = $RecordingFileName
"Meeting title" = $Topic
Site = $AuditData.SiteURL
FullURL = $AuditData.ObjectId
Folder = $AuditData.SourceRelativeURL
Operation = $Rec.Operations }
$TaggedRecordings.Add($DataLine)
} #End If
} #End For
Figure 1 shows the output as viewed through Out-GridView.
Figure 1: Viewing audit log data for the creation of Teams meeting recordings
Search-UnifiedAuditLog can return up to 50,000 audit events from the log. In large tenants where many document events accrue daily, you might exceed this number of audit events in a couple of days or even sooner. In this situation, you’ll need to fetch audit events and store them in another repository and analyze them from there.
Exploit the Audit Log
People sometimes complain when they can’t find a simple off-the-shelf option in a Microsoft 365 administration GUI to do something. The Office 365 audit log contains lots of interesting information which answers many questions, like who’s creating Teams meeting recordings. You only have to look. Or rather, know where to look.
Learn how to exploit the Office 365 data available to tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.
Thank you for sharing this. While, as you mentioned, there is currently “no simple off-the-shelf option”, it’s good to know it is possible to parse out this information when we need to track down if a meeting was recorded. Thanks for taking the time to put this together.
Thank you for this. I’m not sure why, but when I run this for -180 days I get 2 entries. But if I modify this to run for 10 day increments I get a few hundred. Why would a larger date range return such few results?
Because Office 365 keeps items in the audit log for only 90 days if accounts have Office 365 E3 licenses. To go back further, you need Office 365 E5 (to 365 days).
Hi Tony. The bulk of our users have E1. Whether I set the time frame for -14 or -90 days I only see recordings created today. Thoughts?:
$StartDate = (Get-Date).AddDays(-90); $EndDate = (Get-Date) # Set your own date span here!
Workload Date
OneDrive 7/21/2022 19:46
OneDrive 7/21/2022 19:31
OneDrive 7/21/2022 19:23
OneDrive 7/21/2022 19:08
OneDrive 7/21/2022 19:06
OneDrive 7/21/2022 18:43
OneDrive 7/21/2022 18:37
OneDrive 7/21/2022 18:29
I’m having the same issue and I could see the records in the Audit search feature of the Purview compliance porta
Loading...
I don’t know what’s going on. I ran the script just now and it worked perfectly. Obviously, I can’t see your data. By any chance are the locations of the recordings stored in OneDrive somewhere other than Recordings? I don’t know if OneDrive (or Teams) creates a localized folder name to store recordings in, but the script looks for them in the Recordings folder…
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
Thank you for sharing this. While, as you mentioned, there is currently “no simple off-the-shelf option”, it’s good to know it is possible to parse out this information when we need to track down if a meeting was recorded. Thanks for taking the time to put this together.
Hello Tony. Wanted to add my thanks for posting this article and the github link. Excellent work.
Cannot process argument transformation on parameter ‘StartDate’. Cannot convert null to type “Microsoft.Exchange.ExchangeSystem.ExDateTime”.
+ CategoryInfo : InvalidData: (:) [Search-UnifiedAuditLog], ParameterBindin…mationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Search-UnifiedAuditLog
+ PSComputerName : outlook.office365.com
What is $StartDate defined as? The script does this to set the start date for the search to be 14 days before the current date.
$StartDate = (Get-Date).AddDays(-14); $EndDate = (Get-Date) # Set your own date span here!
$OutputCSVFile = “C:\temp\AuditEventsTeamsRecordings.csv”
# Find the audit records
[array]$Records = (Search-UnifiedAuditLog -Operations FileUploaded, FileModified -StartDate $StartDate -EndDate $EndDate -Formatted -ResultSize 5000)
Thank you for this. I’m not sure why, but when I run this for -180 days I get 2 entries. But if I modify this to run for 10 day increments I get a few hundred. Why would a larger date range return such few results?
Because Office 365 keeps items in the audit log for only 90 days if accounts have Office 365 E3 licenses. To go back further, you need Office 365 E5 (to 365 days).
Hi Tony. The bulk of our users have E1. Whether I set the time frame for -14 or -90 days I only see recordings created today. Thoughts?:
$StartDate = (Get-Date).AddDays(-90); $EndDate = (Get-Date) # Set your own date span here!
Workload Date
OneDrive 7/21/2022 19:46
OneDrive 7/21/2022 19:31
OneDrive 7/21/2022 19:23
OneDrive 7/21/2022 19:08
OneDrive 7/21/2022 19:06
OneDrive 7/21/2022 18:43
OneDrive 7/21/2022 18:37
OneDrive 7/21/2022 18:29
Can you see the records in the Audit search feature of the Purview compliance portal?
I’m having the same issue and I could see the records in the Audit search feature of the Purview compliance porta
I don’t know what’s going on. I ran the script just now and it worked perfectly. Obviously, I can’t see your data. By any chance are the locations of the recordings stored in OneDrive somewhere other than Recordings? I don’t know if OneDrive (or Teams) creates a localized folder name to store recordings in, but the script looks for them in the Recordings folder…