Entra / Microsoft 365 · SharePoint & OneDrive
Audit SharePoint operations by user
Generates a per-user CSV report of SharePoint Online document operations from the unified audit log.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-ExchangeOnline
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([int] $LookbackDays = 90,[string] $StartDate = (Get-Date).AddDays(-$LookbackDays); $EndDate = (Get-Date).AddDays(+1); $DroppedRecords = 0; $StartProcessingTime = (Get-Date),[string] $EndDate = (Get-Date))# Check do we have the right modules loaded....$Module = Get-Module$AzureAD = $Module | ? {$_.Name -like "*AzureAD*"}If (!$AzureAD) { Write-Host "This script needs the Azure Active Directory PowerShell module" ; break}$Exo = $Module | ? {$_.Name -eq "ExchangeOnlineManagement"}If (!$Exo) { Write-Host "This script needs the Exchange Online Management PowerShell module" ; break }# Check what our parameter is. If no parameter is passed, we check both tenant users and guests. If Tenant is passed, we only check mailbox owners.Switch ($CheckUsers) {"Tenant" { # Just check tenant users$Type = "(Mailbox owners)"$Users = Get-ExoMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited | Select UserPrincipalName,DisplayName }default { #Check everyone$Type = "(Tenant and guest user accounts)"$Users = Get-AzureADUser -All $True -Filter ("UserType eq 'Guest' or UserType eq 'Member'") | Select UserPrincipalName, DisplayName }} #End switchWrite-Host $Users.Count "accounts will be checked." $Type$Operations = @('FileAccessed', 'FileDownloaded', 'FileModified', 'FileDeleted', 'FileUploaded')# Get a unique session identifier$Sessionid = (New-Guid).Guid$i = 0; $TotalRecordsProcessed = 0$Report = [System.Collections.Generic.List[Object]]::new() # Create output file$Records = 1While ($Records) { #Keep fetching until we have processed all records$i++Write-Host "Fetching audit records...( batch" $i ")"$Records = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations $Operations -ResultSize 2000 -SessionId $SessionId -SessionCommand ReturnLargeSetIf ($i -eq 1) {$TotalRecordsAvailable = $Records[0].ResultCount}If ($Records.Count -ne 0) {$StatusProcessing = "Processing " + $Records.Count + " audit records of " + $TotalRecordsAvailable + ". Total processed so far: " + $TotalRecordsProcessedWrite-Host $StatusProcessing}ForEach ($Rec in $Records) {$AuditData = $Rec.AuditData | ConvertFrom-JSon# Drop all the graphic files and web pages for which SharePoint Online creates audit records# Also drop records where the useragent isn't recorded because of system processing and any created by App@SharePointIf ($AuditData.SourceFileName -like "*.jpg*" -or $AuditData.SourceFileName -like "*.aspx*" -or $AuditData.SourceFileName -like "*.png*" -or [string]::IsNullOrEmpty($AuditData.UserAgent) -or $AuditData.UserId -eq "app@sharepoint" ) { $DroppedRecords++ }Else {# Make some sense of the User agent stringSwitch ($AuditData.UserAgent) { # This switch is used to interpret the UserAgent string and make it more human friendly{$AuditData.UserAgent -like "*Word 2014*"} {$UserAgent = "Microsoft Word 2014 (Mac)"$Version = "2014"; break}{$AuditData.UserAgent -like "*Edg/*"} {$UserAgent = "Microsoft Edge"$Version = $AuditData.UserAgent.Split("/")[5] ; break}{$AuditData.UserAgent -like "*Microsoft Office Excel*"} { # Excel desktop$UserAgent = "Microsoft Excel (desktop)"$Version = $AuditData.UserAgent.Split("/")[1].Split("(")[0] ; break }{$AuditData.UserAgent -Like "*Faraday*"} { #Yammer process for uploading and removing files$UserAgent = "Yammer"$Version = $AuditData.UserAgent.Split("v")[1] ; break }{$AuditData.UserAgent -like "*Skydrive*"} { # OneDrive for Business$UserAgent = "OneDrive for Business"$Version = $AuditData.UserAgent.Split("nc ")[5] ; break }{$AuditData.UserAgent -like "*Microsoft Office PowerPoint*"} { #Word desktop$UserAgent = "Microsoft PowerPoint (desktop)"$Version = $AuditData.UserAgent.Split("/")[1].Split("(")[0]; break }{$AuditData.UserAgent -like "*Microsoft Office Word*"} { #Word desktop$UserAgent = "Microsoft Word (desktop)"$Version = $AuditData.UserAgent.Split("/")[1].Split("(")[0]; break }{$AuditData.UserAgent -like "*Teams/*"} {$UserAgent = "Teams"$Version = $AuditData.UserAgent.Split("/")[3].Split("Ch")[0]; break}{$AuditData.UserAgent -like "*Gecko*" -and $AuditData.UserAgent -like "*Chrome*"} {$UserAgent = "Chromium browser"$Version = $AuditData.UserAgent.Split("/")[3].Split("Safari")[0]}"OneDriveMpc-Transform_Thumbnail/1.0" {$UserAgent = "OneDrive"}"MSWAC" {$UserAgent = "Office Online"$Version = "N/A" }"MSOCS" {$UserAgent = "Office Online"$Version = "N/A" }"MSWACONSync" {$UserAgent = "OneDrive Online"$Version = "N/A"}default {$UserAgent = $AuditData.UserAgent$Version = "N/A" }} # End SwitchIf ($Null -eq $UserAgent) {$UserAgent = "Not Recorded"}$ReportLine = [PSCustomObject] @{TimeStamp = Get-Date ($AuditData.CreationTime) -format gUPN = $AuditData.UserIdOperation = $AuditData.OperationFileName = $AuditData.SourceFileNameObject = $AuditData.ObjectIdSite = $AuditData.SiteUrlClientIP = $AuditData.ClientIPUserAgent = $UserAgentVersion = $VersionFullClientInfo = $AuditData.UserAgent}$Report.Add($ReportLine) }}# Update number of processed records$TotalRecordsProcessed = $TotalRecordsProcessed + $Records.Count}CLS# Because we've fetched large amounts of unsorted data using the ReturnLargeSet parameter, we should sort it to make sure that it's in the correct order$Report = $Report | Sort {$_.TimeStamp -as [DateTime]} -Descending$ProgressDelta = 100/($Users.count); $PercentComplete = 0; $UserNumber = 0; $UserReports = 0ForEach ($U in $Users) {$UserNumber++$Status = "Checking SharePoint activities for " + $U.DisplayName + " ["+ $UserNumber +"/" + $Users.Count + "]"Write-Progress -Activity "Processing audit data" -Status $Status -PercentComplete $PercentComplete$PercentComplete += $ProgressDelta$UserRecords = $Report | ? {$_.UPN -eq $U.UserPrincipalName}If ($UserRecords) {$UserReports++Write-Host "Writing out data for" $U.DisplayName$FileName = "c:\Temp\AuditHistory" + $U.UserPrincipalName + ".csv"$UserRecords | Export-CSV -NoTypeInformation $FileName }}$OutputFiles = Get-ChildItem -Path c:\temp\ |?{$_.LastWriteTime -ge $StartProcessing}Write-Host " "Write-Host "All done." $UserReports "audit reports for SharePoint activity are available in c:\temp\"Write-Host "------------------------------------------------------------------------------"$OutputFiles | ?{$_.Name -Like "*AuditHistory*"} | Ft Name
Parameters
ParameterDefaultNotes
-LookbackDays90How many days back to search unified audit log records.-StartDate(Get-Date).AddDays(-90)Start of the audit log search window.-EndDate(Get-Date).AddDays(1)End of the audit log search window.Attribution
Author
Office365itpros