We have shared files from OneDrive, Teams and SharePoint since we discovered the “Share” button! But have we ever stopped a sharing? Have you ever clicked on a shared file and stopped it, or looked into your OneDrive and had a look at the view “Shared by you“?

In most cases users have no idea that they are still sharing a file. Most don’t have a clue on how to change the way the share either, so how can we expect users to clean up the shared files and links?

There are several ways to delete shared links, you can click on the shared icon on a file you have shared, Manage access and Stop sharing, or go to OneDrive in the web browser, find the view “Shared by you“, share icon and then manage access and stop. This works for files or folder you have shared.

Click on the … dots next to the “people with access” view in the left corner.

You will then get this image, and can stopp sharing for everyone.

This is a easy, but overwhelming way to do it.. But it needs to be a regular thing, so that you don’t overshare and make sure that your files remain under your controll.


But what about the insane number of all shared files from OneDrive within a organization?

Fear not! We have a script!
Sandra, the almighty, have based our script on Vasil Michevs script that removes access from one user, you can find that script here Github Post. And Sandras script is on Github: SISToolbox/OneDrive Sharing Permissions Remover at main · irean/SISToolbox · GitHub.
This script removes sharing permissions on files and folders within users’ OneDrive for Business. It works by enumerating all users in the tenant and checking each user’s OneDrive for shared files or folders, then removing the sharing permissions.

Requirements:

  • PowerShell version 7 or higher
  • The AppID used must have the following permissions:
    • User.Read.All to enumerate all users in the tenant
    • Sites.ReadWrite.All to return all the item sharing details

Make sure to fill in all the required variables before running the script.

The steps

Step 1. Log on to EntraID – Copy the tenant ID.

Step 2. Create a serviceprincipal.

  1. Go to Entra Portal
  2. Register a New Application
    • In the Entra portal go to the App registrations section.
    • Click New registration.
    • Provide a name for the application according to your naming standards.
    • Under Supported account types, select Accounts in this organizational directory only.
    • Click Register to create the application.
  3. Configure API Permissions
    • Open the newly registered application.
    • In the left-hand menu, go to API permissions under the Manage section.
    • Click Add a permission.
    • Choose Microsoft Graph.
    • Under Application permissions, add the following permissions:
      • User.Read.All
      • Sites.ReadWrite.All
  4. Create a Client Secret
    • In the left-hand menu, go to Certificates & secrets.
    • Under the Client secrets section, click New client secret.
    • Provide a description and set the expiration period for the secret.
    • After saving, copy the value of the new client secret and store it securely. This secret will act as the password for the service principal when making connections.

NB: You can check out Per-Torbens method how to connect with a serviceprincipal that goes more into detail and in a more secure manner -> How to connect PowerShell to various M365 services using Certificate-based Authentication – Agder in the cloud

Step 3. Save the code from the section under on your computer. Its called Remove-OneDriveSharingPermissions.ps1 in this blog.

How to run:

# Define the necessary parameters
$TenantID = "your-tenant-id"
$ClientSecretCredential = Get-Credential

# Run the script
.\Remove-OneDriveSharingPermissions.ps1 -TenantID $TenantID -ClientSecretCredential $ClientSecretCredential

The scipt Remove-OneDriveSharingPermissions.ps1 – Copy and save on computer.

#Requires -Version 7.0
# Make sure to fill in all the required variables before running the script
# Also make sure the AppID used corresponds to an app with sufficient permissions, as follows:
#    User.Read.All to enumerate all users in the tenant
#    Sites.ReadWrite.All to return all the item sharing details
# Help file: https://github.com/michevnew/PowerShell/blob/master/Graph_ODFB_remove_all_shared.md
# More info at: https://www.michev.info/blog/post/3018/remove-sharing-permissions-on-all-files-in-users-onedrive-for-business

[CmdletBinding()] #Make sure we can use -Verbose
Param(
    [string]$TenantID ,
    [pscredential]$ClientSecretCredential,
    [switch]$ExpandFolders=$true,
    [int]$Depth = 2)

#==========================================================================
# Helper functions
#==========================================================================
function igall {
    [CmdletBinding()]
    param (
        [string]$Uri
    )
    $nextUri = $uri
    do {
        $result = Invoke-MgGraphRequest -Method GET -uri $nextUri
        $nextUri = $result.'@odata.nextLink'
        if ($result -and $result.ContainsKey('value')) {
            $result.value

        }
        else {
            $result
        }
    } while ($nextUri)
}

