Second Life of a Hungarian SharePoint Geek

July 1, 2018

The sub-webs delivered to you are dirty

Filed under: PowerShell, SP 2013 — Tags: , — Peter Holpar @ 14:39

Recently I had to perform a simple administrative task: a SharePoint website had several sub-webs, each of them having unique permissions. Our goal was to reset the permissions to be inherited from the parent site. So I created a simple PowerShell script to achieve the goal:

$rootWeb = Get-SPWeb ‘http://YourSharePointSite/SomeSite’
$rootWeb.Webs | % { $_.ResetRoleInheritance() }

Note: Although I use the Webs property of the SPWeb object overall in this post to illustrate the problem and later the solution, the very same applies to the GetSubwebsForCurrentUser method of the SPWeb object as well.

To my greatest surprise, I received this error message for each of the sub-web sites:

Exception calling "ResetRoleInheritance" with "0" argument(s): "There are uncommitted changes on the SPWeb object, call SPWeb.Update() to commit the changes before calling this method."

To understand the source of the exception, I followed the call-chain of the SPWeb.ResetRoleInheritance method using Reflector. All of the methods and classes mentioned in the post are declared in the Microsoft.SharePoint assembly in the Microsoft.SharePoint namespace.

First, the SPWeb.ResetRoleInheritance() method invokes the virtual SPSecurableObject.ResetRoleInheritance(), that invokes the internal SPSecurableObjectImpl.ResetRoleInheritance() method, that finally calls the private SPSecurableObjectImpl.RevertRoleInheritance(bool copyRoleAssignments, bool clearSubScopes) method, where the exception get thrown.

The value of the $StackTrace variable in PowerShell confirmed the result of my research:

   at Microsoft.SharePoint.SPSecurableObjectImpl.RevertRoleInheritance(Boolean copyRoleAssignments, Boolean clearSubScopes)
   at Microsoft.SharePoint.SPWeb.ResetRoleInheritance()
   at CallSite.Target(Closure , CallSite , Object )

The SPSecurableObjectImpl.RevertRoleInheritance method contains this condition:

if ((this.m_objectType == SPObjectType.Web) && this.m_web.IsDirty)
{
    throw new InvalidOperationException(SPResource.GetString("SPWebHasUnCommittedChange", new object[0]));
}

It means, the internal IsDirty property of the SPWeb class is checked to see, if there is any uncommitted change in the SPWeb instance. It is common, that your SPWeb instance gets dirty after you change some of its properties, but in our case, we apparently have not change anything, we still get the complain about “dirtiness” our web.

Let’s see, how to check if our SPWeb instance is dirty or not, and find some kind of workaround.

Forget the PowerShell example above for a while, and switch to C#. In this case, the base version of the code looks like this:

  1. using (SPSite site = new SPSite("http://YourSharePointSite/SomeSite"))
  2. {
  3.     using (SPWeb web = site.OpenWeb())
  4.     {
  5.         foreach (SPWeb subWeb in web.Webs) // or the same with webs.GetSubwebsForCurrentUser()
  6.         {
  7.             try
  8.             {
  9.                 subWeb.ResetRoleInheritance();
  10.             }
  11.             finally
  12.             {
  13.                 subWeb.Dispose();
  14.             }
  15.         }
  16.     }
  17. }

Although the IsDirty method of the SPWeb is declared as private, we can access it via Reflection, as we did it in the extension method below:

  1. static class Extensions
  2. {
  3.     public static bool IsDirty(this SPWeb web)
  4.     {
  5.         var result = false;
  6.  
  7.         var pi_isDirty = typeof(SPWeb).GetProperty("IsDirty", BindingFlags.NonPublic | BindingFlags.Instance);
  8.         result = (bool)pi_isDirty.GetValue(web);
  9.  
  10.         return result;
  11.     }
  12. }

