Second Life of a Hungarian SharePoint Geek

August 20, 2016

Issues with the PSContext Object

Filed under: Bugs, PS 2013 — Tags: , — Peter Holpar @ 22:43

A few weeks ago I wrote a post about how to access the PSI methods via the PSContext object, that represent the Project Server context in the server side object model. As I wrote in the side note in my post, there are some issues with this object. In the current post I explain what I mean on that exactly. The content of the post relates to the 2016 May CU of Project Server, older and newer CUs might behave differently.

The PSContext class has no public constructor. There are however three overloads of the static GetContext method you can use to get the context: one with an SPSite parameter, one with an SPWeb parameter, and one with a Uri parameter. These methods call the corresponding private constructors (one with an SPSite parameter, one with an SPWeb parameter, and one with a Uri parameter either) of the PSContext class. The private constructor having a Uri parameter creates an SPSite instance based on the Uri and then calls the constructor having an SPSite parameter. The constructor having an SPSite parameter takes the root web of the SPSite  instance, and calls the constructor having the SPWeb parameter.

A potential issue I found is, that the PSContext constructor with the SPSite parameter type stores the SPSite parameter in a private field and dispose it when the PSContext object gets disposed. Although I have not yet faced with its side effects, you should be aware of this behavior to avoid surprises.

  1. using (SPSite site = new SPSite(pwaUrl))
  2. {                
  3.     using (PSContext projectContext = PSContext.GetContext(site))
  4.     {
  5.         Console.WriteLine("Project count: {0}", projectContext.Projects.Count());
  6.     }
  7.  
  8.     // the site is already disposed at this point
  9.     // but you may want to use it further
  10.     Console.WriteLine("Site url: {0}", site.Url);
  11. }

I think the site should be stored in the private field and be disposed in the case of the PSContext constructor with the Uri parameter type (as we create a new SPSite instance in this case), and not in the constructor with the SPSite parameter when we get the SPSite instance from the external code.

But a more serious issue that caused me some headache already is the next one:

In my recent post I described, that the private static RetrieveValue method of the PJClientCallableContext class (that is an important part of the context construction) behaves differently based on the condition, whether or not the process runs with or without a HTTP context. Without HTTP context, the SPWeb object passed to the PSContext constructor is used to create the context. Otherwise, if we have an HTTP context, the SPWeb object (or the Uri, or the SPSite, depending on which constructor you invoked) passed to the PSContext constructor is simply ignored. Let’s see what it means for the developers.

As long as you use the PSContext object from a console application, or from an application without an HTTP context, like a timer job or Windows service, it’s OK, as far as I see. You might be however surprised if you think the parameters of the GetContext methods have any significance if the PSContext object is used in a web application. Based on my experience they do not have any. Of course, the value of the Uri parameter, if you use this overload, should point to a SharePoint site, to enable the creation of the SPSite instance. But beyond that, the parameter is simply ignored.

If the web page belongs to a PWA instance, then this PWA instance will be used as the context.

If you happen to have multiple PWA instances on the same server, you can not access the other one via the PSContext object, even if you pass a parameter (Uri, SPSite or SPWeb) to the GetContext method that points to the other PWA instance.

You can even pass a parameter (Uri, SPSite or SPWeb) to the GetContext method that points to a SharePoint site without PWA instance, still the PWA instance of the page will be used.

It means on the other side, that you can not use the PSContext object in a web page without PWA instance (at least, unless you try to fake the HTTP context as described here). If you try it, you receive an HTTP 403 error (similar to the error discussed in this post):

A first chance exception of type ‘System.UnauthorizedAccessException’ occurred in mscorlib.dll

Additional information: Attempted to perform an unauthorized operation.

   at Microsoft.ProjectServer.PJClientCallableContext.get_PJContext()
   at Microsoft.ProjectServer.PJClientCallable.HandleDelegation()
   at Microsoft.ProjectServer.PJClientCallable.CallPSITag[TResult](UInt32 ulsID, String caller, Func`2 body, Action`2 onError)
   at Microsoft.ProjectServer.ProjectCollection.<>c__DisplayClass17.<>c__DisplayClass25.<.ctor>b__8()
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.LazyInitValue()
   at Microsoft.ProjectServer.ProjectCollection.<>c__DisplayClass17.<.ctor>b__7()
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.LazyInitValue()
   at Microsoft.ProjectServer.ProjectCollection.GetEnumerator()
   at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source)
   at Custom.YourTest.Page_Load(Object sender, EventArgs e)

I think a better implementation of the PSContext object would be to have a static Current getter property (similar to the SPContext class) that we could use only if there is a HTTP context, otherwise it would return null or throw an Exception. The static GetContext method should have been reserved for usage from processes without HTTP context. Invoking these methods from a process having HTTP context should throw an exception.

July 26, 2016

Security Issue with Connection Manager in SQL Server Integration Services Solutions

Filed under: Bugs, SSIS — Tags: , — Peter Holpar @ 21:23

Today I had to import some data from a password protected Access file into an MS SQL database using SSIS. I am working with SQL Server Data Tools in Visual Studio 2013. Might be that the issue I am writing about cannot be reproduced by the Visual Studio 2015 version of the tool.

I’ve started to create a new connection via Connection Manager. As I typed in the password – as one expect it – black dots were displayed instead of the real characters of the password.

image

It is the same if you switch from the Connection page to the All page of the dialog, where you can see and change all properties of your connection.

image

However, if you click the OK button to persist the changes in the connection, the password are revealed under the Data connection properties:

image

I think it should be a bug in the product.

BTW, after you click the OK button in this dialog as well, the password is displayed again masked (this time by asterisk characters instead of the black dots) in the property grid of the connection:

image

June 30, 2016

How a Missing Permission for a Single User May Crash a Whole SharePoint Custom Web Application

Filed under: Bugs, Security, SP 2013 — Tags: , , — Peter Holpar @ 08:17

As part of our daily jobs we provide support for several custom-developed SharePoint-based web applications, like purchase order workflows, etc. either. Few of them were developed for MOSS 2007 / SharePoint 2010, and then migrated to SharePoint 2013. The code quality reflects often not the best programming practices as well, to tell the through.

The symptoms

A weird error has arisen in one of that applications from a such coding anti-pattern caused us some headache recently.

Every once in a while the users complained, that the custom pages they use otherwise (for example, a day earlier) throw an exception. Restarting the IIS application pool for the SharePoint web application made the pages to function again, however we considered this as a simple quick-and-dirty workaround until we find out the real reason behind the issue.

In the ULS logs we found the following entries:

Application error when access /_layouts/CustomPages/YourPage.aspx, Error=List ‘Config’ does not exist at site with URL ‘http://YourSharePoint’.   
Microsoft.SharePoint.Client.ResourceNotFoundException: List ‘Config’ does not exist at site with URL ‘
http://YourSharePoint’.
Getting Error Message for Exception System.Web.HttpUnhandledException (0x80004005): Exception of type ‘System.Web.HttpUnhandledException’ was thrown. —> System.TypeInitializationException: The type initializer for ‘Company.Custom.Config’ threw an exception. —> System.ArgumentException: List ‘Config’ does not exist at site with URL ‘
http://YourSharePoint’. —> Microsoft.SharePoint.Client.ResourceNotFoundException: List ‘Config’ does not exist at site with URL ‘http://YourSharePoint’.     — End of inner exception stack trace —     at Microsoft.SharePoint.SPListCollection.GetListByName(String strListName, Boolean bThrowException)     at Company.Custom.Config..cctor()     — End of inner exception stack trace —     at …
…Company.Custom.Config.get_ConfigValue()     at Company.Custom.Pages.Layouts.Company.Custom.YourPage.Page_Load(Object sender, EventArgs e)     at Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase.OnLoad(EventArgs e)     at Microsoft.SharePoint.WebControls.LayoutsPageBase.OnLoad(EventArgs e)     at System.Web.UI.Control.LoadRecursive()     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.HandleError(Exception e)     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessReques…    …t()     at System.Web.UI.Page.ProcessRequest(HttpContext context)     at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Based on the logs the users have obviously problem with accessing the custom list called “Config”. At first we thought the list was deleted accidentally, or the users do not have permission on it, but after a quick check it turned out that the list is there, and the users have no problem accessing it via the web UI.