function test-module {
    [CmdletBinding()]
    param(
        [String]$Name
  
    )
   Write-Host "Checking module $name"
    if(-not (Get-Module $Name)){
        Write-Host "Module $Name not imported, trying to import"
        try{
            if($Name -eq 'Microsoft.Graph'){
                Write-Host "Microsoft.Graph module import takes a while"
                Import-Module $Name  -ErrorAction Stop
            }
            else{
                Import-Module $Name  -ErrorAction Stop
            }
        
        }
        catch{
            Write-Host "Module $Name not found, trying to install"
            Install-Module $Name -Scope CurrentUser -AllowClobber -Force -AcceptLicense -SkipPublisherCheck
            Write-Host "Importing module  $Name "
            Import-Module $Name  -ErrorAction stop 
        }
    } 
    else{
        Write-Host "Module $Name is imported"
    }   
}
function processChildren {

    Param(
        #Graph User object
        [Parameter(Mandatory = $true)]$User,
        #URI for the drive
        [Parameter(Mandatory = $true)][string]$URI,
        #Use the ExpandFolders switch to specify whether to expand folders and include their items in the output.
        [switch]$ExpandFolders,
        #Use the Depth parameter to specify the folder depth for expansion/inclusion of items.
        [int]$depth)

    $URI = "$URI/children"
    $children = @()
    try {
        $children += igall $URI
    } catch {
    }

    if (!$children) { Write-Verbose "No child items found..."; return }

    #handle different children types
    $output = @()
    $cFolders = $children | Where-Object { $_.Folder }
    $cFiles = $children | Where-Object { $_.File } #doesnt return notebooks
    $cNotebooks = $children | Where-Object { $_.package.type -eq "OneNote" }

    #Process Folders
    foreach ($folder in $cFolders) {
        $output += (processFolder -User $User -folder $folder -ExpandFolders:$ExpandFolders -depth $depth -Verbose:$VerbosePreference)
    }

    #Process Files
    foreach ($file in $cFiles) {
        if ($file.shared) {
            Write-Host "Found shared file ($($file.name)), removing permissions..."
            RemovePermissions $User.id $file.id -Verbose:$VerbosePreference
            $fileinfo = New-Object psobject
            $fileinfo | Add-Member -MemberType NoteProperty -Name "Name" -Value $file.name
            $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemType" -Value "File"
            $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemPath" -Value $file.webUrl
            $output += $fileinfo
        }
        else { continue }
    }

    #Process Notebooks
    foreach ($notebook in $cNotebooks) {
        if ($notebook.shared) {
            Write-Host "Found shared notebook ($($notebook.name)), removing permissions..."
            RemovePermissions $User.id $notebook.id -Verbose:$VerbosePreference
            $fileinfo = New-Object psobject
            $fileinfo | Add-Member -MemberType NoteProperty -Name "Name" -Value $notebook.name
            $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemType" -Value "Notebook"
            $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemPath" -Value $notebook.webUrl
            $output += $fileinfo
        }
    }
    return $output
}

function processFolder {

    Param(
        #Graph User object
        [Parameter(Mandatory = $true)]$User,
        #Folder object
        [Parameter(Mandatory = $true)]$folder,
        #Use the ExpandFolders switch to specify whether to expand folders and include their items in the output.
        [switch]$ExpandFolders,
        #Use the Depth parameter to specify the folder depth for expansion/inclusion of items.
        [int]$depth)

    #if the Shared property is set, fetch permissions
    if ($folder.shared) {
        Write-Host "Found shared folder ($($folder.name)), removing permissions..."
        RemovePermissions $User.id $folder.id -Verbose:$VerbosePreference
        $fileinfo = New-Object psobject
        $fileinfo | Add-Member -MemberType NoteProperty -Name "Name" -Value $folder.name
        $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemType" -Value "Folder"
        $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemPath" -Value $folder.webUrl
    }

    #Since this is a folder item, check for any children, depending on the script parameters
    if (($folder.folder.childCount -gt 0) -and $ExpandFolders -and ((3 - $folder.parentReference.path.Split("/").Count + $depth) -gt 0)) {
        Write-Verbose "Folder $($folder.Name) has child items"
        $uri = "https://graph.microsoft.com/v1.0/users/$($user.id)/drive/items/$($folder.id)"
        $folderItems = processChildren -User $user -URI $uri -ExpandFolders:$ExpandFolders -depth $depth -Verbose:$VerbosePreference
    }

    #handle the output
    if ($folderItems) { $f = @(); $f += $fileinfo; $f += $folderItems; return $f }
    else { return $fileinfo }
}

