Share via


SharePoint Online: PowerShell to delete unique permissions in all list items

Introduction

By default, lists and their items inherit the groups and permission levels of the site above them in the hierarchy—that is, a site inherits permissions from its parent site, then the list inherits permissions from the site, and finally each item in the list inherits these permissions from the list. If you make a change in the parent site, its subsites, their lists, and their items automatically get the same change.1

This inheritance is often broken because not all site users should have access to all lists and all documents on the site. And because SharePoint allows you to get even more granular - not everybody who has access to the list should have access to all its items. Just recently there was an interesting solution where all users had view-only permissions on the list and edit permissions on chosen files. 

Such solutions are often dictated by security reasons, e.g. the item or document contains sensitive data, and you might want to restrict who can see it.  As the site grows and the list expands, the number of items increases until you end up with hundreds of items with unique permissions, which you no longer need, and more importantly - probably lost control over.

Time for a fresh start?

Just as you were stopping the permission inheritance for each item, you can now delete its unique permissions. The trick is - for one and every item separately.

 Watch out -  If you choose to resume permissions inheritance, you lose any unique permission settings on the content.2

This is where scripting comes in handy. 

Here we will create a script using CSOM and PowerShell. CSOM stands for client-side object model which allows you to authenticate to SharePoint Online and perform operations on its elements. It is not limited to PowerShell only. Let's open PowerShell ISE and look at it from PowerShell perspective:

  1. Install SharePoint Online SDK 

  2. After you have installed the SDK, you need to refer it in your script:



      # Paths to SDK. Please verify location on your computer.  
      Add-Type -Path     "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"  
      Add-Type -Path     "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"  

3. Create a context for your operations. The URL refers to a site where the list is located. It can be a site collection or one of the subsites:


      $ctx    =New-Object Microsoft.SharePoint.Client.ClientContext(    $Url    )  

4. Add the credentials of the admin. You cannot perform operations against a site to which you don't have permissions! 

The password has to be a secure string.

$password = ConvertTo-SecureString -string $AdminPassword -AsPlainText -Force
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, $password)

5. Now let's get the list where you want to change the inheritance for your items. You need to load the elements on which you want to perform operations, because before you can access value properties of a client object, you must call the LOAD method




      $ll    =    $ctx    .Web.Lists.GetByTitle(    $ListTitle    )  
   
                  $ctx      .Load(      $ll      )      
   
                  $ctx      .ExecuteQuery()      
  1.  ExecuteQuery()  performs the operations that you stated in the lines above. This virtual method is “synchronous”, which means that code execution is blocked until a response is received from the server. If the caller does not want to be blocked and the caller is managed, the caller should evoke the ExecuteQueryAsync() method.

  2. Now let's load the items.  The .GetItems() function requires a CamlQuery. We create a Microsoft.SharePoint.Client.CamlQuery object and pass it as a variable to $ll.GetItems()  where $ll stands for the list we retrieved earlier.

$spQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
  
  $itemki=$ll.GetItems($spQuery)
  $ctx.Load($itemki)
  $ctx.ExecuteQuery()
  1. Theoretically we could leave it at that but our CamlQuery is missing one thing. Imagine that your items are actually files stored in various folders in your library (document library is a type of list). Our present solution would return only items visible on the first-level view. All items in the folders would remain hidden. That's why we need to use "Recursive". It loops through all the folders and gets ALL the items available in the list.


      $spqQuery    .ViewXml =    "<View Scope='RecursiveAll' />"    ;  

9. Inserted in the code:

$spQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
 
$spqQuery.ViewXml ="<View Scope='RecursiveAll' />";
  $itemki=$ll.GetItems($spQuery)
  $ctx.Load($itemki)
  $ctx.ExecuteQuery()
  1. Now let's go through all the items and delete its unique permissions


      for    (    $j    =0;    $j -lt $itemki.Count ;$j++)
                  {      
                      
                      
                      $itemki      [      $j      ].ResetRoleInheritance()      
                  
                 
                  }      

11. Where should we put $ctx.ExecuteQuery() ? That's an interesting question.
Now, I have only 9 items in my list so it doesn't really matter if I put it here and wait for the server response 9 times, each one for every item:


for($j=0;$j -lt $itemki.Count ;$j++)
  {
           
           
      $itemki[$j].ResetRoleInheritance()
     $ctx.ExecuteQuery()
      
  }
  1. But it's not very efficient. Imagine that your SharePoint Online list has 5000 elements and you would like to contact the server 5000 times - at some point the script execution may even get blocked as a suspicion of DDOS attack or for protection of other tenants since SharePoint Online means shared tenancy (actually this would never happen with 5000 items, Microsoft servers are far more resilient than that :) ).

  2. CSOM supports Request Batching. That means that a series of operations can be submitted to the server in a single request when you call the ExecuteQuery method.6 It allows to minimize the number of messages that are passed between the client and the server, as .ExecuteQuery() could contain single or multiple operations. So, the best practice is to minimize the number of calls using $ctx.ExecuteQuery() method. 



      for    (    $j    =0;    $j -lt $itemki.Count ;$j++)
                  {                    
                      $itemki      [      $j      ].ResetRoleInheritance()               
                  }      
   
      $ctx    .ExecuteQuery()  
  1. In the example above all my 9 items will be processed in one go.

You can enjoy full script here

Other languages

This site is available in other languages:

http://c.statcounter.com/10258698/0/7814e84e/0/