This list is used by the web application to persist specific application-wide settings as name-value pairs. A custom static class called “Config” is responsible to read up the configuration values from the SharePoint list into a static Dictionary field, and provide them to the other components of the application as static properties of the class. In the original implementation of the class the configuration values were read up from the list in the static constructor and that is without elevated permission, since (at least theoretically) all users should have at least read permissions to the list.

What’s wrong with this approach? Why can’t users that definitely do have permission to the list access it?

If there is at least a single user without permission to the list, and this user happens to be the first one that tries to access the Config object after the application pool of the web application (re)started or recycled by IIS, the static constructor must throw obviously a TypeInitializationException exception, as there is an unhandled exception in the static constructor. The real exception, that caused the problem is included in the InnerException of the TypeInitializationException exception. But why do the other users (having permissions to the list) become the same error?

The answer can be found on MSDN:

”If a static constructor throws an exception, the runtime will not invoke it a second time, and the type will remain uninitialized for the lifetime of the application domain in which your program is running.”

In our case the application domain means the process of the IIS application pool. Although it is not documented in the article mentioned above, but based on our experience on each further access on any static methods / properties the very same exception would be thrown as the first time.

It is not something SharePoint-specific, one can have the same issue with broken SQL Server connection as well.

To illustrate the behavior I extended the code sample I found here, provided by Jon Skeet, author of one of my favorite books, C# in Depth:

  1. using System;
  2. using System.Threading;
  3.  
  4. public static class Config
  5. {
  6.     static Config()
  7.     {
  8.         DateTime now = DateTime.Now;
  9.  
  10.         Console.WriteLine("Static constructor invoked at {0:s}", now);
  11.         throw new ApplicationException(string.Format("List not found, time stamp: {0:s}", now));
  12.     }
  13.  
  14.     public static string Value
  15.     {
  16.         get { return "a value"; }
  17.     }
  18. }
  19.  
  20. class StaticConfigTest
  21. {
  22.     static void Main()
  23.     {
  24.         for (int i = 0; i < 5; i++)
  25.         {
  26.             DateTime now = DateTime.Now;
  27.             Console.WriteLine("Config value read at {0:s}", now);
  28.  
  29.             try
  30.             {
  31.                 var value = Config.Value;
  32.             }
  33.             catch (Exception e)
  34.             {
  35.                 var text = string.Format("'{0}' ({1})", e.Message, e.GetType().Name);
  36.                 var ie = e.InnerException;
  37.                 if (ie != null)
  38.                 {
  39.                     text += string.Format(" '{0}' ({1})", ie.Message, ie.GetType().Name);
  40.                 }
  41.                 Console.WriteLine(text);
  42.             }
  43.  
  44.             // wait 5 secs
  45.             Thread.Sleep(5000);
  46.         }
  47.     }
  48. }

When you execute this code, it will output something like this:

image

The main points you should notice:

  • There is only a single line of “Static constructor invoked”. It means, the static constructor is only invoked once.
  • The main exception is always a TypeInitializationException, the “real” exception (in our case, it is an ApplicationException) is provided in the InnerException property. Based on the ULS logs above, the InnerException is logged out by SharePoint (that is a good thing), and it caused us a bit of confusion (that is not so good, of course).
  • The exception thrown by accessing the static members is always the same as the first one, compare the time stamps values in the output.

How to solve the problem once we know the real reason of the issue?

As we wanted to remedy the issue as fast as possible, the very first step was to resolve the direct cause of the problem, so we granted permissions on the Config list for the few users who did not have access earlier.

For a long-term solution, I think the most important step was to refactor the Config class. The code of the static constructor was transformed to a private static method (let’s call it InitializeIfNeeded). In this method we lock the Dictionary object used to store the configuration values, to provide a kind of  thread safety and support potential concurrent calls. We check next, if the configuration values were already initialized (via a static boolean field called isConfigInitialized). If they were, we exit from the InitializeIfNeeded method. If they were not, we read up the values from the SharePoint list into the Dictionary object, and set the value of the isConfigInitialized field to true. On accessing each of the static properties of the Config class representing the individual configuration values, we invoke first the InitializeIfNeeded method, to ensure the values are available. Using this simple step we can ensure, that users that do have permission on the SharePoint list can accesd the configuration values. Users without permission may have still the problem that the values cannot be read from the list, if they are the first ones to visit the pages. If they are visiting the pages after the values were already initialized (via a visit of a user with permission), they can of course access the configuration values as well.

As next step, in the InitializeIfNeeded method we embedded the code responsible for reading up the values from the SharePoint list into the Dictionary object into an elevated permission block to ensure all user (even the ones without direct permission to the list) can read the values up.

After testing the new version and deploying it into the production system, we can revoke the direct permissions from the Config list for the standard users, and let only administrators to read and change configuration value via the web UI.

May 29, 2016

Project Publishing Failed due to Deleted SharePoint User

Filed under: Bugs, Event receivers, PowerShell, PS 2013 — Tags: , , , — Peter Holpar @ 05:53

In my recent post I wrote about a project publishing issue that was a result of a scheduling conflict.

The other day we had a similar problem with project publishing, but in this special case failed an other sub-process of the publishing process, the task synchronization. Another important difference from the former one is that at the scheduling conflict it was an end-user issue (a business user caused the conflict in the project plan scheduling), and in the case I’m writing about now, it was a mistake of an administrator plus a suboptimal code block in Project Server, that we can consider as a bug as well. But more on that a bit later…