function RemovePermissions {

    Param(
        #Use the UserId parameter to provide an unique identifier for the user object.
        [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$UserId,
        #Use the ItemId parameter to provide an unique identifier for the item object.
        [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$ItemId)

    #fetch permissions for the given item
    $permissions = @()
    $uri = "https://graph.microsoft.com/beta/users/$($UserId)/drive/items/$($ItemId)/permissions"
    $permissions = igall $uri
    if (-not $permissions) {
        continue
    }

    foreach ($entry in $permissions) {
        if ($entry.inheritedFrom) { Write-Verbose "Skipping inherited permissions..." ; continue }
        Write-Host -ForegroundColor Green "Tar bort $($entry.id)"
        Invoke-MgGraphRequest -Method DELETE -Verbose:$VerbosePreference -Uri "$uri/$($entry.id)" -Headers $authHeader -SkipHeaderValidation -ErrorAction Stop | Out-Null
    }
    #check for sp. prefix on permission entries
    #SC admin permissions are skipped, not covered via the "shared" property
}

#==========================================================================
# Main script starts here
#==========================================================================
test-module -name microsoft.graph.authentication  

Connect-MgGraph -TenantId $TenantID -ClientSecretCredential $ClientSecretCredential

#prepare auth header
$global:authHeader = @{
    'Content-Type' = 'application\json'
}

#Check the user object
Write-Verbose "Checking user $user ..."
igall 'https://graph.microsoft.com/v1.0/users/' | foreach-object {
    $user = $_.userPrincipalName
    Write-Host "Processing user $user ODFB drive..."
    #Check whether the user has ODFB drive provisioned
    $uri = "https://graph.microsoft.com/v1.0/users/$($_.id)/drive/root"
    try { $UserDrive = Invoke-MgGraphRequest -Uri $uri -Verbose:$VerbosePreference -Headers $authHeader -ErrorAction Stop }
    catch { Write-Warning "User $user doesn't have OneDrive provisioned, ignoring..." }


    #If no items in the drive, skip
    if ( (-not $UserDrive) -or ($UserDrive.folder.childCount -eq 0)) {
        Write-Host "No items found for user $user"
    } else {
        #enumerate items in the drive and prepare the output
        Write-Verbose "Processing drive items..."
        processChildren -User $_ -URI $uri -ExpandFolders:$true -depth $depth
    }
}

The script should do something like this when started.

(Translation: Tar bort = removing)

The result?

And if you are still not sure what to use when when it comes to sharing, have a look here: Navigating Teams and the abundance of possibilities! – Agder in the cloud

Authors

  • Åsne Holtklimpen

    Åsne is a Microsoft MVP within Microsoft Copilot, an MCT and works as a Cloud Solutions Architect at Crayon. She was recently named one of Norway’s 50 foremost women in technology (2022) by Abelia and the Oda network. She has over 20 years of experience as an IT consultant and she works with Microsoft 365 – with a special focus on Teams and SharePoint, and the data flow security in Microsoft Purview.

    View all posts
  • Sandra has over 10 years of experience in IT, specializing in Identity and Governance within Microsoft Entra. She is passionate about using PowerShell, automation, and optimizing processes with advanced functions.

    View all posts

Discover more from Agder in the cloud

Subscribe to get the latest posts sent to your email.

By Åsne Holtklimpen

Åsne is a Microsoft MVP within Microsoft Copilot, an MCT and works as a Cloud Solutions Architect at Crayon. She was recently named one of Norway’s 50 foremost women in technology (2022) by Abelia and the Oda network. She has over 20 years of experience as an IT consultant and she works with Microsoft 365 – with a special focus on Teams and SharePoint, and the data flow security in Microsoft Purview.

Related Post

Leave a Reply