Having our extension method, we can dump out easily, if  the root web site and its sub-webs are dirty or not.

  1. using (SPSite site = new SPSite("http://YourSharePointSite/SomeSite"))
  2. {
  3.     using (SPWeb web = site.OpenWeb())
  4.     {
  5.         Console.WriteLine("Web '{0}' is dirty: '{1}'", web.Url, web.IsDirty());
  6.         foreach (SPWeb subWeb in web.Webs) // or the same with webs.GetSubwebsForCurrentUser()
  7.         {
  8.             try
  9.             {
  10.                 Console.WriteLine("Web '{0}' is dirty: '{1}'", subWeb.Url, subWeb.IsDirty());
  11.                 subWeb.ResetRoleInheritance();
  12.             }
  13.             finally
  14.             {
  15.                 subWeb.Dispose();
  16.             }
  17.         }
  18.     }
  19. }

The result shows, that the parent site is not dirty, but all of it sub-webs (returned either by the Webs property or the GetSubwebsForCurrentUser method) are all dirty.

There are two possible workarounds for the issue. We should either call the Update method of the SPWeb instance before invoking the ResetRoleInheritance method, thus clearing the IsDirty flag, or if we don’t want to commit any possible changes, we can create another, clear SPWeb instance from scratch based on the ID or the Url of the original SPWeb object, and invoke the ResetRoleInheritance method on the new instance. The code sample below illustrates both of these options:

  1. using (SPSite site = new SPSite("http://YourSharePointSite/SomeSite"))
  2. {
  3.     using (SPWeb web = site.OpenWeb())
  4.     {
  5.         Console.WriteLine("Web '{0}' is dirty: '{1}'", web.Url, web.IsDirty());
  6.  
  7.         foreach (SPWeb subWeb in web.Webs) // or the same with webs.GetSubwebsForCurrentUser()
  8.         {
  9.             try
  10.             {
  11.                 Console.WriteLine("Web '{0}' is dirty: '{1}'", subWeb.Url, subWeb.IsDirty());
  12.                 // option 1
  13.                 subWeb.Update();
  14.                 Console.WriteLine("Web '{0}' is dirty: '{1}'", subWeb.Url, subWeb.IsDirty());
  15.                 subWeb.ResetRoleInheritance();
  16.                 //// option 2
  17.                 //using (SPWeb subWebNew = site.OpenWeb(subWeb.ID)) // or site.OpenWeb(subWeb.Url)
  18.                 //{
  19.                 //    Console.WriteLine("Web '{0}' is dirty: '{1}'", subWebNew.Url, subWebNew.IsDirty());
  20.                 //    subWebNew.ResetRoleInheritance();
  21.                 //}
  22.             }
  23.             finally
  24.             {
  25.                 subWeb.Dispose();
  26.             }
  27.         }
  28.     }
  29. }

After this detour into C#, let’s go back to our original PowerShell sample. Although there are no extension methods in PowerShell, we can define a helper function to query and display the value of the IsDirty property, and we can apply both of the above workarounds to “clear” or web instance as well:

  1. function IsDirty($web) {
  2.     $pi = [Microsoft.SharePoint.SPWeb].GetProperty("IsDirty", [Reflection.BindingFlags] "NonPublic,Instance")
  3.     $isDirty = $pi.GetValue($web)
  4.     return $isDirty
  5.     Write-Host Web $($web.Url) is dirty $isDirty
  6. }
  7.  
  8. $rootWeb = Get-SPWeb 'http://YourSharePointSite/SomeSite'
  9. Write-Host Web $($rootWeb.Url) is dirty $($rootWeb.IsDirty)
  10. $rootWeb.Webs | % {
  11.     $web = $_
  12.     IsDirty $web
  13.     # option 1
  14.     $web.Update()
  15.     IsDirty $web
  16.     $web.ResetRoleInheritance()
  17.     ## option 2
  18.     #$webClear = Get-SPWeb $web.Url
  19.     #IsDirty $webClear
  20.     #$webClear.ResetRoleInheritance()
  21. }

But wait! Is there really nothing in PowerShell like extension methods in C#? Couldn’t we extend or object somehow to be able to write nicer code? About this theme and much more plan I write in a later post.

 

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.