First the symptoms we experienced. On the Manage Queue Jobs page in our PWA (http://YourProjectServer/PWA/_layouts/15/pwa/Admin/queue.aspx) we saw an entry of Job TypeSharePoint Task List Project” and Job State Failed And Blocking Correlation”.

Clicking on the entry displayed this information:

Queue: GeneralQueueJobFailed (26000) – ManagedModeTaskSynchronization.SynchronizeTaskListInManagedModeMessage. Details: id=’26000′ name=’GeneralQueueJobFailed’ uid=’46918ff3-3719-e611-80f4-005056b44e32′ JobUID=’adcad466-44bd-444b-a803-073fd12a2426′ ComputerName=’4fc61930-ef50-461b-b9ef-084a666c61ca’ GroupType=’ManagedModeTaskSynchronization’ MessageType=’SynchronizeTaskListInManagedModeMessage’ MessageId=’1′ Stage=” CorrelationUID=’cd56b408-a303-0002-d428-98cd03a3d101′.

The corresponding entries in the ULS logs:

PWA:http://YourProjectServer/PWA, ServiceApp:ProjectServerApplication, User:i:0#.w|YourDomain\FarmAccount, PSI: [QUEUE] SynchronizeTaskListInManagedModeMessage failed on project 5c21bf1b-c910-e511-80e5-005056b44e34. Exception: System.NullReferenceException: Object reference not set to an instance of an object.     at Microsoft.Office.Project.Server.BusinessLayer.ProjectModeManaged.UpdateAssignedToField(SPWeb workspaceWeb, DataSet taskDS, Guid taskUID, SPListItem listItem)     at Microsoft.Office.Project.Server.BusinessLayer.ProjectModeManaged.SynchronizeTask(SPList list, DataSet taskDS, Dictionary`2 taskMapping, DataRow row, DataView secondaryView, Dictionary`2 redoEntries)     at Microsoft.Office.Project.Server.BusinessLayer.ProjectModeManaged.<>c__DisplayClass1.<SynchronizeTaskListI…
…nManagedMode>b__0(SPWeb workspaceWeb)     at Microsoft.Office.Project.Server.BusinessLayer.Project.<>c__DisplayClass3d.<TryRunActionWithProjectWorkspaceWebInternal>b__3c()     at Microsoft.SharePoint.SPSecurity.<>c__DisplayClass5.<RunWithElevatedPrivileges>b__3()     at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)     at Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(WaitCallback secureCode, Object param)     at Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(CodeToRunElevated secureCode)     at Microsoft.Office.Project.Server.BusinessLayer.Project.TryRunActionWithProjectWorkspaceWebInternal(IPlatformContext context, Guid projectUid, Action`1 method, Boolean noThrow, DataRow row)     at Microsoft.Office.Project.Server.Busine…
…ssLayer.ProjectModeManaged.SynchronizeTaskListInManagedMode(Guid projectUid)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.ProcessPublishMessage.ProcessSynchronizeTaskListInManagedModeMessage(Message msg, Group messageGroup, JobTicket jobTicket, MessageContext mContext), LogLevelManager Warning-ulsID:0x000CE687 has no entities explicitly specified.

So we have a NullReferenceException in the UpdateAssignedToField method of the Microsoft.Office.Project.Server.BusinessLayer.ProjectModeManaged class (Microsoft.Office.Project.Server assembly).

From the job message type “ManagedModeTaskSynchronization.SynchronizeTaskListInManagedModeMessage” it was obvious, that we have an issue with the synchronization between the project tasks and the Tasks list of the Project Web Site (PWS) of the project having the ID 5c21bf1b-c910-e511-80e5-005056b44e34”,  and from the method name “UpdateAssignedToField” we could assume, that the problem is caused either by an existing value of the “Assigned To” field, or by constructing a new value we want to update the field with.

We can use the following script to find out, which PWS belongs to the project ID above:

$pwa = Get-SPWeb http://YourProjectServer/PWA
$pwa.Webs | ? { $_.AllProperties[‘MSPWAPROJUID’] -eq ‘5c21bf1b-c910-e511-80e5-005056b44e34’ }

If we have a look at the code of the UpdateAssignedToField method, we see it begins with these lines. These lines are responsible for removing users from the “Assigned To” field (of type SPFieldUserValueCollection) that are no longer responsible for the task. The second part of method (not included below) is responsible for inserting new user entries. I highlighted the line that may cause (and in our case in fact has caused) an error if the value of the assignedTo[i].User expression is null.

bool isModified = false;
SPFieldUserValueCollection assignedTo = listItem["AssignedTo"] as SPFieldUserValueCollection;
DataRowView[] source = taskDS.Tables[1].DefaultView.FindRows(taskUID);
if (assignedTo != null)
{
    for (int i = assignedTo.Count – 1; i >= 0; i–)
    {
        string userName = ClaimsHelper.ConvertAccountFormat(assignedTo[i].User.LoginName);
        if (!source.Any<DataRowView>(resourceRow => (string.Compare(userName, resourceRow.Row.Field<string>("WRES_CLAIMS_ACCOUNT"), StringComparison.OrdinalIgnoreCase) == 0)))
        {
            assignedTo.RemoveAt(i);
            isModified = true;
        }
    }
}

The expression may be null if the user it refers to was deleted from the site. Note, that the expression assignedTo[i].LookupId even in this case returns the ID of the deleted user, and the expression assignedTo[i].LookupValue return its name.

How to detect which projects and which users are affected by the issue? I wrote the script below to display the possible errors:

  1. $rootWeb = Get-SPWeb http://YourProjectServer/PWA
  2.  
  3. $rootWeb.Webs | % {
  4.  
  5.     $web = $_
  6.  
  7.  
  8.     Write-Host ——————————-
  9.     Write-Host $web.Title
  10.  
  11.  
  12.     $foundMissingUsers = New-Object 'Collections.Generic.Dictionary[int,string]'
  13.  
  14.     $list = $web.Lists["Tasks"]
  15.  
  16.     if ($list -ne $null)
  17.     {
  18.         $list.Items | % {
  19.             $_["AssignedTo"] | ? {
  20.                  ($_.User -eq $null) -and (-not $foundMissingUsers.ContainsKey($_.LookupId)) } | % {
  21.                      if ($_ -ne $null ) { $foundMissingUsers.Add($_.LookupId, $_.LookupValue) }
  22.                  }
  23.         }
  24.  
  25.         $foundMissingUsers | % { $_ }
  26.     }
  27. }

Assuming

$allUserIds = $rootWeb.SiteUsers | % { $_.ID }

we could use

$allUserIds -NotContains $_.LookupId

instead of the condition

$_.User -eq $null

in the script above.

Indeed, we could identify two users on two separate projects, that were deleted by mistake, although they have assignments in the project Tasks lists.

We have recreated the users (and assigned the new users to the corresponding enterprise resources), but they have now another IDs. What can we do to fix the problem? The synchronization does not work anymore on these projects (making the project publishing impossible as well) so it does not provide a solution. We could replace the users in the “Assigned To” field, or simply remove the wrong one (it would be re-inserted by the second part of the UpdateAssignedToField method during the next synchronization), but there is an event receiver (Microsoft.Office.Project.PWA.ManagedModeListItemEventHandler) registered on this list, that cancels any changes in the list items when you want to persist the changes via the Update method. To avoid that, we could temporary disable the event firing, as described here.

We used the following script to fix the errors.

  1. $rootWeb = Get-SPWeb http://YourProjectServer/PWA
  2. $siteUsers = $rootWeb.SiteUsers
  3.  
  4.  
  5. # disable event firing to prevent cancelling updates by PreventEdits method (Microsoft.Office.Project.PWA.ManagedModeListItemEventHandler)
  6. # http://sharepoint.stackexchange.com/questions/37614/disableeventfiring-using-powershell
  7. $receiver = New-Object "Microsoft.SharePoint.SPEventReceiverBase"
  8. $type = $receiver.GetType()
  9. [System.Reflection.BindingFlags]$flags = [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic
  10. $method = $type.GetMethod("DisableEventFiring", $flags)
  11. $method.Invoke($receiver, $null)
  12.  
  13.  
  14. $rootWeb.Webs | ? { $_.Title -eq 'YourProjectName' } | % {
  15.  
  16. $web = $_
  17.  
  18. Write-Host ——————————-
  19. Write-Host $web.Title
  20.  
  21. $userPairs = ((122, 3421), (145, 2701))
  22.  
  23. $userPairsResolved = $userPairs | Select-Object -Property `
  24.   @{ Name="OldUserId"; Expression = { $_[0] }},
  25.   @{ Name="NewUser"; Expression = { $up = $_; $siteUsers | ? { $_.ID -eq $up[1] } }}
  26.  
  27. $list = $web.Lists["Tasks"]
  28.  
  29. if ($list -ne $null)
  30. {
  31.     $list.Items | % { $list.Items | % {
  32.         $item = $_
  33.         [Microsoft.SharePoint.SPFieldUserValueCollection]$assignedTo = $item["AssignedTo"]
  34.         if ($assignedTo -ne $null)
  35.         {
  36.             $isModified = $false
  37.  
  38.             # iterate through the assignments
  39.             for($i = 0; $i -lt $assignedTo.Count; $i++)
  40.             {
  41.                 if ($assignedTo[$i].User -eq $null)
  42.                 {
  43.                     $userName = $assignedTo[$i].LookupValue
  44.                     $userid = $assignedTo[$i].LookupId
  45.                     $taskTitle = $item.Title.Trim()
  46.                     Write-Host Task """$taskTitle""" assigned user """$userName""" "($userId)" missing
  47.                     $newUser = $userPairsResolved | ? { $_.OldUserId -eq $userid } | % { $_.NewUser }
  48.                     if ($newUser -ne $null)
  49.                     {
  50.                         $newUserId = $newUser.Id
  51.                         $newUserName = $newUser.Name
  52.                         do { $replaceAssignedTo = Read-Host Would you like to replace the assignment of the missing user with """$newUserName""" "($newUserId)"? "(y/n)" }
  53.                         until ("y","n" -contains $replaceAssignedTo )
  54.  
  55.                         if ($replaceAssignedTo -eq "y")
  56.                         {
  57.                             # step 1: removing the orphaned entry
  58.                             $assignedTo.RemoveAt($i)
  59.  
  60.                             # step 2: create the replacement
  61.                             [Microsoft.SharePoint.SPFieldUserValue]$newUserFieldValue = New-Object Microsoft.SharePoint.SPFieldUserValue($web, $newUser.Id, $newUser.Name)     
  62.                             $assignedTo.Add($newUserFieldValue)
  63.  
  64.                             # set the 'modified' flag
  65.                             $isModified = $true
  66.                         }
  67.                     }
  68.                     else
  69.                     {
  70.                         Write-Host WARNING No user found to replace the missing user with -ForegroundColor Yellow
  71.                     }
  72.                       }
  73.             }
  74.  
  75.             # update only if it has been changed
  76.             if ($isModified)
  77.             {
  78.             $item["AssignedTo"] = $assignedTo
  79.             $item.Update()
  80.             Write-Host Task updated
  81.             }
  82.         }
  83.     }}
  84. }
  85.  
  86. }
  87.  
  88. # re-enabling event fireing
  89. $method = $type.GetMethod("EnableEventFiring", $flags)
  90. $method.Invoke($receiver, $null)

