How to Decrypt Protected SharePoint Files Using PowerShell and the Graph API

Unlocking Protected SharePoint Documents

In my article about how to decrypt SharePoint Online documents with PowerShell, I explained how to use the Unlock-SPOSensitivityLabelEncryptedFile cmdlet to decrypt protected SharePoint files by removing the sensitivity labels protecting the files. The example script uses cmdlets from the SharePoint PnP module to return a set of files from a folder in a document library for processing, and the unlock cmdlet then removes protection from any file with a sensitivity label.

The script works, but it’s not as flexible as I would like. For instance, because PnP can’t distinguish files with labels, every document in the folder is processed whether it is labelled or not. This does no harm, but it’s not something that you might want to do in the case of something like a tenant-to-tenant migration where thousands of protected documents might need to be processed.

Update May 10, 2021: The latest version of the SharePoint Online PowerShell module contains the Get-FileSensitivityLabelInfo cmdlet. This can be run to return the label status of a file, including if the label assigned to the file encrypts the file. The existence of this cmdlet removes some of the need to use the Graph to find and remove labels from protected files, but the Graph is still the fastest way to get the job done.

Using the Sites Microsoft Graph API

Which brings me to an updated version of the script (available from GitHub), which uses the Sites API from the Microsoft Graph to navigate through SharePoint Online and find labelled documents to process. Apart from being able to search for documents with sensitivity labels, a Graph API is usually the fastest way to deal with large numbers of objects.

Because we’re making Graph calls from PowerShell, we need to create a registered app in Azure AD to use as the entry point to the Graph (the same steps as outlined in this post are used). The app needs to be able to read site data, so I assigned it Sites.Read.All and Sites.ReadWrite.All permissions (Figure 1).

Setting API permissions for the Graph app
Figure 1: Setting API permissions for the Graph app

Finding Protected Documents

The script accepts two parameters: the name of the site to search (not the URL) and an optional folder. If multiple matching sites are found, the user is asked to choose which one to search (Figure 2).

Choosing a SharePoint Online site to investigate for protected documents
Figure 2: Choosing a SharePoint Online site to investigate for protected documents

Once a target site is confirmed, the script figures out if a folder is specified and if that folder exists in the chosen site. In Graph terms, we’re now dealing with drive objects. The default drive is the root folder of a document library and each folder is a different drive. To find folders, we need to find the child objects in the root, identify the right folder, find its drive identifier, and use that to find the files in the folder. All good, clean Graph fun.

The Drive API returns a maximum of 200 items at a time, so some Nextlink processing is needed to fetch the complete set of files in a folder. Each file is examined to figure out if it has a sensitivity label with protection, and if so, the display name of the label. After processing all the files, we tell the user what we’ve found and ask permission to go ahead and decrypt the files (Figure 3). If the user chooses not to proceed, the script writes details of the protected files out to a CSV file.

Reporting the protected files found in a folder in a SharePoint Online document library

Decrypt protected SharePoint files
Figure 3: Reporting the protected files found in a folder in a SharePoint Online document library

Decrypting Files

Files are decrypted by calling the Unlock-SPOSensitivityLabelEncryptedFile cmdlet. There’s no native Graph API call to decrypt SharePoint documents. In any case, we’re running a PowerShell script so it’s easy to call the cmdlet.

An Example to Build On

The script is an example of what’s possible with a combination of PowerShell and Graph API calls. I’m sure that the code and the functionality can be improved (feel free to suggest changes and improvements via GitHub). I’m just happy to demonstrate how things work and how including the Graph enables some extra flexibility.


Read the Office 365 for IT Pros eBook to find much more information about how sensitivity labels work – and many PowerShell examples too!

