In modern enterprise environments, sensitivity labels and encryption policies are critical for protecting data. However, there are scenarios—such as data migration, restructuring, or administrative overrides—where these protections need to be temporarily removed. This blog post walks through a PowerShell script designed to automate the removal of Microsoft Purview sensitivity labels and encryption from files stored in SharePoint Online document libraries.
This script is particularly useful for IT administrators managing large-scale SharePoint environments where manual intervention would be inefficient and error-prone. It leverages the PnP PowerShell module and Microsoft Entra ID app registrations to authenticate and perform operations across multiple sites.
Requirements
To run this script, you will need PowerShell 7 installed along with the PnP.PowerShell module. You will also need four app registrations within Entra ID that are generated by PnP.Powershell. For more information on creating these app registrations, see https://pnp.github.io/powershell/articles/registerapplication.html.
Script
Here is the full script. You can copy and paste this to VS Code for modification. See below the script for an explanation of what the script does and what you might need to change.
# RUN FIRST BY ITSELF, NOT AS PART OF WHOLE SCRIPT EXECUTION if ($cred -eq $null) {$cred = get-credential} $temp_owner = "admin@domain.onmicrosoft.com" # temporary site collection administrator (should be account running script) $justification_text = "Administrator Change." # justification to provide when label is removed $pnp_clientID_1 = "<app id 1>" # application ID of Pnp.Powershell Entra ID app $pnp_clientID_2 = "<app id 2>" # application ID of Pnp.Powershell Entra ID app $pnp_clientID_3 = "<app id 3>" # application ID of Pnp.Powershell Entra ID app $pnp_clientID_4 = "<app id 4>" # application ID of Pnp.Powershell Entra ID app $SP_domain = "<sp domain>" # part between https:// and .sharepoint.com $log_file = 'C:\Users\admin\Downloads\remove-label-SP.txt' $completed_log = 'C:\Users\admin\Downloads\remove-label-completed-SP.txt' $sites = import-csv 'C:\Users\admin\Downloads\Data explorer _ Microsoft Purview-SP.csv' # sites to process. exported from Purview reports. # create log files $date = get-date -format "MM-dd-yyyyTHH:mm:ss" $f_log_file = ($log_file.replace(".txt","_$date.txt")).replace(":","_") $f_log_file = $f_log_file.replace("C_","C:") if ((test-path $f_log_file) -eq $false) {new-item -type file $f_log_file | out-null} add-content $f_log_file "$date SCRIPT_START" if ((test-path $completed_log) -eq $false) {new-item -type file $completed_log | out-null} add-content $completed_log "$date SCRIPT_START" $date = "" # connect to SharePoint $pnp_url = "https://" + $SP_domain + ".sharepoint.com/" $pnp_clientID = $pnp_clientID_1 connect-pnponline -url $pnp_url -clientid $pnp_clientID -credential $cred add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") CONNECTED USING APP REG 1" # set up progress bar $count = 0 $total = $sites.count ForEach ($site in $sites) { # set counts for progress bar $count = $count + 1 $percent = $count/$total $percent_readable = $percent.tostring("P") $percent_bar = $percent * 100 # display progress bar write-progress "processing $count of $total - $percent_readable complete." -PercentComplete $percent_bar $site_url = $site.URL add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") START $site_url" # connect to site set-pnptenantsite $site_url -owners $temp_owner connect-pnponline -url $site_url -clientid $pnp_clientID -credential $cred # Get All Files from the document library - In batches of 500 $lists = get-pnplist | where-object -filterscript {$_.BaseTemplate -eq "101" -AND $_.ItemCount -ne "0" -AND $_.Title -ne "_catalogs/hubsite" -AND $_.Title -ne "Style Library" -AND $_.Title -notlike "PersistedManagedNavigationList*"} $list_count = $lists.count add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") FOUND $list_count List(s)" ForEach ($list in $lists) { $list_title = $list.Title add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") PROCESSING $list_title" $list_items = Get-PnPListItem -List $list_title -pagesize 4000 | where-object -filterscript {$_.FieldValues['_DisplayName'] -like "L2*"} # set up progress bar $count = 0 $total = $list_items.count $list_items_count = $list_items.count add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") FOUND $list_items_count Files with L2 Label" $list_processing_count = 0 do { ForEach ($item in $list_items) { # set counts for progress bar $count = $count + 1 $percent = $count/$total $percent_readable = $percent.tostring("P") $percent_bar = $percent * 100 # display progress bar write-progress "processing $count of $total - $percent_readable complete." -PercentComplete $percent_bar $file_url = $Item.FieldValues['FileRef'] $url = ($pnp_url + $file_url).replace(".com//",".com/") try { Unlock-PnPSensitivityLabelEncryptedFile -Url $url -JustificationText $justification_text -erroraction stop add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") RELABELED $file_url" } catch { add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") ERR_RELAB $file_url $($_.Exception.Message)" if ($($_.Exception.Message) -eq "ResourceBudgetExceeded") { $id_rotated = 0 write-host "ResourceBudgetExceeded. Changing App Reg." -foregroundcolor red if ($pnp_clientID -eq $pnp_clientID_1 -AND $id_rotated -eq 0) { $pnp_clientID = $pnp_clientID_2 connect-pnponline -url $site_url -clientid $pnp_clientID -credential $cred write-host "changed from client id 1 to client ID 2" -foregroundcolor yellow add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") CHANGED TO APP REG 2" $id_rotated = 1 start-sleep -s 120 } if ($pnp_clientID -eq $pnp_clientID_2 -AND $id_rotated -eq 0) { $pnp_clientID = $pnp_clientID_3 connect-pnponline -url $site_url -clientid $pnp_clientID -credential $cred write-host "changed from client id 2 to client ID 3" -foregroundcolor yellow add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") CHANGED TO APP REG 3" $id_rotated = 1 start-sleep -s 120 } if ($pnp_clientID -eq $pnp_clientID_3 -AND $id_rotated -eq 0) { $pnp_clientID = $pnp_clientID_4 connect-pnponline -url $site_url -clientid $pnp_clientID -credential $cred write-host "changed from client id 3 to client ID 4" -foregroundcolor yellow add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") CHANGED TO APP REG 4" $id_rotated = 1 start-sleep -s 120 } if ($pnp_clientID -eq $pnp_clientID_4 -AND $id_rotated -eq 0) { $pnp_clientID = $pnp_clientID_1 connect-pnponline -url $site_url -clientid $pnp_clientID -credential $cred write-host "changed from client id 4 to client ID 1" -foregroundcolor yellow add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") CHANGED TO APP REG 1" $id_rotated = 1 start-sleep -s 120 } } } $list_processing_count++ $percent = 0 $percent_readable = "" $percent_bar = 0 $file_url = "" $url = "" start-sleep -seconds 1 } } until ($list_processing_count -eq $list_items_count) add-content -path $f_log_file -value "$(Get-Date -format "MM-dd-yyyyTHH:mm:ss") COMPLETED $list_title" $list_title = "" $list_items = "" $list_items_count = 0 } remove-pnpsitecollectionadmin -owners $temp_owner $date = get-date -format "MM-dd-yyyyTHH:mm:ss" add-content -path $f_log_file -value "$date COMPLETED $site_url" add-content -path $completed_log -value "$date COMPLETED $site_url" write-host "$date COMPLETED $site_url" -foregroundcolor green $date = "" $site_url = "" $lists = "" $list_count = "" } $date = get-date -format "MM-dd-yyyyTHH:mm:ss" add-content -path $f_log_file -value "$date SCRIPT_END" add-content -path $completed_log -value "$date SCRIPT_END" $date = ""
Script Overview and Breakdown
1. Credential Handling
The script begins by checking if credentials are already stored. If not, it prompts the user to enter them:
if ($cred -eq $null) {$cred = get-credential}
Note that this needs to be run independently of the script. This will also need to be an account that is in Entra ID with at least SharePoint Administrator access. This account also either needs to be either temporarily excluded from MFA or have a policy in Entra ID Conditional Access temporarily applied to it so that it is not prompted to MFA. Setting up either of these options is out of scope for this blog post.
2. Variable Initialization
Next, variables are configured to assist with script execution. Follow the notes next to the variables for an explanation of the variable. Note that the site collection administrator should be the same account used above.
3. Logging
Two log files are created: one for general logging and another to track completed sites.
4. CSV Input
The script also imports a CSV file containing the list of SharePoint sites to process—typically exported from Microsoft Purview reports.
5. Connect to SharePoint
The script connects to SharePoint Online using the first app registration ID.
6. Identifying and Processing Labeled Files
Within each library, the script identifies files with the label specified in the variable.
7. Throttling and App Registration Rotation
Microsoft 365 APIs enforce throttling limits. If the script encounters a ResourceBudgetExceeded error, it automatically rotates to the next app registration ID and reconnects. This built-in resilience allows the script to continue processing without manual intervention.
8. Cleanup and Final Logging
After processing each site, the script removes the temporary admin and logs the completion. This ensures that permissions are not left elevated and that the process is fully auditable.
Conclusion
This PowerShell script is a powerful tool for SharePoint Online administrators needing to remove sensitivity labels and encryption at scale. It automates a complex, multi-step process while incorporating robust error handling, logging, and authentication management.
By leveraging Microsoft Purview reports, PnP PowerShell, and app registration rotation, the script ensures compliance and efficiency in sensitive data operations. Whether you’re preparing for a migration, restructuring your information architecture, or responding to an urgent business need, this script can save hours of manual work and reduce the risk of human error.
Disclaimer
All content provided on this blog is for information purposes only. Windows Management Experts, Inc. makes no representation as to the accuracy or completeness of any information on this site. Windows Management Experts, Inc. will not be liable for any errors or omissions in this information nor for the availability of this information. It is highly recommended that you consult one of our technical consultants, should you need any further assistance.
Windows Management Experts
Now a Microsoft Solutions Partner for:
✓ Data & AI
✓ Digital and App Innovation
✓ Infrastructure
✓ Security
The Solutions Partner badge highlights WME’s excellence and commitment. Microsoft’s thorough evaluation ensures we’re skilled, deliver successful projects, and prioritize security over everything. This positions WME in a global tech community, ready to innovate on the cloud for your evolving business needs.
Contact us: sales@winmgmtexperts.com