The variable $userPairs contains the array of old user IDnew user ID mappings. In step 1 we remove the orphaned user entry (the one referring the deleted user), in step 2 we add the entry for the recreated user. If you plan to run the synchronization (for example, by publishing the project) after the script, step 2 is not necessary, as the synchronization process inserts the references for the users missing from the value collection.

Note 1: The script runs only on the selected project (in this case “YourProjectName”), to minimize the chance to change another project unintentionally.

Note 2: The script informs a user about the changes it would perform, like to replace a reference to a missing user to another one, and waits a confirmation (pressing the ‘y’ key) for the action on behalf on the user executes the script. If you have a lot of entries to change, and you are sure to replace the right entries, you can remove this confirmation and make the script to finish faster.

Content Query Web Part Issue on SharePoint 2013 Developer Site – Just another Workaround

Filed under: Bugs, CQWP, SP 2013 — Tags: , , — Peter Holpar @ 05:42

Yesterday I was to test the Content Query Web Part (CQWP), but when wanted to edit its settings, I got an error that was logged in ULS like:

Application error when access /Pages/YourPage.aspx, Error=Key cannot be null.  Parameter name: key   at System.Collections.SortedList.IndexOfKey(Object key)     at System.Collections.SortedList.ContainsKey(Object key)     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.AppendListTypes(SortedList sortedListItems, SPListTemplateCollection listTypes)     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.populateListTypeDropDown()     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.populateCBQControls()     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.OnPreRender(EventArgs e)     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System…
….Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
System.ArgumentNullException: Key cannot be null.  Parameter name: key    at System.Collections.SortedList.IndexOfKey(Object key)     at System.Collections.SortedList.ContainsKey(Object key)     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.AppendListTypes(SortedList sortedListItems, SPListTemplateCollection listTypes)     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.populateListTypeDropDown()     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.populateCBQControls()     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.OnPreRender(EventArgs e)     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecur…
…siveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
Getting Error Message for Exception System.Web.HttpUnhandledException (0x80004005): Exception of type ‘System.Web.HttpUnhandledException’ was thrown. —> System.ArgumentNullException: Key cannot be null.  Parameter name: key     at System.Collections.SortedList.IndexOfKey(Object key)     at System.Collections.SortedList.ContainsKey(Object key)     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.AppendListTypes(SortedList sortedListItems, SPListTemplateCollection listTypes)     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.populateListTypeDropDown()     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.populateCBQControls()     at Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart.OnPreRender(EventArgs e)    …
… at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Control.PreRenderRecursiveInternal()     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.HandleError(Exception e)     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesA…
…fterAsyncPoint)     at System.Web.UI.Page.ProcessRequest()     at System.Web.UI.Page.ProcessRequest(HttpContext context)     at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

As it turned out by the analyzing the source code of the methods in the stack trace, the error is caused by the AppendListTypes method (Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart class in Microsoft.SharePoint.Publishing assembly). This method iterates through the available list templates in the web site just to add their name to a list box of the tool part. The ContainsKey method of the System.Collections.SortedList class is used to check, if the given name already exists in the list, for the case multiple list templates would have the same name. In this case a numeric counter would be appended to the name to avoid name conflicts in the list. So far so good.

But if the Name property of the list template happens to be null, calling the ContainsKey method with this value as key causes an ArgumentNullException. It is the IndexOfKey method of the SortedList class that throws the exception, just to be exact.

We can find out which list templates have null as their Name via this script:

$web = Get-SPWeb http://YourSite
$web.ListTemplates | ? { $_.Name -eq $null }

This script returns in our case a single list template with InternalNameDraftAppsListTemplate”. The type property of the template is SPListTemplateType.DeveloperSiteDraftApps, that means the integer value 1230 (decimal) or 0x4ce (hexadecimal).

Just as remark: there is another template without name, but in this case the name is not null, but it is an empty string, so it does not cause problem for the CQWP tool part. This list template has the internal name “wfsvc”. Its list template type is not included in the SPListTemplateType enumeration, the integer value of the type is 4501. This list template serves as a base for a list instance that is used by SharePoint to keep track of workflows.

My first idea was to set the Name property of the “DraftAppsListTemplate” template via a script like this:

$lt = $web.ListTemplates | ? { $_.Name -eq $null }
$lt.Name = ‘DraftAppsListTemplate’
$lt.Update()

Of course it fails, due to the read-only behavior of the Name property and due the lack of the Update method on the SPListTemplate class.

As a second try, I searched for possible solutions on the web, and found this forum thread, where it is suggested as a workaround to temporary inactivate the feature the list template belongs to while one wants to edit the CQWP properties:

Disable-SPFeature -Identity e374875e-06b6-11e0-b0fa-57f5dfd72085 -Url http://YourSite

I don’t like this idea, as:

– there might be cases that somebody without the permission to activate / inactivate features should edit the CQWP

– inactivating the feature affects the site functionality and may disrupt of work other using the same environment

So instead of that I came up with another workaround. Of course, it is not a fully supported solution so you should use it only for your own risk.

I’ve created a backup copy of the file containing the list template (located at 15\TEMPLATE\FEATURES\Developer\DeveloperListTemplates.xml), and edited the original version, adding the missing DisplayName attribute to the XML definition of the list template.

Note: The DisplayName attribute corresponds to the Name property of the SPListTemplate class, while the Name attribute translates to the InnerName property.

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/"&gt;
  <ListTemplate
      FeatureId="E374875E-06B6-11E0-B0FA-57F5DFD72085"
      BaseType="0"
      Name="DraftAppsListTemplate"
      DisplayName="DraftAppsListTemplate"
      DisableAttachments="TRUE"
      DisallowContentTypes="TRUE"
      FolderCreation="FALSE"
      Hidden="TRUE"
      HiddenList="False"
      OnQuickLaunch="False"
      SecurityBits="11"
      Type="1230"
      VersioningEnabled="FALSE"
      AllowDeletion="FALSE" >
  </ListTemplate>