20 Replies to “How to Decrypt Protected SharePoint Files Using PowerShell and the Graph API”

  1. I’ve tried running this on a site that I know for sure has lots of encrypted files but just get the following response “No encrypted files found in Site-Name – exiting”. Any ideas?

      1. These files were originally labelled with AIP scanner does that make any difference?

      1. That’s because the script doesn’t go into subfolders. The code is written to illustrate a principle, not to handle every condition you might like it to. I would never have any time if I tried to do that… So I don’t…

  2. Love this script – shame that Microsoft haven’t got a good native way to decrypt content for tenant to tenant moves. I am running into an issue though with a test environment; the list of protected content is returned but for each item but when attempting to decrypt, see the error “Unlock-SPOSensitivityLabelEncryptedFile : Invalid URI: The format of the URI could not be determined.”. Any ideas?

    1. Have you been able to run the cmdlet interactively with PowerShell? There’s something obviously up with the URI that’s been fed into it. So I would look at the URI and figure out what PowerShell is trying to process by starting with an attempt to remove a label from a single document.

  3. This is a great article, just what I was looking for for our decryption toolbox! – Would you know of an article showing how to use PowerShell to apply a label to selected documents/folders in SPO? Cheers Danny

    1. There’s an API coming. It’s not yet available. I hope to have some information about it before the end of July. However, to set expectations, the API will be throttled and is not designed for high-scale application of labels to documents.

  4. Hi, Thanks for your article !. I tried to found the attribute “sensitivitylabel” from Microsoft Graph but this attribute does not appear in the results even if the file is encrypted. I tested in v1.0 and beta. Do you know why ? For the moment I had to retrieve all the files with graph and verify them with Get-FileSensitivityLabelInfo. Thanks !

    1. I just tested the script again and this code retrieves all files in the target 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”)

      And then this code filters for files with a sensitivity label assigned with protection:

      ForEach ($File in $Files.Value) {
      If ($File.SensitivityLabel.ProtectionEnabled -eq $True) {
      $FileName = $BaseUrl + $File.Name
      $ReportLine = [PSCustomObject] @{
      File = $File.Name
      FileURL = $FileName
      Label = $File.SensitivityLabel.DisplayName
      LabelGuid = $File.SensitivityLabel.Id }
      $Report.Add($ReportLine) } #End If
      } # End For

      It all works. I get something like this:

      File : How to Hide Documents from Delve.docx
      FileURL : https://office365itpros.sharepoint.com/sites/BlogsAndProjects/Shared%20Documents/Blog%20Posts/How to
      Hide Documents from Delve.docx
      Label : Secret
      LabelGuid : 81955691-b8e8-4a81-b7b4-ab32b130bff5

  5. Tony, I have a question regarding the script above. For me it’s not running the way it should, and I refer at line 52 of code, the reason seems to be the site ID value, that instead of being only something like “12345678-12ab-12ab-34ab-123456abcdef”, it is tenant.sharepoint.com,12345678-12ab-12ab-34ab-123456abcdef,1234abcd-df36-49ba-a147-1234abc93582 (the ID’s are not real ID’s, just random numbers. Can you please help me select just the SiteID that is needed in the script line 75

    1. I ran the script and everything went as planned. The $Site variable is filled by executing a Graph request to search for the site passed in $SearchSite. Maybe that variable isn’t populated? When the request runs, it populates the Value property in $Site with details of matching sites. Each site looks like this:

      createdDateTime : 2021-10-30T11:21:58Z
      description : All about PR
      id : office365itpros.sharepoint.com,9ca62cd9-a20e-49c8-b116-c2596a64ddc5,ef300dac-c802-4d79-bc99-07
      43c1b8ba62
      lastModifiedDateTime : 2021-10-16T23:15:28Z
      name : PublicRelations
      webUrl : https://office365itpros.sharepoint.com/sites/PublicRelations
      displayName : Public Relations
      root :
      siteCollection : @{hostname=office365itpros.sharepoint.com}

      $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 found
      Write-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.DisplayName
      Write-Host “Found site to process:” $SiteName }

      1. Tony, I used Graph-explorer, and if I use the variable “$Site, which for you (above) is id : office365itpros.sharepoint.com,9ca62cd9-a20e-49c8-b116-c2596a64ddc5,ef300dac-c802-4d79-bc99-07
        43c1b8ba62, the next variable $SiteId = $Site.Value.ID is the whole string, not just the ID of the site in this case 9ca62cd9-a20e-49c8-b116-c2596a64ddc5. If I use this hardcoded ID it works, and I get a list of files in Graph Explorer.
        Bottom line is that we are trying to get a list of all PDF’s in SharePoint that were encrypted with sensitivity labels, and my knowledge in graph is limited. Using just PowerShell I am limited because of the throttling. Please let me know if you have any suggestions.

      2. I really can’t help too much because I don’t have access to your data. Without that access, it’s impossible for me to debug anything.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.