Last active
April 8, 2025 13:31
-
-
Save joerodgers/2b525109d2153611ddc219f53d63b2a7 to your computer and use it in GitHub Desktop.
Reports Teams recordings (.mp4) analytics in all onedrives
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -Modules @{ ModuleName="PnP.PowerShell"; ModuleVersion="2.4.0" } | |
function New-Recording | |
{ | |
[CmdletBinding()] | |
param | |
( | |
[parameter(mandatory=$true)] | |
[AllowEmptyString()] | |
[string] | |
$SiteUrl, | |
[parameter(mandatory=$true)] | |
[AllowEmptyString()] | |
[string] | |
$FileName, | |
[parameter(mandatory=$true)] | |
[AllowEmptyString()] | |
[string] | |
$ContentType, | |
[parameter(mandatory=$true)] | |
[AllowNull()] | |
[int] | |
$ActorCount, | |
[parameter(mandatory=$true)] | |
[AllowNull()] | |
[int] | |
$ActionCount | |
) | |
if( [string]::IsNullOrEmpty( $ContentType ) ) | |
{ | |
$ContentType = "Unknown" | |
} | |
[PSCustomObject] @{ | |
SiteUrl = $SiteUrl | |
FileName = $FileName | |
ContentType = $ContentType | |
ActorCount = $ActorCount | |
ActionCount = $ActionCount | |
} | |
} | |
function Get-RecordingMetadata | |
{ | |
[CmdletBinding()] | |
param | |
( | |
) | |
begin | |
{ | |
} | |
process | |
{ | |
try | |
{ | |
$web = Invoke-PnPSPRestMethod -Method GET -Url '/_api/web?$select=ServerRelativeUrl' -ErrorAction Stop | |
} | |
catch | |
{ | |
Write-Error "Failed to retrieve ServerRelativeUrl. Error: $_" | |
return | |
} | |
$serverRelativeUrl = $web.ServerRelativeUrl | |
# get the first 5000 recordings from the /Documents/Recordings folder | |
$filter = '{{ | |
"parameters": {{ | |
"FolderServerRelativeUrl" : "{0}/Documents/Recordings", | |
"ViewXml" : "<View Scope=\"RecursiveAll\"><Query><Where><Eq><FieldRef Name=''File_x0020_Type''/><Value Type=''text''>mp4</Value></Eq></Where></Query><RowLimit Paged=\"TRUE\">5000</RowLimit></View>", | |
"RenderOptions" : 4099 | |
}} | |
}}' -f $serverRelativeUrl | |
# check if the Documents/Recordings folder exists | |
try | |
{ | |
$folderExistsUrl = "/_api/web/GetFolderByServerRelativeUrl('{0}')/Exists" -f "$serverRelativeUrl/Documents/Recordings" | |
$response = Invoke-PnPSPRestMethod -Method GET -Url $folderExistsUrl -ErrorAction Stop | |
if( -not $response.value ){ return } # folder does not exist | |
} | |
catch | |
{ | |
Write-Error "Failed to determine if the Recordings folder exists. Error: $_" | |
return | |
} | |
# pull first 5k .mp4 files from /Documents/Recordings folder | |
try | |
{ | |
$listItemsUrl = '/_api/web/GetListByTitle(''Documents'')/RenderListDataAsStream' | |
$response = Invoke-PnPSPRestMethod -Method POST -Url $listItemsUrl -Content $filter -ErrorAction Stop | |
if( -not $response.ListData.Row ){ return } | |
} | |
catch | |
{ | |
Write-Error "Failed to read .mp4 files at $serverRelativeUrl/Documents/Recordings. Error: $_" | |
return | |
} | |
# enumerate .mp4 files | |
$recordings = foreach( $row in $response.ListData.Row ) | |
{ | |
$uri = [uri]$row.'.spItemUrl' | |
if( -not $uri ) | |
{ | |
Write-Error "Failed to determine recording drive url." | |
return | |
} | |
# fetch media properties | |
try | |
{ | |
$uri = [uri]$row.'.spItemUrl' # list item drive url | |
$mediaUrl = '{0}&select=media' -f $uri.AbsoluteUri | |
$media = Invoke-PnPSPRestMethod -Method GET -Url $mediaUrl -Accept "application/json" -ErrorAction Stop | |
} | |
catch | |
{ | |
Write-Error "Failed to media properties for $($uri.AbsoluteUri). Error: $_" | |
} | |
# fetch file analytics | |
try | |
{ | |
$analyticsUrl = '{0}/analytics/allTime' -f $uri.AbsolutePath | |
$analytics = Invoke-PnPSPRestMethod -Method GET -Url $analyticsUrl -ErrorAction Stop | |
} | |
catch | |
{ | |
Write-Error "Failed to analytics properties for $($uri.AbsoluteUri). Error: $_" | |
} | |
New-Recording -SiteUrl $serverRelativeUrl -FileName $row.FileLeafRef -ContentType $media.media.mediasource.contentType -ActorCount $analytics.access.actorCount -ActionCount $analytics.access.actionCount | |
} | |
return $recordings | |
} | |
end | |
{ | |
} | |
} | |
# requires SharePoint > Application > Sites.FullControl.All | |
Connect-PnPOnline -Url "https://$env:CDX_TENANT-admin.sharepoint.com" ` | |
-ClientId $env:CDX_CLIENTID ` | |
-Thumbprint $env:CDX_THUMBPRINT ` | |
-Tenant $env:CDX_TENANTID ` | |
-ErrorAction Stop | |
$timestamp = Get-Date -Format FileDateTime | |
$exportPath = "C:\_temp\recording_analytics_$timestamp.csv" | |
$connection = Get-PnPConnection -ErrorAction Stop | |
$drives = Get-PnPTenantSite -IncludeOneDriveSites -Filter "Url -like '.sharepoint.com/personal/'" -ErrorAction Stop | |
$counter = 0 | |
foreach( $drive in $drives ) | |
{ | |
$counter++ | |
Write-Host "[$(Get-Date)] - ($counter/$($drives.Count)) - Scanning drive: $($drive.Url)" | |
try | |
{ | |
# connect to the onedrive | |
Connect-PnPOnline -Url $drive.Url ` | |
-ClientId $connection.ClientId ` | |
-Thumbprint $connection.Certificate.Thumbprint ` | |
-Tenant $connection.Tenant ` | |
-ErrorAction Stop | |
if( $recordings = Get-RecordingMetadata -ErrorAction Stop ) | |
{ | |
$recordings | |
$recordings | Export-Csv -Path $exportPath -NoTypeInformation -Append | |
} | |
} | |
catch | |
{ | |
Write-Host "Failed to scan site: $($drive.Url). Error: $_" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment