Entra / Microsoft 365 · Compliance & audit
Auto-apply retention labels
Applies retention labels to files in SharePoint Online and OneDrive for Business sites.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes RecordsManagement.ReadWrite.All, Sites.Read.All
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
function Get-DriveItems {[CmdletBinding()]param ([Parameter()]$Drive,[Parameter()]$FolderId)# Get data for a folder and its children, check each file to see if it has a retention label. If it has, and the# retention threshold is not met, apply the default retention label defined in the script[array]$Data = Get-MgDriveItemChild -DriveId $Drive -DriveItemId $FolderId -All# Split the data into files and folders[array]$Folders = $Data | Where-Object {$_.folder.childcount -gt 0} | Sort-Object Name[array]$Files = $Data | Where-Object {$null -ne $_.file.mimetype}# Process the filesForEach ($File in $Files) {# Get retention label information$FileExtension = $File.Name.Split(".")[1].ToUpper()If ($FileExtension -notin $SupportedFileTypes) {continue}If ($File.CreatedDateTime) {$FileCreatedDateTime = Get-Date $File.CreatedDateTime -format 'dd-MMM-yyyy HH:mm'}# Is the file already older than the retention period? If so, we leave it aloneIf ($File.LastModifiedDateTime) {[datetime]$LastModifiedDateTime = Get-Date $File.LastModifiedDateTimeIf ($LastModifiedDateTime -lt $RetentionDuration) {Write-Host ("File {0} last modified {1} is older than retention period" -f $File.Name, $LastModifiedDateTime)$ReportLine = [PSCustomObject]@{TimeStamp = (Get-Date -format s)FileName = $File.NameFolder = $File.parentreference.nameCreated = $FileCreatedDateTimeAuthor = $File.CreatedBy.User.DisplayNameLastModified = Get-Date $LastModifiedDateTime -format 'dd-MMM-yyyy HH:mm''Last modified by' = $File.LastModifiedBy.User.DisplayName'Retention label' = $RetentionLabel['Name']Path = $Site.WebUrlAction = "Retention label not applied - modified date older than retention period"}$ReportData.Add($ReportLine)Continue}} Else {$LastModifiedDateTime = $null}Try {$FileInfo = Get-MgDriveItemRetentionLabel -DriveId $Drive -DriveItemId $File.Id -ErrorAction Stop} Catch {Write-Host ("Error reading retention label data from file {0}" -f $File.Name)Continue}If ([string]::IsNullOrEmpty($FileInfo.Name)) {# Attempt to apply the defined retention labelIf (!$PreviewMode) {$Status = Update-MgDriveItemRetentionLabel -DriveId $Drive -DriveItemId $File.Id -BodyParameter $RetentionLabelIf ($Status.Name) {Write-Host ("Retention label assigned to {0}" -f $File.Name) -ForegroundColor DarkGray$ReportLine = [PSCustomObject]@{TimeStamp = (Get-Date -format s)FileName = $File.NameFolder = $File.parentreference.nameCreated = $FileCreatedDateTimeAuthor = $File.CreatedBy.User.DisplayNameLastModified = Get-Date $LastModifiedDateTime -format 'dd-MMM-yyyy HH:mm''Last modified by' = $File.LastModifiedBy.User.DisplayName'Retention label' = $RetentionLabel['Name']Path = $Site.WebUrlAction = "Retention label applied"}$ReportData.Add($ReportLine)} Else {Write-Host ("Unable to assign retention label to {0}" -f $File.Name) -ForegroundColor RedContinue}} Else {# Preview Mode, so just log the fact that we would have applied the label$ReportLine = [PSCustomObject]@{TimeStamp = (Get-Date -format s)FileName = $File.NameFolder = $File.parentreference.nameCreated = $FileCreatedDateTimeAuthor = $File.CreatedBy.User.DisplayNameLastModified = Get-Date $LastModifiedDateTime -format 'dd-MMM-yyyy HH:mm''Last modified by' = $File.LastModifiedBy.User.DisplayName'Retention label' = $RetentionLabel['Name']Path = $Site.WebUrlAction = "Retention label not applied (preview mode)"}$ReportData.Add($ReportLine)}}}# Process the folders found in the rootForEach ($Folder in $Folders) {Write-Host ("Processing folder {0}" -f $Folder.Name) -ForegroundColor GreenGet-DriveItems -Drive $Drive -FolderId $Folder.Id}}# Start of Main Scriptparam ([switch]$Global:PreviewMode)if ($PreviewMode) {Write-Host "Running in preview mode. No changes will be made."}# Start of real workConnect-MgGraph -Scopes RecordsManagement.ReadWrite.All, Sites.Read.All$LocationsFile = 'c:\temp\Locations.csv'If (!(Test-Path $LocationsFile)) {Write-Host "Locations file for unlabeled files not found - exiting"Break}[array]$AllLocations = Import-Csv -Path $LocationsFile# Exclude all OneDrive for Business sites[array]$Locations = $AllLocations | Where-Object {$_.Location -notlike "*my.sharepoint.com/personal*"}# Define default retention label to apply$Global:RetentionLabel = @{}$RetentionLabel.Add("Name","General Purpose Information")$Global:RetentionDuration = (Get-Date).AddYears(-3)# Supported file types that we will apply retention labels to$Global:SupportedFileTypes = "DOCX", "PPTX", "XLSX", "PDF"# Output PowerShell lists for reports$Global:ReportData = [System.Collections.Generic.List[Object]]::new()$Global:ProblemSites = [System.Collections.Generic.List[Object]]::new()# Let people know what we plan to doIf ($PreviewMode) {Write-Host "Running in preview mode. No retention labels will be applied to files."}# Try to access each site. If we can gain access, look for unlabeled filesForEach ($Location in $Locations) {$Uri = $Location.Location; $Site = $null; $LookupUri = $null# Create a value that we can find with a call to Get-MgSite. The resulting value will be something like# office365itpros.sharepoint.com:/sites/SeniorTeam# $LookUpUri = $Uri.Split('//')[1].split("/")[0] + ":/sites/" + $Uri.Split('//')[1].split("/")[2]Try {$Global:Site = Get-MgSite -Search $Uri -ErrorAction Stop} Catch {# Try to find the site with the site nameTry {$Global:Site = Get-MgSite -Search $Uri.Split("/sites/")[1] -ErrorAction Stop} Catch {Write-Host ("Unable to access site {0} {1}" -f $Uri, $_.Exception.Message) -ForegroundColor Red# Log the problem site$ProblemSiteReportLine = [PSCustomObject]@{TimeStamp = (Get-Date -format s)Site = $UriAction = "Unable to access site"}$ProblemSites.Add($ProblemSiteReportLine)Continue}}If (!$Site) {Write-Host ("Site {0} not found" -f $Uri) -ForegroundColor RedContinue} Else {Write-Host ("Processing site {0} to look for unlabeled files" -f $Site.DisplayName) -ForegroundColor Yellow}Try {[array]$AllDrives = Get-MgSiteDrive -SiteId $Site.Id -ErrorAction Stop} Catch {Write-Host ("Unable to access drives in site {0} ({1}) {2}" -f $Site.DisplayName, $URI, $_.Exception.Message) -ForegroundColor Red$ProblemSiteReportLine = [PSCustomObject]@{TimeStamp = (Get-Date -format s)Site = $UriAction = "Unable to access drives"}$ProblemSites.Add($ProblemSiteReportLine)Continue}[array]$Drives = $AllDrives | Where-Object {$_.Name -notlike "*Preservation Hold Library*" -and $_.Name -notlike "*Teams Wiki Data*"}If (!$Drives) {Write-Host "No drives found in site $URI"Continue}# Process each driveForEach ($Drive in $Drives) {Write-Host ("Processing drive {0} in site {1}" -f $Drive.Name, $Site.DisplayName) -ForegroundColor CyanGet-DriveItems -Drive $Drive.Id -FolderId "root"}}Write-Host "The following sites could not be processed"$ProblemSites | Format-Table Site, Action -AutoSize[array]$SitesWithLabeledFiles = $ReportData | Where-Object {$_.Action -eq 'Retention label applied'} | Select-Object -ExpandProperty Path | Sort-Object -UniqueWrite-Host ""If ($PreviewMode) {Write-Host "Retention Labeling Run Statistics (Preview mode)"Write-Host "------------------------------------------------"} Else {Write-Host "Retention Labeling Run Statistics"Write-Host "--------------------------------"}Write-Host ""Write-Host ("Retention label used: {0}" -f $RetentionLabel['Name'])Write-Host ("Retention date threshold: {0} ({1} days)" -f $RetentionDuration, (New-TimeSpan -Start $RetentionDuration).Days)Write-Host ("Total {0} of files processed in {1} locations" -f $ReportData.Count, ($Locations.count - $ProblemSites.count))If ($PreviewMode) {Write-Host ("Retention labels not applied because of preview mode: {0}" -f ($ReportData | Where-Object {$_.Action -eq "Retention label not applied (preview mode)"}).Count)} Else {Write-Host ("Retention labels applied: {0}" -f ($ReportData | Where-Object {$_.Action -eq "Retention label applied"}).Count)}Write-Host ("Files older than the threshold to apply retention label: {0}" -f ($ReportData | Where-Object {$_.Action -eq "Retention label not applied - modified date older than retention period"}).Count)Write-Host ""Write-Host "Sites where labels were applied:"$SitesWithLabeledFilesWrite-Host ""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) + "\Retention Labeling.xlsx"If (Test-Path $ExcelOutputFile) {Remove-Item $ExcelOutputFile -ErrorAction SilentlyContinue}$ReportData | Export-Excel -Path $ExcelOutputFile -WorksheetName "Retention Labeling Report" -Title ("Retention Labeling Report") -TitleBold -TableName "RetentionLabels"} Else {$CSVOutputFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\Retention Labeling.CSV"$ReportDtra | Export-Csv -Path $CSVOutputFile -NoTypeInformation -Encoding Utf8}If ($ExcelGenerated) {Write-Host ("An Excel report is available in {0}" -f $ExcelOutputFile)} Else {Write-Host ("A CSV report is available in {0}" -f $CSVOutputFile)}
Attribution
Author
Office365itpros