</Elements>

I saved the changes and performed an IISRESET. After that I had no more list templates with null as Name, and was able to edit the CQWP properties without inactivating any features.

The real solution would be to fix the error in the AppendListTypes method.

First of all, one should avoid possible null values. Instead of the current code:

string name = template.Name;

it would be better:

string name = (template.Name != null) ? template.Name : template.InnerName;

I assume the Name attribute of the ListTemplate schema, that corresponds the InnerName property of the SPListTemplate class is a mandatory one, as described here. So the new expression could not be evaluated to null any more. Although at the same place the DisplayName attribute that corresponds to the Name property of the SPListTemplate class is defined as required either, its lack of the original version of the DeveloperListTemplates.xml file demonstrates, it is not mandatory in practice. If we want to be sure we can append this line to the former one:

name = name ?? string.Empty;

Second, I don’t see if there is really any possible business case, when one would include items from lists created based on the DraftAppsListTemplate in the CQWP. So one should simply skip this list template at all in the modified version of the AppendListTypes method:

switch (type)
{
    case 110:
    case 0x76:
    case 0x75:
    case 0x2776:
    case 600:
    case 1230:
        break;

    default:
    {
       // code block is omitted intentionally
    }
}

Our farm is patched with Cumulative Update, May 2016, might be that Microsoft provides a fix in a later update.

May 16, 2016

Project Server Displays Incorrect Effective Rights for Resources

Filed under: Bugs, PS 2013, Security — Tags: , , — Peter Holpar @ 15:55

We observed the following – in my opinion buggy – behavior in case of Project Server 2013 (patch state: 2016 March CU):

In our project web sites we have a web page that should display the name of the project and the title of the project owner. The name is displayed using client-side technologies, that means JavaScript and the Project Server JavaScript object model. See the code snippets below. Note, that these are parts of an AngularJS applications and cannot be used alone, but only part of the whole application. I show the code only to provide you an overview, about what I’m writing here.

The “business logic” from the controller:

  1. var promise = OurCustomService.getProjInfo($scope);
  2. promise.then(function (pi) {
  3.     var projName = pi.project.get_name();
  4.     // for some users the get_owner() mehtod returns null
  5.     var projManName = pi.project.get_owner().get_title();            
  6. }, function (errorMsg) {
  7.     console.log("Error: " + errorMsg);
  8. });
  9.  
  10.     }, function (errorMsg) {
  11.         console.log("Error: " + errorMsg);
  12.     });
  13. });

…and the service code:

  1. this.getProjInfo = function ($scope) {
  2.     var deferred = $q.defer();
  3.  
  4.     var ctx = new SP.ClientContext.get_current();
  5.  
  6.     var projContext = PS.ProjectContext.get_current();
  7.     projContext.set_isPageUrl(ctx.get_isPageUrl);
  8.     var proj = projContext.get_projects().getById($scope.projectId);
  9.     projContext.load(proj, "Name", "Owner.Title");
  10.  
  11.     projContext.executeQueryAsync(
  12.         function () {
  13.             deferred.resolve(
  14.                 {
  15.                     project: proj
  16.                 });
  17.         },
  18.         function (sender, args) {
  19.             deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  20.         }
  21.     );
  22.  
  23.     return deferred.promise;
  24. };

We found that this solution does not work for a lot of our users. Having a look via Internet Explorer (F12) Developer Tools in the code running with their credentials I found that the object returned by the pi.project.get_owner() expression is null, causing an exception as I want to access the get_title() method of this null object. It was obviously a security issue. To be able to access the title of the project owner (that is a resource as well), the user should have the View Enterprise Resource Data category permission in relation to the project owner resource.

When checking the Owner property of the Project via REST (the Guid in the URL is the ID of the given project):

http://YourProjectServer/PWA/_api/ProjectServer/Projects(‘1EF03FA9-2F7A-E411-80D4-005056B47337&#8217;)/Owner

the users having the problem received null as result, however, other users having more permissions (including the required one) received the full info of the project owner as expected.

Similarly, we have checked the resources available for the user via the REST query:

http://YourProjectServer/PWA/_api/ProjectServer/EnterpriseResources

The result for the “problematic” users did not contained the resource that is the project owner, however for the other users (the ones who had no problem with the AngularJS application mentioned above) the result included this resource as well.

No problem, it sounds OK up to this point.

