Entra / Microsoft 365 · SharePoint & OneDrive
Decrypt protected SharePoint documents with Graph
Uses Microsoft Graph to find encrypted SharePoint files, then decrypts them with Unlock-SPOSensitivityLabelEncryptedFile. Requires PowerShell 7+.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
# Review required modules and connection steps before running.# Connect to Microsoft Graph or Exchange Online as needed for this script.
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = "",[string] $AppId = "")If (!$SearchFolder) {Write-Host "We're going to search" $SearchSite }Else {Write-Host "We're going search" $SearchSite "site and folder" $SearchFolder }# These values need to be changed to reflect the registered app (in Azure AD) and tenant details$AppSecret = "Dz6A1tbmc1Z-H4qsJxALy.-JR2_gj-1E~1"# Construct URI and body needed for authentication$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"$body = @{client_id = $AppIdscope = "https://graph.microsoft.com/.default"client_secret = $AppSecretgrant_type = "client_credentials" }# Get OAuth 2.0 Token$tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing# Unpack Access Token$token = ($tokenRequest.Content | ConvertFrom-Json).access_token# Base URL$headers = @{Authorization = "Bearer $token"}# https://graph.microsoft.com/v1.0/sites?search=* for all sites# https://graph.microsoft.com/v1.0/sites?search="Corporate Accounting (Billing)"$URI = "https://graph.microsoft.com/v1.0/sites?search='$($SearchSite)'"[array]$Site = (Invoke-RestMethod -Uri $URI -Headers $Headers -Method Get -ContentType "application/json")If ($Site.Value.Count -eq 0) { # Nothing foundWrite-Host "No matching sites found - exiting"; break }If ($Site.Value.Count -eq 1) { # Only one site found - go ahead$SiteId = $Site.Value.Id$SiteName = $Site.Value.DisplayNameWrite-Host "Found site to process:" $SiteName }Elseif ($Site.Value.Count -gt 1) { # More than one site found. Ask which to useCLS; Write-Host "More than one matching site was found."; [int]$i=1ForEach ($SiteOption in $Site.Value) {Write-Host $i ":" $SiteOption.DisplayName; $i++}[Int]$Answer = Read-Host "Enter the number of the site to use"If (($Answer -gt 0) -and ($Answer -le $i)) {[int]$Si = ($Answer-1)$SiteName = $Site.Value[$Si].DisplayNameWrite-Host "OK. Selected site is" $Site.Value[$Si].DisplayName$SiteId = $Site.Value[$Si].Id }}If ($SearchFolder) { # We've been asked to look in a specific folder, so find its drive id$Uri = "https://graph.microsoft.com/v1.0/sites/$($Siteid)/lists/Documents/Drive/root/children"$SiteData = (Invoke-RestMethod -Uri $URI -Headers $Headers -Method Get -ContentType "application/json")# Look for the target folder in the set of resources returned$TargetFolder = $SiteData.Value | Where-Object {$_.Name -eq $SearchFolder -and $_.Folder -ne $Null}If ($TargetFolder) { # We found the folder$DriveId = $TargetFolder.Id }Else { # We didn't... so exit to let the user try again$DriveId = $NullWrite-Host "Can't find the" $SearchFolder "folder" in the $SiteName "site"; break }}Else { # Search folder isn't defined, so we look in the default folder of the document library in chosen site$Uri = "https://graph.microsoft.com/v1.0/sites/$($Siteid)/lists/Documents/Drive/root/"$SiteData = (Invoke-RestMethod -Uri $URI -Headers $Headers -Method Get -ContentType "application/json")$TargetFolder = $SiteData.Id }# Retrieve files in the folder, including sensitivity label info. SharePoint returns a default of 200 files per call, so we use the nextlink to keep on fetching files until we are done$BaseUrl = $TargetFolder.WebUrl + "/"$Report = [System.Collections.Generic.List[Object]]::new() # Create output fileWrite-Host "Searching for files in the target site/folder"If (!$SearchFolder) { # Search the root folder of the site$Uri = "https://graph.microsoft.com/v1.0/sites/$($Siteid)/lists/Documents/Drive/root/children?`$select=sensitivitylabel,weburl,name" }Else { # Search the nominated folder$Uri = "https://graph.microsoft.com/v1.0/sites/$($Siteid)/lists/Documents/Drive/Items/$($DriveId)/children?`$select=sensitivitylabel,weburl,name"}$Files = (Invoke-RestMethod -Uri $URI -Headers $Headers -Method Get -ContentType "application/json")$FilesCount = $Files.Value.CountIf ($FilesCount -eq 0) { # No files found in that locationWrite-Host "No files can be found in that location - exiting";break}ForEach ($File in $Files.Value) {If ($File.SensitivityLabel.ProtectionEnabled -eq $True) {$FileName = $BaseUrl + $File.Name$ReportLine = [PSCustomObject] @{File = $File.NameFileURL = $FileNameLabel = $File.SensitivityLabel.DisplayNameLabelGuid = $File.SensitivityLabel.Id }$Report.Add($ReportLine) } #End If} # End For$NextLink = $Files.'@Odata.NextLink'While ($NextLink -ne $Null) {$Files = (Invoke-RestMethod -Uri $Nextlink -Headers $Headers -Method Get -ContentType "application/json")$FilesCount = $Files.Value.Count + $FilesCountForEach ($File in $Files.Value) {If ($File.SensitivityLabel.ProtectionEnabled -eq $True) {$FileName = $BaseUrl + $File.Name$ReportLine = [PSCustomObject] @{File = $File.NameFileURL = $FileNameLabel = $File.SensitivityLabel.DisplayNameLabelGuid = $File.SensitivityLabel.Id }$Report.Add($ReportLine) } #End If} # End For$NextLink = $Files.'@odata.NextLink' }# Prompt to go ahead with decryption after clearing screen.CLS; [int]$i = 0If ($Report.Count -eq 0) {Write-Host "No encrypted files found in" $SiteName "- exiting"; break }Else {Write-Host $Report.Count "of" $FilesCount "files in" $SiteName "have sensitivity labels with encryption" }# List documents found and ask whether to proceed$Report | Format-Table File, Label -AutoSize$PromptTitle = 'Remove Encryption from documents'$PromptMessage = 'Please confirm whether to go ahead and remove encryption from these files'$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&yes", 'yes?'$no = New-Object System.Management.Automation.Host.ChoiceDescription "&no", 'no?'$cancel = New-Object System.Management.Automation.Host.ChoiceDescription "&cancel", 'Exit'$PromptOptions = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $cancel)$PromptDecision = $host.ui.PromptForChoice($PromptTitle, $PromptMessage, $PromptOptions, 0)# Decision made Y or default to go ahead and remove protection from the documents, so let's do it.If ($PromptDecision -eq 0) {ForEach ($F in $Report) {Write-Host "Removing encryption from" $F.FileUnlock-SPOSensitivityLabelEncryptedFile -FileUrl $F.FileUrl -JustificationText "Administrator removed label"$i++ }Write-Host "All done. Encryption removed from $i files" }Else {Write-Host "OK. Details of the encrypted files are in c:\temp\EncryptedDocuments.csv"$Report | Export-CSV -NoTypeInformation c:\temp\EncryptedDocuments.csv }
Parameters
ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.-AppId""Application (client) ID for the app registration used to connect.Attribution
Author
Office365itpros