Entra / Microsoft 365 · Compliance & audit
Report file sharing audit events
Report audit events for file sharing from SharePoint Online and OneDrive for Business when sharing occurs with tenant members or guests.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes "AuditLog.Read.All","User.ReadBasic.All" -ErrorAction Stop
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([int] $LookbackDays = 180,[string] $StartDate = (Get-Date $EndDate).AddDays(-$LookbackDays),[string] $EndDate = (Get-Date).AddHours(1))Connect-MgGraph -Scopes "AuditLog.Read.All","User.ReadBasic.All" -ErrorAction Stop[array]$Modules = Get-Module | Select-Object -ExpandProperty NameIf ("ExchangeOnlineManagement" -Notin $Modules) {Write-Host "Connecting to Exchange Online..." -ForegroundColor YellowConnect-ExchangeOnline -showBanner:$false -UserPrincipalName (Get-MgContext).Account}# Check permissions available to the signed-in account and disconnect from the Graph if the requisite permissions are not available[string[]]$CurrentScopes = (Get-MgContext).Scopes[string[]]$RequiredScopes = @('AuditLog.Read.All','User.ReadBasic.All')$CheckScopes =[object[]][Linq.Enumerable]::Intersect($RequiredScopes,$CurrentScopes)If ($CheckScopes.Count -ne 2) {Write-Host ("To run this script, you need to connect to Microsoft Graph with the following scopes: {0}" -f $RequiredScopes) -ForegroundColor RedDisconnect-GraphBreak}Write-Host "Checking for sensitivity labels..." -ForegroundColor Yellow# Connect to compliance endpoint to retrieve details of sensitivity labelsConnect-IPPSSession -ShowBanner:$false -UserPrincipalName (Get-MgContext).Account[array]$Labels = Get-Label -ErrorAction SilentlyContinueIf ($Labels) {$LabelsHash = @{}ForEach ($Label in $Labels) {$LabelsHash.Add([string]$Label.ImmutableId, $Label.DisplayName)}}Write-Host "Setting up background audit query to find file sharing events..."$AuditJobName = ("Audit job created at {0}" -f (Get-Date -format 'dd-MMM-yyyy HH:mm'))$AuditQueryStart = (Get-Date $StartDate -format s)$AuditQueryEnd = (Get-Date $EndDate -format s)[array]$AuditOperationFilters = "SharingSet"$AuditQueryParameters = @{}$AuditQueryParameters.Add("@odata.type","#microsoft.graph.security.auditLogQuery")$AuditQueryParameters.Add("displayName", $AuditJobName)$AuditQueryParameters.Add("OperationFilters", $AuditOperationFilters)$AuditQueryParameters.Add("filterStartDateTime", $AuditQueryStart)$AuditQueryParameters.Add("filterEndDateTime", $AuditQueryEnd)$Uri = "https://graph.microsoft.com/beta/security/auditLog/queries"$AuditJob = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $AuditQueryParameters[int]$i = 1[int]$SleepSeconds = 20$SearchFinished = $false; [int]$SecondsElapsed = 20Write-Host "Checking audit query status..."Start-Sleep -Seconds 30$Uri = ("https://graph.microsoft.com/beta/security/auditLog/queries/{0}" -f $AuditJob.id)$AuditQueryStatus = Invoke-MgGraphRequest -Uri $Uri -Method GetWhile ($SearchFinished -eq $false) {$i++Write-Host ("Waiting for audit search to complete. Check {0} after {1} seconds. Current state {2}" -f $i, $SecondsElapsed, $AuditQueryStatus.status)If ($AuditQueryStatus.status -eq 'succeeded') {$SearchFinished = $true} Else {Start-Sleep -Seconds $SleepSeconds$SecondsElapsed = $SecondsElapsed + $SleepSeconds$AuditQueryStatus = Invoke-MgGraphRequest -Uri $Uri -Method Get}}$AuditRecords =$Uri = ("https://graph.microsoft.com/beta/security/auditLog/queries/{0}/records?`$top=999" -f $AuditJob.Id)[array]$AuditSearchRecords = Invoke-MgGraphRequest -Uri $Uri -Method GET[array]$AuditRecords = $AuditSearchRecords.value$NextLink = $AuditSearchRecords.'@Odata.NextLink'While ($null -ne $NextLink) {$AuditSearchRecords = $null[array]$AuditSearchRecords = Invoke-MgGraphRequest -Uri $NextLink -Method GET$AuditRecords += $AuditSearchRecords.valueWrite-Host ("{0} audit records fetched so far..." -f $AuditRecords.count)$NextLink = $AuditSearchRecords.'@odata.NextLink'}Write-Host ("Audit query {0} returned {1} records" -f $AuditJobName, $AuditRecords.Count)$AuditRecords = $AuditRecords | Sort-Object {$_.createdDateTime -as [datetime]} -Descending# Hash table to store user details so we don't have to call the Graph API for each user$UserDetails = @{}# Defined the type of target user or group to filter out unwanted records[array]$TargetTypes = "Member", "Guest"$AuditReport = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $AuditRecords) {# Ignore anything in SharePoint Embedded like Loop or Outlook NewslettersIf ($Rec.AuditData.ObjectId -like "*CSP_*" -or $Rec.AuditData.ObjectId -like "*contentstorage*") { Continue }# No interest in sharing done by the app@sharepoint account because these actions are for files like Teams meeting recordingsIf ($Rec.UserPrincipalName -eq 'app@sharepoint') { Continue }# No interest in SharePoint system group assigned to allow users access to a single item in a list or libraryIf ($Rec.AuditData.TargetUserOrGroupName -like 'Limited Access System Group*') { Continue }# And focus solely on sharing links created by usersIf ($Rec.AuditData.TargetUserOrGroupType -notin $TargetTypes) { Continue }If ($Rec.AuditData.UserAgent -like "*Teams*") {$UserAgent = "Teams"} Else {$UserAgent = "SharePoint"}# Find what permissions are in the sharing link$Matches = [regex]::Matches($Rec.AuditData.EventData, "<(.*?)>(.*?)</\1>")$SharingLinkData = @{}ForEach ($Match in $Matches) {$SharingLinkData[$Match.Groups[1].Value] = $Match.Groups[2].Value}# Get the user name from the user principal name. If the user principal name is not in the hash table, get it from the Graph API# and add it to the hash table for future use.$UserName = $null; $UPN = $null; $User = $nullIf ($Rec.userPrincipalName -like "*@*") {$UserName = $UserDetails[$Rec.userPrincipalName]$UPN = $Rec.userPrincipalNameIf ($null -eq $UserName) {Write-Host ("Checking user {0}..." -f $Rec.userPrincipalName)$User = Get-MgUser -UserId $Rec.userPrincipalName -ErrorAction SilentlyContinueIf ($null -ne $User) {$UserDetails.Add($User.UserPrincipalName, $User.displayName)$UserName = $User.displayName$UPN = $User.UserPrincipalName} Else {Write-Host ("User {0} is unknown in the directory" -f $Rec.userPrincipalName)$UserName = $Rec.userPrincipalName$UPN = $Rec.userPrincipalName}}}$TargetUserName = $null; $TargetUPN = $nullIf ($Rec.Auditdata.targetUserorGroupName -like "*@*") {$TargetUserOrGroupName = $Rec.AuditData.targetUserorGroupName$TargetUserName = $UserDetails[$TargetUserorGroupName]$TargetUPN = $Rec.AuditData.targetUserorGroupNameIf ($null -eq $TargetUserName) {$TargetUser = Get-MgUser -UserId $TargetUserorGroupName -ErrorAction SilentlyContinueIf ($null -ne $TargetUser) {$UserDetails.Add($TargetUser.UserPrincipalName, $TargetUser.displayName)$TargetUserName = $TargetUser.displayName$TargetUPN = $TargetUser.UserPrincipalName} Else {Write-Host ("User {0} is unknown in the directory" -f $TargetUserOrGroupName)$TargetUserName = $TargetUserOrGroupName$TargetUPN = $TargetUserOrGroupName}}} Else {$TargetUserName = $Rec.AuditData.targetUserorGroupName$TargetUPN = $Rec.AuditData.targetUserorGroupName}If ($TargetUPN -like "*#EXT#*") {$TargetDomain = $TargetUPN.split("_")[1].Split("#")[0]} Else {$TargetDomain = $TargetUPN.split("@")[1]}If ($Rec.AuditData.sensitivityLabelId) {$LabelName = $LabelsHash[$Rec.AuditData.sensitivityLabelId]} else {$LabelName = $null}If ($Rec.AuditData.SiteSensitivityLabelId) {$SiteLabelName = $LabelsHash[$Rec.AuditData.SiteSensitivityLabelId]} else {$SiteLabelName = $null}$ReportLine = [PSCustomObject][Ordered]@{CreatedDateTime = Get-Date $Rec.createdDateTime -format 'dd-MMM-yyyy HH:mm:ss'User = $UserNameUPN = $UPNOperation = $Rec.operationPermission = $SharingLinkData['PermissionsGranted']FileName = $Rec.AuditData.SourceFileNameTargetUserName = $TargetUserNameTargetUPN = $TargetUPNTargetType = $Rec.AuditData.TargetUserOrGroupTypeSiteUrl = $Rec.AuditData.SiteUrlSensitivityLabelId = $Rec.AuditData.sensitivityLabelIdSensitivityLabel = $LabelNameSiteSensitiviityLabel = $Rec.AuditData.SiteSensitivityLabelIdSiteSensitivityLabel = $SiteLabelNameUserAgent = $UserAgentObject = $Rec.AuditData.ObjectIdTargetDomain = $TargetDomain}$AuditReport.Add($ReportLine)}$AuditReport | Out-GridView -Title "Audit Report for File Sharing Events with Users and Guests"Write-Host "Generating report..."If (Get-Module ImportExcel -ListAvailable) {$ExcelGenerated = $TrueImport-Module ImportExcel -ErrorAction SilentlyContinue$ExcelOutputFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\File Sharing Events.xlsx"If (Test-Path $ExcelOutputFile) {Remove-Item $ExcelOutputFile -ErrorAction SilentlyContinue}$AuditReport | Export-Excel -Path $ExcelOutputFile -WorksheetName "File Sharing Ecvents" `-Title ("File Sharing Events {0}" -f (Get-Date -format 'dd-MMM-yyyy')) -TitleBold -TableName "FileSharingEvents"} Else {$CSVOutputFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\File Sharing Ecvents.CSV"$AuditReport | Export-Csv -Path $CSVOutputFile -NoTypeInformation -Encoding Utf8}If ($ExcelGenerated) {Write-Host ("An Excel report of calendar items is available in {0}" -f $ExcelOutputFile)} Else {Write-Host ("A CSV report of calendar items is available in {0}" -f $CSVOutputFile)}Write-Host ""Write-Host "Here's a snapshot of the domains users are sharing files with..."$AuditReport | Group-Object TargetDomain -NoElement | Sort-Object Count -Descending | Format-Table Name, CountWrite-Host "All done..."
Parameters
ParameterDefaultNotes
-LookbackDays180Number of days back to search the unified audit log.-StartDate(Get-Date $EndDate).AddDays(-180)Start of the reporting window.-EndDate(Get-Date).AddHours(1)End of the reporting window.Attribution
Author
Office365itpros