However, when we selected any of  “problematic” these users on the Manage Users page in PWA Settings, clicked Check Effective Rights, change the Permission Type to Category Permission – Resource, and selected the selected the project owner (the one, the user has in practice no permission at all) in the Security Object – Resource list, the report shows, that the user has View Enterprise Resource Data permission via a group (let’s say All Users) and a category (let’s say My Project Team). Then we clicked other resources in the Security Object – Resource list as well, and found, that based on the report, the user should have View Enterprise Resource Data permission to almost all of these resources either, although based on the REST query above (http://YourProjectServer/PWA/_api/ProjectServer/EnterpriseResources) he has permission only a very few of them.

That is pretty strange. The users are really member of the All Users group, and the My Project Team category is really assigned to the All Users group.

The resources affected by the My Project Team category are selected by the “They are members of a Project Team on a project owned by the User” rule:

image

Members of the All Users group have View Enterprise Resource Data permission on resources included in the My Project Team category:

image

The resources displayed by the Effective Rights page as ones the “problematic” users have permission to are however no team members of the users at all!

How is it possible? In this post I don’t want to bore you with very deep technical details (I plan to post these details in a follow-up post later), the most important facts are, that the objects and stored procedures used to check the permissions when you want to access a resource differ from the ones used to display the effective rights.

For example, when checking the “They are members of a Project Team on a project owned by the User” rule, the pub.MSP_WEB_FN_SEC_ResourcesInCategoryRule3 table-valued function is used, where @res_uid parameter is the resource ID of the current user. It should return the ID of all of the resources that are affected by this category rule:

SELECT RES_UID AS OBJ_UID
FROM pub.MSP_ASSIGNMENTS
WHERE WRES_UID_MANAGER = @res_uid
  AND WASSN_DELETED_IN_PROJ = 0

UNION

SELECT PR.RES_UID AS OBJ_UID
FROM pub.MSP_PROJECTS P
INNER JOIN pub.MSP_PROJECT_RESOURCES PR ON PR.PROJ_UID = P.PROJ_UID
WHERE P.WRES_UID = @res_uid

That means, resources returned by the query if the resource that belongs to the current user (the one that wants to access another resource) is either an assignment owner of  a non-deleted project task assignment where the target resource (the one the current user want to access) is the assignment resource (first part of the UNION query), or there is a project that has the current user as project manager and the target resource as project resource (second part of the UNION query). That sounds logically.

On the contrary, when displaying the effective rights, the pub.MSP_WEB_FN_SEC_GetEffectiveCategories_NONCLAIMSCOMPLIANT tabled-value function is called by the pub.MSP_WEB_SP_SEC_ReadUserEffectiveRightsWithCategoryPermissions_NONCLAIMSCOMPLIANT stored procedure. This function uses the following condition to check the “They are members of a Project Team on a project owned by the User” rule, where @res_uid parameter is the resource ID of the current user, and the @wsec_obj_uid parameter is the ID of the target resource. It should insert the value 3 into the temporary @rule_table is the target resource is affected by the category rule:

IF EXISTS (SELECT TOP 1 RES_UID FROM MSP_ASSIGNMENTS WHERE WRES_UID_MANAGER = @res_uid AND RES_UID = @wsec_obj_uid)
    OR EXISTS (SELECT TOP 1 RES_UID FROM MSP_PROJECT_RESOURCES WHERE RES_UID = @wsec_obj_uid)
    OR EXISTS (SELECT TOP 1 WRES_UID as RES_UID FROM MSP_PROJECTS WHERE WRES_UID = @res_uid)
BEGIN
    INSERT INTO @rule_table(WSEC_OBJ_RULE_TYPE) VALUES (3)
END

As far as I see, this condition is wrong. It says that there should be an assignment having the current user as an assignment owner and the target resource as assignment resource (see first part of the UNION in the first SQL query above, differs in checking the WASSN_DELETED_IN_PROJ flag), or there is any project, where the target resource is a resource, or there is any project where the current user is the project manager (compare with the second part of the UNION query above, condition this time is total wrong). It means we may receive a false positive on the Effective Rights page for each resources, that are resources on any project, and for all resources if the current user (the one we check the effective rights for) is a project manager of any project. In fact, we should receive a positive value in all of these cases (as long as there is no explicit deny), it is a false positive, when there is no other, valid positive value via other categories.

I think one should re-arrange the condition like this:

IF EXISTS (SELECT TOP 1 RES_UID FROM MSP_ASSIGNMENTS WHERE WRES_UID_MANAGER = @res_uid AND RES_UID = @wsec_obj_uid AND WASSN_DELETED_IN_PROJ = 0)
    OR EXISTS (SELECT TOP 1 PR.RES_UID FROM MSP_PROJECTS P INNER JOIN MSP_PROJECT_RESOURCES PR ON PR.PROJ_UID = P.PROJ_UID
    WHERE PR.RES_UID = @wsec_obj_uid AND P.WRES_UID = @res_uid)
BEGIN
    INSERT INTO @rule_table(WSEC_OBJ_RULE_TYPE) VALUES (3)
END

or even better, one could simply re-use the logic implemented in the pub.MSP_WEB_FN_SEC_ResourcesInCategoryRule3 table-valued function:

IF EXISTS (SELECT TOP 1 OBJ_UID FROM pub.MSP_WEB_FN_SEC_ResourcesInCategoryRule3(@res_uid) WHERE OBJ_UID = @wsec_obj_uid)
BEGIN
    INSERT INTO @rule_table(WSEC_OBJ_RULE_TYPE) VALUES (3)
END

April 20, 2016

How to Start the Wrong SharePoint Workflow Unintentionally from the UI

Filed under: Bugs, SP 2010 — Tags: , — Peter Holpar @ 22:38

A few weeks ago we wanted to start a specific workflow on a SharePoint list item, that is located in a list that has multiple custom workflows (none of them has a workflow initiation form) associated with it. We performed the action at the very same time with a colleague of mine (having only 4 seconds difference, as it turned out later), independently from each other. After a while, when we checked the status of the item, we found, that two workflows started on the item. The one, we both wanted to start, was started by my colleague, and I’ve started an other workflow. My colleague said I might have clicked the wrong workflow on the workflow.aspx web page, but I was sure I clicked the right one.

What happened?

I was able to reproduce the issue in our test environment by loading the Workflow page in two separate browser tabs, and starting the same workflow in each of them. I was pretty confident, that the developers of the page made the mistake to try to start the selected workflow by its index in the array of available workflows, instead of the Id of the workflow association.To be sure, I’ve checked the source of the workflow.aspx file (located in the LAYOUTS folder, having a lot of in-line code) and the class behind it, the Microsoft.SharePoint.ApplicationPages.WorkflowPage class (located in the Microsoft.SharePoint.ApplicationPages assembly).

Note: If you try to start the last workflow on the page (or as a special case of it, you have only a single associated workflow) twice in two separate browser, you get an error on the second try. In this case you have the following entry (Level: Unexpected) in the ULS logs:

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.  Parameter name: index    at System.Collections.ArrayList.get_Item(Int32 index)     at Microsoft.SharePoint.ApplicationPages.WorkflowPage.OnLoad(EventArgs e)     at System.Web.UI.Control.LoadRecursive()     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

The OnLoad method of the WorkflowPage class calls the ConstructStartArray method if it is not a page post back. In the ConstructStartArray a new ArrayList is created in the m_alwaStart field, and it is populated by the available workflows, by iterating through the workflow associations on the list, on the content type, and on the web level. In each case, the FCanStartWorkflow method of the base class (WorkflowPageBase) is invoked to ensure the current user has the permission to start the workflow manually (see the SPWorkflowAssociation.PermissionsManual property) and if there is no running instance of this workflow type on the item already (via the FIsRunningWt method of the same class). By the end of the ConstructStartArray method the ArrayList in the m_alwaStart field contains the workflows the user can start on the item. So far so good.

Let’s see how this list is rendered in the in-line code of the workflow.aspx page.

<%
    bool fColumnPosition=true;
    int iwa = 0;
    strImageUrl = "/_layouts/images/availableworkflow.gif";
    foreach (SPWorkflowAssociation wa in m_alwaStart)
    {
        string strIniUrl = GenerateStartWorkflowLink(wa);
        if (strIniUrl == null)
            strIniUrl = "javascript:StartWorkflow(" + Convert.ToString(iwa) + ")";
%>

<%

    iwa++;

%>

As you can see, the parameter used with the StartWorkflow method is really a simple counter, the index of the workflow association in the ArrayList in the m_alwaStart field.

The StartWorkflow JavaScript method simply sets a form value (iwaStart) and posts back the page:

function StartWorkflow(iwa)
    var elIwaStart = document.getElementById("iwaStart");
    elIwaStart.value = iwa;
    theForm.submit();
}

The server side GenerateStartWorkflowLink method of the WorkflowPage class, that you can also see in the inline-code above should display the workflow initiation form for the workflow association, if any exists.

Back to the server side, and let’s see what happens with the value posted back by the StartWorkflow method in the OnLoad method of the WorkflowPage class. If the request is a post back, than it reads the index of the workflow to start, and looks up the workflow by this index from the array of workflow associations in the m_alwaStart field:

int num2 = Convert.ToInt32(base.Request.Form["iwaStart"]);
if (num2 >= 0)
{
    base.StartWorkflow((SPWorkflowAssociation) this.m_alwaStart[num2]);
}

Problem: this array might be not the same, as the one returned on the first page load. If a workflow that precedes the workflow we want to start (or the same workflow) is started in the meantime, the workflow associations are changed (for example, workflows are registered or removed on the web, on the list or on the content type level), or the permissions are changed, it is possible (or even very likely) that the user starts another workflow, not the one he clicked on on the web UI.

Solution: would be to use the Id (of type Guid) of the Microsoft.SharePoint.Workflow.SPWorkflowAssociation instance as the identifier of the item in the array instead of  the index / position in the array.

That would mean in the in-line code, instead of using the iwa counter:

strIniUrl = "javascript:StartWorkflow(" + Convert.ToString(wa.Id) + ")";

and in the OnLoad method, handling the post back as:

Guid waId = Guid.Parse(base.Request.Form["iwaStart"] as string);
base.StartWorkflow(this.m_alwaStart.ToArray().First<SPWorkflowAssociation>(wa => wa.Id == waId));

Note: I could reproduce this buggy behavior in SharePoint 2010 and in SharePoint 2013 using site collections that were not upgraded to the SharePoint 2013 mode. However, as long as I see, “native” SharePoint 2013 sites do suffer from the same kind of problem.

March 3, 2016

Access Denied when Setting Group Properties as Group Owner

Filed under: Bugs, Security, SP 2013 — Tags: , , — Peter Holpar @ 22:10

We have a SharePoint group whose members should administer group membership of other, business-related SharePoint groups. We assigned this group as a Group Owner to the other groups, thus they have the required permissions to group administration.

This week one of the group administrators was to change the description of one of the groups. Actually, it is not part they mission, however I don’t see any problem with that. SharePoint itself defines the rule of the group owner on the Change Group Settings page:

The owner can change anything about the group such as adding and removing members or deleting the group.

Yes, they can even delete the group, I have tested it.

However in this case the member of the group owner group received an Access Denied error, when he submitted the changes to the server. To be able to understand the reason, let’s first see the corresponding ULS logs:

03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           8e2s    Medium      Unknown SPRequest error occurred. More information: 0x80070005    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           aix9j    High        SPRequest.AddOrUpdateItem: UserPrincipalName=i:0).w|s-1-5-21-1613396233-3282607421-4023646941-2481, AppPrincipalName= ,bstrUrl=
http://YourSharePoint ,bstrListName={F0FF0E7F-61FE-4BBF-993A-2F396E44E133} ,bAdd=False ,bSystemUpdate=False ,bPreserveItemVersion=False ,bPreserveItemUIVersion=False ,bUpdateNoVersion=False ,pbstrNewDocId=00000000-0000-0000-0000-000000000000 ,bHasNewDocId=False ,bstrVersion=23 ,bCheckOut=False ,bCheckin=False ,bUnRestrictedUpdateInProgress=False ,bMigration=False ,bPublish=False ,bstrFileName=<null>    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)), StackTrace:    at Microsoft.SharePoint.SPListItem.AddOrUpdateItem(Boolean bAdd, Boolean bSystem, Boolean bPreserveItemVersion, Boolean bNoVersion, Boolean bMigration, Boolean bPublish, Boolean bCheckOut, Boolean bCheckin, Guid newGuidOnAdd, Int32& ulID, Object& objAttachmentNames, Object& objAttachmentContents, Boolean suppressAfterEvents, String filename, Boolean bPreserveItemUIVersion)     at Microsoft.SharePoint.SPListItem.UpdateInternal(Boolean bSystem, Boolean bPreserveItemVersion, Guid newGuidOnAdd, Boolean bMigration, Boolean bPublish, Boolean bNoVersion, Boolean bCheckOut, Boolean bCheckin, Boolean suppressAfterEvents, String filename, Boolean bPreserveItemUIVersion)     at …    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      …Microsoft.SharePoint.SPListItem.Update()     at Microsoft.SharePoint.ApplicationPages.CBaseNewGroup.UpdateAdditionalProperties(Int32 groupId)     at Microsoft.SharePoint.ApplicationPages.EditGroup.DoOperation()     at Microsoft.SharePoint.ApplicationPages.CBaseNewGroup.BtnOK_Click(Object sender, EventArgs e)     at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessRequest()     at System.Web.UI.Page.ProcessRequest(HttpContext context)     at System.Web.HttpApplication.CallHandlerExecutionStep.Syste…    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      …m.Web.HttpApplication.IExecutionStep.Execute()     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)     at System.Web.HttpApplication.PipelineStepManager.ResumeSteps(Exception error)     at System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb)     at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.UnsafeII…    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      …SMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)     at System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)      e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ftd0    Medium      Access Denied. Exception: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)), StackTrace:   at Microsoft.SharePoint.Library.SPRequestInternalClass.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bPreserveItemUIVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bUnRestrictedUpdateInProgress, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback)  …    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ftd0    Medium      …   at Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bPreserveItemUIVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bUnRestrictedUpdateInProgress, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback).    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             Micro Trace                       uls4    Medium      Micro Trace Tags: 0 nasq,6 agb9s,18 ak8dj,12 b4ly,0 b4ly,41 aix9j,0 ai1wu,0 ftd0    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             Monitoring                        b4ly    Medium      Leaving Monitored Scope (Request (POST:
http://YourSharePoint/_layouts/15/editgrp.aspx?Group=YourGroup&Source=http%3A%2F%2FYourSharePoint%2F%5Flayouts%2F15%2Fpeople%2Easpx%3FMembershipGroupId%3D6650)). Execution Time=81,9053366451775    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             Claims Authentication             amge7    Medium      SPFederationAuthenticationModule.IsRedirectToLogOnPage: Detected a redirection but the redirect is not to a known signin page:
http://YourSharePoint/_layouts/15/AccessDenied.aspx    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x49EC    SharePoint Foundation             Monitoring                        nasq    Medium      Entering monitored scope (Request (GET:
http://YourSharePoint/_layouts/15/AccessDenied.aspx)). Parent No    

