Migrate sensitivity labels to the files in a new tenant!

You have just finished migrating files in SharePoint from one tenant to another. What happens to the sensitivity labels? When files are moved to a new tenant, the sensitivity labels are lost. Even if the labels have the same name in the new tenant, they don’t have the same ID. There is also a chance you are migrating into a tenant that has completely different naming for the labels.

I have created a PowerShell script that finds all the sensitivity labels set on files in the source tenant, then finds the files in the destination tenant and sets the original label. I also have a lookup table in the script, so you can map labels if the names are different in the destination tenant.

You need two app registrations. Also, the destination app needs to be associated with an Azure subscription, as the assignment of the sensitivity label is a metered API. (https://learn.microsoft.com/en-us/graph/metered-api-setup)

Destination: Directory.Read.All, Files.ReadWrite.All, Sites.Read.All, and InformationProtectionPolicy.Read.All

Source: Directory.Read.All, Files.Read.All, Sites.Read.All, and InformationProtectionPolicy.Read.All

# Alexander Holmeset
# Version 1.0
# This script is designed to extract sensitivity labels from files in SharePoint document libraries in one tenant and apply them to the same files in another tenant.
# It uses Microsoft Graph API to interact with SharePoint and sensitivity labels.
# The script connects to two different tenants using app registrations and retrieves files from SharePoint document libraries.
# App regstiration permissions source: Directory.Read.All, Files.Read.All, Sites.Read.All and InformationProtectionPolicy.Read.All
# App regstiration permissions target: Directory.Read.All, Files.ReadWrite.All, Sites.Read.All and InformationProtectionPolicy.Read.All
# Parameters for the app registration source tenant
$ClientId = "xxxxxxx"
$TenantId = "xxxxxxx"
$ClientSecret = "xxxxxxx"
# Parameters for the app registration target tenant
$ClientId2 = "xxxxxxx"
$TenantId2 = "xxxxxxx"
$ClientSecret2 = "xxxxxxx"
# File types to check
$filetypes = "docx", "docm", "xlsx", "xlsm", "xlsb", "pptx", "ppsx", "pdf"
# Define the hash table with lookup values. This is used to map the sensitivity labels to their corresponding values in the new tenant.
# The keys are the sensitivity labels from the source tenant, and the values are the corresponding labels in the target tenant.
# You can add more mappings as needed.
$lookupTable = @{
"Internal" = "InternalRestricted"
"Public" = "PublicUnrestricted"
"Confidential" = "ConfidentialRestricted"
}
# Convert the Client Secret to a SecureString
$SecureClientSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
# Create a PSCredential object with the Client ID and Secure Client Secret
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ClientId, $SecureClientSecret
# Connect to Microsoft Graph using the Tenant ID and Client Secret Credential in the source tenant
Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential
# Initialize the output array
$OutputData = @()
# Function to get all items in a drive (document library) recursively
function Get-AllDriveItems {
param (
[Parameter(Mandatory)]
[string]$DriveId,
[Parameter()]
[string]$ParentId,
[Parameter()]
[string]$CurrentPath = ""
)
$Items = @()
if ($ParentId) {
# Write-Host "Processing folder with ParentId: $ParentId" -ForegroundColor Cyan
try {
$ParentItem = Get-MgDriveItem -DriveId $DriveId -DriveItemId $ParentId -ExpandProperty 'children' -ErrorAction Stop
$DriveItems = $ParentItem.Children
if($DriveItems) {
Write-Host "Found $($DriveItems.Count) child items in folder '$($ParentItem.Name)' (ParentId: $ParentId)" -ForegroundColor Green
} else {
Write-Host "No child items found in folder '$($ParentItem.Name)' (ParentId: $ParentId)" -ForegroundColor Yellow
}
} catch {
Write-Warning "Failed to get children for item ID '$ParentId'. Error: $_"
return $Items
}
} else {
Write-Host "Processing root folder" -ForegroundColor Cyan
try {
$RootItem = Get-MgDriveRoot -DriveId $DriveId -ExpandProperty 'children' -ErrorAction Stop
$DriveItems = $RootItem.Children
if($DriveItems) {
Write-Host "Found $($DriveItems.Count) items in root folder" -ForegroundColor Green
} else {
Write-Host "No items found in root folder" -ForegroundColor Yellow
}
} catch {
Write-Error "Failed to get root item and its children. Error: $_"
return $Items
}
}
if (-not $DriveItems) {
Write-Verbose "No items found at this level."
return $Items
}
foreach ($Item in $DriveItems) {
# Update the file path
if ($CurrentPath -eq "") {
$ItemPath = "/$($Item.Name)"
} else {
$ItemPath = "$CurrentPath/$($Item.Name)"
}
# Add the ItemPath property to the item
$Item | Add-Member -NotePropertyName 'ItemPath' -NotePropertyValue $ItemPath
$Items += $Item
Write-Host "Found item: $($Item.Name) (Id: $($Item.Id))" -ForegroundColor Green
if ($Item.Folder) {
Write-Host "Recursing into folder: $($Item.Name) (Id: $($Item.Id))" -ForegroundColor Cyan
if ($Item.Id) {
$Items += Get-AllDriveItems -DriveId $DriveId -ParentId $Item.Id -CurrentPath $ItemPath
} else {
Write-Warning "Item '$($Item.Name)' has a null ID and cannot be processed."
}
}
}
return $Items
}
# Now, process all SharePoint sites and their document libraries
Write-Host "Starting to process all SharePoint sites and their document libraries..." -ForegroundColor Magenta
try {
# Retrieve all SharePoint sites
$sites = Get-MgSite -All | Where-Object { $_.WebUrl -notlike "*personal*" }
} catch {
Write-Error "Failed to retrieve SharePoint sites. Error: $_"
exit
}
foreach ($site in $sites) {
try {
Write-Host "Processing site: $($site.DisplayName) <$($site.WebUrl)>" -ForegroundColor Magenta
# Get all document libraries (drives) in the site
$drives = Get-MgSiteDrive -SiteId $site.Id -ErrorAction Stop
foreach ($drive in $drives) {
Write-Host "Processing document library: $($drive.Name)" -ForegroundColor Cyan
$driveId = $drive.Id
# Get all items in the document library starting from the root
$AllItems = Get-AllDriveItems -DriveId $driveId
Write-Host "Total items retrieved in document library '$($drive.Name)': $($AllItems.Count)" -ForegroundColor Green
foreach ($Item in $AllItems) {
Write-Host "Processing item: $($Item.Name) (Id: $($Item.Id))" -ForegroundColor Gray
# Check if item is a file
if ($Item.File) {
try {
# Get versions for the item
$labels = @()
If($filetypes -contains (($item.ItemPath).split(".")[1]) ){
write-Host "Filetype: $($item.ItemPath).split(".")[1]" -ForegroundColor Gray
$labels = (Invoke-GraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/drives/$($driveId)/items/$($Item.Id)/extractSensitivityLabels&quot; -ContentType "application/json" -ErrorAction Stop).labels
if($labels){
foreach ($label in $labels) {
$labelname = @()
$labelname = (Invoke-GraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/security/informationProtection/sensitivityLabels/$($label.sensitivityLabelId)&quot; -ContentType "application/json" -ErrorAction Stop).name
Write-Host "Sensitivity label: $($label.name)" -ForegroundColor Yellow
$OutputData += [PSCustomObject]@{
SiteName = $site.DisplayName
LibraryName = $drive.Name
ItemName = $Item.Name
ItemPath = $Item.ItemPath
ItemId = $Item.Id
SensitivityLabel = $labelname
}
}
}
}
} catch {
Write-Warning "Failed to get versions for item '$($Item.Name)'. Error: $_"
continue
}
}
}
}
} catch {
Write-Warning "Failed to process site $($site.DisplayName). Error: $_"
continue
}
}
Disconnect-MgGraph
# Convert the Client Secret to a SecureString
$SecureClientSecret2 = ConvertTo-SecureString -String $ClientSecret2 -AsPlainText -Force
# Create a PSCredential object with the Client ID and Secure Client Secret
$ClientSecretCredential2 = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ClientId2, $SecureClientSecret2
# Connect to Microsoft Graph using the Tenant ID and Client Secret Credential
Connect-MgGraph -TenantId $TenantId2 -ClientSecretCredential $ClientSecretCredential2
# Now, process all SharePoint sites and their document libraries
Write-Host "Starting to process all SharePoint sites and their document libraries..." -ForegroundColor Magenta
try {
# Retrieve all SharePoint sites
$sites = Get-MgSite -All | Where-Object { $_.WebUrl -notlike "*personal*" }
} catch {
Write-Error "Failed to retrieve SharePoint sites. Error: $_"
exit
}
$LabelsDestination = (Invoke-GraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/security/informationProtection/sensitivityLabels/&quot; -ContentType "application/json" -ErrorAction Stop | ConvertTo-Json | ConvertFrom-Json).value
foreach ($site in $sites) {
try {
#Write-Host "Processing site: $($site.DisplayName) <$($site.WebUrl)>" -ForegroundColor Magenta
# Get all document libraries (drives) in the site
$drives = Get-MgSiteDrive -SiteId $site.Id -ErrorAction Stop
foreach ($drive in $drives) {
#Write-Host "Processing document library: $($drive.Name)" -ForegroundColor Cyan
$driveId = $drive.Id
# Get all items in the document library starting from the root
$AllItems = Get-AllDriveItems -DriveId $driveId
#Write-Host "Total items retrieved in document library '$($drive.Name)': $($AllItems.Count)" -ForegroundColor Green
foreach ($Item in $AllItems) {
#Write-Host "Processing item: $($Item.Name) (Id: $($Item.Id))" -ForegroundColor Gray
# Check if item is a file
if ($Item.File) {
try {
# Get versions for the item
$labels = @()
If($filetypes -contains (($item.ItemPath).split(".")[1]) ){
#Find file in source tenant
$labels = $OutputData | Where-Object{$_.SiteName -contains $site.DisplayName -and $_.LibraryName -contains $drive.Name -and $_.ItemName -contains $Item.Name -and $_.ItemPath -contains $Item.ItemPath}
# If there are labels, process them
if($labels){
foreach ($label in $labels) {
# Define the variable to check
$valueToCheck = "$($label.SensitivityLabel)"
$resultLabel = @()
# Lookup the value in the hash table
if ($lookupTable.ContainsKey($valueToCheck)) {
$resultLabel = $lookupTable[$valueToCheck]
Write-Output "The value for '$valueToCheck' is '$resultLabel'."
} else {
Write-Output "The value '$valueToCheck' is not found in the lookup table."
$resultLabel = $($label.SensitivityLabel)
}
$newlabel = @()
$newlabel = $LabelsDestination | Where-Object{$_.name -contains $resultLabel}
Write-Host "Setting sensitivity label: $($newlabel.id) $($resultLabel)" -ForegroundColor Yellow
Set-MgDriveItemSensitivityLabel -DriveId $driveId -DriveItemId $Item.Id -SensitivityLabelId $newlabel.id -ErrorAction Stop
exit
}
}
}
} catch {
Write-Warning "Failed to get versions for item '$($Item.Name)'. Error: $_"
continue
}
}
}
}
} catch {
Write-Warning "Failed to process site $($site.DisplayName). Error: $_"
continue
}
}

2 thoughts on “Migrate sensitivity labels to the files in a new tenant!

    • ah, that’s true, thanks for the reminder.
      Strange thing is that I didn’t do anything about it while testing and it worked. Could be something I have done previously.
      Il update the article anyways.

      Like

Leave a reply to Tony Redmond Cancel reply