As you can see, on the page editgrp.aspx we have an UnauthorizedAccessException: Access is denied. However, at first it seems to be not group-related, as it is thrown in the SPListItem.AddOrUpdateItem method. What’s that?

As I am aware of that the rich text description of a group is stored in the Notes field corresponding list item in the hidden user information list,and not in the SPGroup object itself, at this point I had already an idea, what might be the reason of the the issue. But let’s prove that scientifically!

Later in the stack trace we found the UpdateAdditionalProperties method of the Microsoft.SharePoint.ApplicationPages.CBaseNewGroup class, called by the DoOperation method of the Microsoft.SharePoint.ApplicationPages.EditGroup class.

The DoOperation method updates group properties stored in the SPGroup object itself, but the UpdateAdditionalProperties method updates the information stored in the list item fields in the user information list, like the description of the group, and other fields requested in optional query string parameters:

  1. protected void UpdateAdditionalProperties(int groupId)
  2. {
  3.     SPListItem itemById = base.Web.SiteUserInfoList.GetItemById(groupId);
  4.     string text = this.txtGrpDescription.Text;
  5.     itemById["Notes"] = text;
  6.     int num = 1;
  7.     while (true)
  8.     {
  9.         string str2 = base.Request.QueryString["FieldName" + num.ToString(CultureInfo.InvariantCulture)];
  10.         if (string.IsNullOrEmpty(str2))
  11.         {
  12.             break;
  13.         }
  14.         string str3 = base.Request.QueryString["FieldValue" + num.ToString(CultureInfo.InvariantCulture)];
  15.         itemById[str2] = str3;
  16.         num++;
  17.     }
  18.     itemById.Update();
  19. }

A minor detour about the user information list. As you know, it is a hidden list on the SharePoint site collection root web site, and typically accessed via the URL http://YourSharePoint/_catalogs/users/simple.aspx. However, it is a common misunderstanding, even in case of well-known SharePoint experts, that “This list is only visible to and accessible by administrators”. Based on my experience, it is true only for the webpage (_catalogs/users/simple.aspx), but not for the list itself. The list inherits its permissions from the root site, meaning anyone having read permission on the root site, can access the list.

For example, simply by using REST:

http://YourSharePoint/_api/Web/SiteUserInfoList

or (assuming the Guid is the ID of the list):

http://YourSharePoint/_api/Lists/GetById(‘6749e2d2-ca87-445f-8fc1-b7f7a4e410ad&#8217;)

However

http://YourSharePoint/_api/Lists/GetByTitle(‘User%20Information%20List&#8217;)

does not work, because it is a hidden list.

One can even list all site users via the URL:

http://YourSharePoint/_api/Web/SiteUserInfoList/Items?$select=Title,Name

To be sincerely, I’m not sure if it is a feature or simply a security vulnerability. I’ve also tried to update the list items of the user list from the client side using the managed client object model by the credentials of a user having write permission on the site, but up to know I have not “succeeded”, received access denied. But I don’t give up. Winking smile

After the detour, let’s back to our issue. Since in our case the user had no write permissions on the root site (and thus no write permissions on the list and the list items), the UpdateAdditionalProperties method was not able to set the group description (the Notes field of the list item) and resulted in the Access Denied error. Other members of the same owner group, having the write permission on the root site level, have no such problems.

It is important to point out, that even if the user has no write permission and get the Access Denied error, other changes in the group configuration (like the Name or “Who can edit the membership of the group“) are updated for the group, as they are already saved by the DoOperation method before the exception was thrown by the UpdateAdditionalProperties method. You can prove that by navigating back to the Change Group Settings page, and reload it in browser via F5. The same is true, when one sets the group properties by code, like the managed or JavaScript client object model or by REST request. The owner can change group properties stored in the SPGroup object without error, but not the list item in the user list.

I consider this behavior to be a bug, and I think the UpdateAdditionalProperties method should include an elevated privileges code block to enable group owners to change the properties stored in the list item fields as well.

That’s all I wanted to share about the error itself, but if you have time, you can read on for another story.

There is an other misunderstanding I read in other SharePoint blogs while researching the issue. One may think, that setting a group as its own group owner (Solution 1) and selecting the “Group Members” option for “Who can edit the membership of the group“ (Solution 2) are interchangeable (see Scott Baitz stating his Solution 2 “will provide the same functionality as the solution above”, Solution 1). That is definitely false. Let’s see the difference, starting with Solution 2, then comparing the extra permissions one get via Solution 1.

Solution 2

If you allow group members to edit group membership by implementing Solution 2, the group members can add other users to the group or remove users, even themselves from the group. (Note: If they removed themself  from the group, that action would be of course not reversible. They can not add themself back to the group, only if they have that permission via the group owner group or they have other administrative permission, like site collection administrator, or full control permission on the site.)

The group members have access to the page People and Groups : YourGroup (http://YourSharePoint/_layouts/15/people.aspx?MembershipGroupId=4600, assuming 4600 is the ID of your group). where they can list and change the group membership.

On this page they have the following options:

New menu:
Add Users

Actions menu:
E-Mail Users
Call/Message Selected Users
Remove Users from Group

Settings menu:
View Group Permissions

The group members can not edit group properties. The “Group Settings” is not displayed in the Settings menu, and even if they try to access the page by its URL (like http://YourSharePoint//_layouts/15/editgrp.aspx?Group=YourGroup), they get an error message:

Only owners of the group "YourGroup" can change its settings

(Actually, the permissions are checked in the InitPage method of the Microsoft.SharePoint.ApplicationPages.EditGroup class, by invoking the CanCurrentUserManageGroup method of the Microsoft.SharePoint.SPGroup class.)

Solution 1

As a group owner, you have the same options as in the case of Solution 1, and additionally you have the “Group Settings” in the Settings menu to access the People and Groups : Change Group Settings page and edit group properties. (Of course, if you remove yourself or your group from the Group Owner field, and you have no other extra permission on the site, you can not undo this action. It’s the same as above in Solution 2 with group membership.). If the group owner has no write permission of the root site, an Access Denied is thrown when setting group properties via the page, see the original issue in this post.

Finally, when a user browses the groups in a site, he can found that the “View Group Permissions” is displayed in the  Settings menu for one group, but it is not available for the other group. This option (as you can expect) is again permission dependent. The permissions are checked in the OnLoad method of the Microsoft.SharePoint.ApplicationPages.PeoplePage class (the code behind of the people.aspx page). The “View Group Permissions” option is visible, if the DoesCurrentUserHavePermission method of the Microsoft.SharePoint.WebControls.GroupPermissions class returns true:

internal static bool DoesCurrentUserHavePermission(SPWeb web, SPGroup group)
{
    if (!group.ContainsCurrentUser && !group.CanCurrentUserEditMembership)
    {
        return false;
    }
    return true;
}

(Note that the method has two parameters. The first one is of type SPWeb but that is not used at all in the method.)

That means, the “View Group Permissions” option is displayed only if the user is member of the group or can edit the group membership. On the other side, if the user can access the people.aspx page for the group, and see the group membership only because the group is configured to allow “Everyone” as “Who can view the membership of the group“, this option is not available. With other words, a user that is not in the group itself, not the owner of the group, and have no admin rights are not allowed to see the group permissions.

February 17, 2016

Visual Studio Build Error: Could not copy the file because it was not found

Filed under: Bugs, Visual Studio — Tags: , — Peter Holpar @ 22:03

Recently we got a bunch of errors when building a project in Visual Studio 2013, complaining about missing content files:

Could not copy the file "C:\projects\OurProject\images\image1.png" because it was not found.

We checked the files in the file system, but they have really disappeared. We had luck, as we had the original files under source control, so we could restore them from the repository. But it did not provide a long-term solution, as we got the very same error on the next build, so we had to restore them again. Restarting Visual Studio did not help as well.

To prohibit further deletion I set the files as read-only. It led me to the solution, as we got this time another build error message:

Unable to copy file "C:\projects\OurProject\images\image1.png" to ".\image1.png". Access to the path ‘.\image1.png’ is denied.

The problem was that the Output path (under project properties Build / Output) was accidentally deleted, so Visual Studio was to create the build at the source file location. Resetting the original value, the default bin/debug path resolved our issue.

One can simple reproduce the issue, simply create a new project (let it be a Console Application, for example), add an image as content file (Build Action = Content) to it and set its Copy to Output Directory property to Copy always. Then delete the content of the Output path property, save the project and try to build.

I know that it was our mistake, but to tell the truth, I would expect Visual Studio not to delete my source files, but rather validate my input, and not allow to leave the Output path empty.

February 17, 2015

Unhandled Exception in Gantt Chart View after Editing Calendar Item in Datasheet View

Filed under: Bugs, Calendar, SP 2010 — Tags: , , — Peter Holpar @ 22:48

The other day a user complained that since last week he receives an exception when navigating to a customized Calendar list in SharePoint.

The error details found in ULS logs and displayed on the web UI after turning custom errors off:

Unable to cast object of type ‘System.DBNull’ to type ‘System.String’.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidCastException: Unable to cast object of type ‘System.DBNull’ to type ‘System.String’.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. 

Stack Trace:
[InvalidCastException: Unable to cast object of type ‘System.DBNull’ to type ‘System.String’.]
   Microsoft.SharePoint.WebControls.GanttV4.<NormalizeDateFields>b__3a(Nullable`1 value, DataRow dr, String col) +114
   Microsoft.SharePoint.WebControls.GanttV4.TransformDataTableColumns(IEnumerable`1 cols, Func`4 transform) +454
   Microsoft.SharePoint.WebControls.GanttV4.GenerateGridSerializer() +62
   Microsoft.SharePoint.WebControls.GanttV4.OnPreRender(EventArgs e) +243
   System.Web.UI.Control.PreRenderRecursiveInternal() +108
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

image

The single reference we found for this error on the web did not helped at all.

The default view for the list was a Gantt chart view that included all events in the list, other views (like All Events) were displayed without any error.

My very first idea was that the Gantt view should have been altered recently that caused the error, however as we checked the last modified date for the view, it turned out that it has not been modified recently.

Next, as we reduced the item count in the view, the error was displayed not immediately, but only after navigating through several pages of events. It clearly indicated that the problem is caused by data corruption in one or more items. Checking the items created or modified last week, we found a single item. Opening the item for edition from the All Events view, and simply saving it without any modifications solved the issue. Comparing the item’s Xml property before and after the save event the most significant difference was that the fAllDayEvent field (ows_fAllDayEvent attribute in the Xml property) was missing in the former one.

Since the All Day Event column was a mandatory one, it was first a surprise that a such event existed. The only possible solution via the UI (we did not assumed that somebody manipulated the items via code) was the All Events view. Since there were a few custom columns inserted to this view, the All Day Event column was simply removed from the view to provide enough space for the new columns. If we switch this view to the Datasheet View, we can enter new items without performing the validation rules or even saving a default value for the All Day Event field, and so the same error can be reproduced.

Older Posts »

Create a free website or blog at WordPress.com.