Quantcast
Channel: Second Life of a Hungarian SharePoint Geek
Viewing all 206 articles
Browse latest View live

Computing Hash of SharePoint Files

$
0
0

Recently we had to compare master pages of hundreds of sites to find out, which ones contain customizations. Comparing the content of files means in practice typically to compare the hash values calculated based on the file content.

But how to compute the hash of a file stored in SharePoint?

If you had the SharePoint sites mapped to the Windows File System via WebDAV, you could use the Get-FileHash cmdlet from PowerShell 4.0 (or above). In PowerShell 5.0 the Get-FileHash cmdlet is able to compute the hash of input streams as well, so we could use SPFile.OpenBinaryStream method to access the file content, and then compute its hash value.

Since I have only PowerShell 2.0 on my SharePoint server, I had to create my own hashing solution, the Get-SPFileHash function:

function Get-SPFileHash($fileUrl) 

  $site = New-Object Microsoft.SharePoint.SPSite $fileUrl
  $web = $site.OpenWeb()
  $file = $web.GetFile($fileUrl)
  $bytes = $file.OpenBinary()
  $md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
  $hash = [System.BitConverter]::ToString($md5.ComputeHash($bytes))
  return $hash
}

Having this method, it is easy to create a summary of all sites, their master pages, and the hash values of the master page content:

$siteUrl = "http://YourSharePointSite"

$site = Get-SPSite $siteUrl

$site.AllWebs | % {
  $obj = New-Object PSObject
  $obj | Add-Member NoteProperty Url($_.Url)
  $obj | Add-Member NoteProperty MasterUrl($_.MasterUrl)
  $obj | Add-Member NoteProperty FileHash(Get-SPFileHash ($siteUrl + $_.MasterUrl))
  Write-Output $obj
} | Export-CSV "C:\data\MasterPageHash.csv"

You can import the resulting CSV file into Excel and process its content as you wish.

Note: If you simply double-click on the CSV file created by the former script in Windows Explorer, it is not opened in the format you probably wish: values separated into columns. Instead of that, the first column would contain the entire line. You should first prepare the file: open it in Notepad, optionally remove the first header line, and save the file again, changing the encoding from ANSI to Unicode. Next, start Excel, and open the CSV file from Excel, setting the separator character to Comma on the second page of the Text Import Wizard.



How to process the output of the stsadm EnumAllWebs operation

$
0
0

Recently I wrote about how to process the output of the Test-SPContentDatabase PowerShell cmdlet.

If you use the EnumAllWebs operation of the stsadm command, you have similar options as well. However, you have the output in XML format in this case, so we should use XPath expressions to access the information we need.

First, save the result into a text file:

stsadm -o EnumAllWebs -DatabaseName YourContentDB -IncludeFeatures -IncludeWebParts >> C:\Output.xml

then load its content into an XML object:

$reportXml = [Xml] (Get-Content C:\Output.xml)

To list the missing features:

Select-Xml -Xml $reportXml -XPath ‘//Site’ | % { $siteId = $_.Node.Id; Select-Xml -Xml $_.Node -XPath ‘Features/Feature[@Status="Missing"]/@Id’ | % { (Get-SPFeature -Site $siteId -Identity $_.Node.Value).DisplayName } }

Note, that site templates created by saving an existing site as a site template from the UI might be reported as missing features as explained here.

To get an overview of the site templates:

Select-Xml -Xml $reportXml -XPath ‘//Web/@TemplateName’ | % { $_.Node.Value } | Group { $_ }

image

To get an overview of the missing web-scoped features:

Select-Xml -Xml $reportXml -XPath ‘//Web/Features/Feature[@Status="Missing"]/@Id’ | % { $_.Node.Value } | Group { $_ } | % { Write-Host $_.Name – Count is $_.Count }

Finally, to get an overview of the missing web parts:

Select-Xml -Xml $reportXml -XPath ‘//Web/WebParts/WebPart[@Status="Missing"]/@Id’ | % { $_.Node.Value } | Group { $_ } | % { Write-Host $_.Name – Count is $_.Count }


People Picker is very slow when searching users

$
0
0

The environment of a customer of us consists of several Active Directory domains, a few of them were recently migrated from former domains.

Users of the SharePoint sites complained that when they try to look up users via the People Picker, the result is displayed only after a delay of  30-40 seconds, instead of the former 3-5 seconds.

I’ve tried to catch the problem using Wireshark, filtering for the LDAP protocol, as described in this post. However, I found no problem with the requests / responses, except for a delay of about 30 seconds, although no request using this protocol was sent in this time lag. Obviously, the sender process waited for a response sent using another protocol.

Removing the LDAP filter in Wireshark, I found these retransmission attempts:

No.     Time            Source                        Destination   Protocol Length  Info
3241    44.218621000    IP of the SharePoint Server   IP of the DC    TCP    66    53607 > msft-gc [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
3360    47.217136000    IP of the SharePoint Server   IP of the DC    TCP    66    [TCP Retransmission] 53607 > msft-gc [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
3791    53.221414000    IP of the SharePoint Server   IP of the DC    TCP    62    [TCP Retransmission] 53607 > msft-gc [SYN] Seq=0 Win=8192 Len=0 MSS=1460 SACK_PERM=1

The msft-gc is an LDAP-like protocol used to query the Global Catalog (GC) in the Active Directory (uses port 3268). The retransmission timeout (RTO) value of the packet 3360 was 3 sec., the RTO of the packet 3791 was 9 sec., both causing delay in the user search process.

The source IP was the address of the SharePoint server, the IP address in the destination is the address of a former Domain Controller (DC). The server, that acted as DC of a domain that was already migrated was online, but the DC-role was already demoted on it . The IP address of the server was registered in DNS, so the server could be PINGed, but it did not respond to LDAP requests (including msft-gc) anymore.

The entries in the ULS logs has provided further evidence, that there is an issue with the Global Catalog in the AD forest (see the SearchFromGC method in the stack trace below):.

08/06/2015 13:26:34.08     w3wp.exe (0x66BC)                           0x9670    SharePoint Foundation             General                           72e9    Medium      Error in resolving user ‘UserName‘ : System.Runtime.InteropServices.COMException (0x8007203A): The server is not operational.       at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)     at System.DirectoryServices.DirectoryEntry.Bind()     at System.DirectoryServices.DirectoryEntry.get_AdsObject()     at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)     at Microsoft.SharePoint.WebControls.PeopleEditor.SearchFromGC(SPActiveDirectoryDomain domain, String strFilter, String[] rgstrProp, Int32 nTimeout, Int32 nSizeLimit, SPUserCollection spUsers, ArrayList& rgResults)     at Microsoft.SharePoint.Utilities.SPUserUtility.ResolveAgainstAD(String input, Boolean inputIsEmailOnly, SPActiveDirectoryDomain globalCatalog, SPPrincipalType scopes, SPUserCo…    04482a74-c00f-4005-9cd3-11f765eca7a0
08/06/2015 13:26:34.08*    w3wp.exe (0x66BC)                           0x9670    SharePoint Foundation             General                           72e9    Medium      …llection usersContainer, TimeSpan searchTimeout, String customFilter)     at Microsoft.SharePoint.Utilities.SPActiveDirectoryPrincipalResolver.ResolvePrincipal(String input, Boolean inputIsEmailOnly, SPPrincipalType scopes, SPPrincipalSource sources, SPUserCollection usersContainer)     at Microsoft.SharePoint.Utilities.SPUtility.ResolvePrincipalInternal(SPWeb web, SPWebApplication webApp, Nullable`1 urlZone, String input, SPPrincipalType scopes, SPPrincipalSource sources, SPUserCollection usersContainer, Boolean inputIsEmailOnly, Boolean alwaysAddWindowsResolver).    04482a74-c00f-4005-9cd3-11f765eca7a0

Removing the orphaned DC entry from the AD  resolved the People Picker problem as well.


The SharePoint Time Machine

$
0
0

Assume you have a SharePoint list with a lot of items. The list supports versioning and you should provide a snapshot of the items at a given time in the past.

As you know, the Versions property (of type SPListItemVersionCollection) of the SPListItem class contains the item versions. One can access a specific version via the indexer property of the collection, by the ID of the version (where the ID = 512 * major version number + minor version number), or by the version number (a.k.a. label, for example, 2.3), but there is no direct support to get the actual version at a specific time in the past.

To achieve my goal, I’ve implemented the GetVersionFromDate extension method, that iterates through the method, and returns the version we need based on its creation date:

  1. public static SPListItemVersion GetVersionFromDate(this SPListItemVersionCollection versions, DateTime localDate)
  2. {
  3.     SPListItemVersion result = null;
  4.  
  5.     if (versions != null)
  6.     {
  7.         DateTime date = versions.ListItem.Web.RegionalSettings.TimeZone.LocalTimeToUTC(localDate);
  8.  
  9.         SPListItemVersion prevVersion = null;
  10.  
  11.         // versions[0] – current item version
  12.         // versions[versions.Count – 1] – first item version created
  13.         for (int i = versions.Count – 1; i >= 0; i–)
  14.         {
  15.             SPListItemVersion version = versions[i];
  16.             if (version.Created > date)
  17.             {
  18.                 result = prevVersion;
  19.                 break;
  20.             }
  21.             // if it is the last (actual) version and there is no result yet,
  22.             // then the date specified should be greater than the creation date of the last version
  23.             // we take the last version
  24.             else if (i == 0)
  25.             {
  26.                 result = version;
  27.             }
  28.  
  29.             prevVersion = version;
  30.         }                
  31.  
  32.     }
  33.  
  34.     return result;
  35. }

Note, that the Created property stores the creation date as UTC time, that we should convert first.

Using this method accessing the specific version is so simple as:

  1. SPList list = web.Lists["Your List"];
  2. SPListItem item = list.Items.GetItemById(1);
  3.  
  4. DateTime date = DateTime.Parse("2015/06/29 13:40");
  5. SPListItemVersion version = item.Versions.GetVersionFromDate(date);
  6. Console.WriteLine(version["APropertyName"]);

If you go through the items in the list and get the version of the specific time, you already have the required snapshot.


Change in the User Resolution in SharePoint 2013 People Picker

$
0
0

After a SharePoint 2010 to SharePoint 2013 migration our users complained, that in the multiple Active Directory domain environment they have (I wrote about it recently) the People Picker does not resolve the users the same way it did earlier. Only a subset of the users was resolved, users from a few domains were not included in the results at all.

The reason of this issue is a change in the GetTrustedDomains method of the Microsoft.SharePoint.Utilities.SPUserUtility class. Now (in SP 2013) it includes an extra condition, checking the value of  SPWebService.ContentService.PeoplePickerSearchInMultipleForests.

If you need the same behavior as in the SP 2010 version, you should set the value of  the PeoplePickerSearchInMultipleForests property to true.

You can achieve it using PowerShell:

$cs = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$cs.PeoplePickerSearchInMultipleForests = $true
$cs.Update()

or via C#:

SPWebService.ContentService.PeoplePickerSearchInMultipleForests = true;
SPWebService.ContentService.Update();


Waiting for Project Server Queue Operations in Server Side Code

$
0
0

Recently we found a bug in our server-side Project Server code. Our goal was to set some project-related enterprise custom field values. Before setting the values, we tested, if the project is checked-out (to another user), and if so, we forced a a check-in, to be able to check out again to ourselves. After setting the values, we updated the project and published it, including check-in.

  1. if (proj.IsCheckedOut)
  2. {
  3.     proj.Draft.CheckIn(true);
  4. }
  5. DraftProject draftProj = proj.CheckOut();
  6. // set some custom field values
  7. draftProj.SetCustomFieldValue("customFieldName", "customFieldValue");
  8. draftProj.Update();
  9. draftProj.Publish(true);

If the project was not checked-out, the code worked as expected. However, if the project was checked-out to a use, we got an exception despite of the test on the line

DraftProject draftProj = proj.CheckOut();

The exception was:

Microsoft.ProjectServer.PJClientCallableException was unhandled
  _HResult=-2146233088
  _message=CICOCheckedOutInOtherSession
  HResult=-2146233088
  IsTransient=false
  Message=CICOCheckedOutInOtherSession
  Source=Microsoft.ProjectServer
  PSErrorCode=10103
  PSErrorName=CICOCheckedOutInOtherSession
  StackTrace:
       at Microsoft.ProjectServer.PublishedProject.CheckOut()
       at SPGeneral.Program.Main(String[] args) in c:\projects\PSTest\Program.cs:line 37
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

The message “CICOCheckedOutInOtherSession” suggested, that the project is not yet checked-in, although our traces showed that the line proj.Draft.CheckIn(true) was executed.

To understand the source of the problem, we should first understand how the CheckIn / CheckOut and Publish methods are executed:

Both of the CheckIn and Publish methods of the DraftProject class are executed asynchronously. These methods return a QueueJob object. The actual job is performed by the queue subsystem of Project Server.

The CheckOut method of the PublishedProject class is executed synchronously, it returns a DraftProject instance immediately.

In the above sample it means, that proj.CheckOut() is called, before the project would be effectively checked-in as a result of executing proj.Draft.CheckIn(true).

How to solve the problem, keeping our code readable if possible?

If you are working with client-side object model of Project Server, you know probably, that there is a solution for such issues: the WaitForQueue method of the ProjectContext object.Unfortunately, the equivalent of this method is not implemented in the server-side object model. It’s pretty strange, as usually its opposite is the case: methods and properties available on the server-side are many times missing on the client-side OM.

No problem, we can implement a similar method for ourselves!

The WaitForQueue method “wait for the specified queue job to complete, or for a maximum number of seconds”. It returns a JobState object, that should be JobState.Success if the queue job succeeded. If we have a look at the implementation of the method, we can see, that it calls the internal IsPendingJob method of the ProjectContext object to compare the current state of the job with the expected values, the refresh the job status by polling the server side objects. The wait time is decremented by 2 seconds in every iteration, the IsPendingJob method is responsible to sleep the thread for this two seconds.

Note: It means that the maximum wait time specified when calling WaitForQueue method is only an approximate value, as it does not include the send / response time of the server requests involved in the refreshing the job status, for example a one-minute wait time means 30 iterations, that is 30 requests / responses. So don’t be surprise if your wait times are considerably longer than specified if you have a slow network or busy server.

After this theory, lets see our own implementation:

  1. public static bool IsPending(this QueueJob job, out QueueConstants.JobState state)
  2. {
  3.         state = job.JobState;
  4.         switch (state)
  5.         {
  6.             case QueueConstants.JobState.Unknown:
  7.             case QueueConstants.JobState.ReadyForProcessing:
  8.             case QueueConstants.JobState.SendIncomplete:
  9.             case QueueConstants.JobState.Processing:
  10.             case QueueConstants.JobState.ProcessingDeferred:
  11.             case QueueConstants.JobState.OnHold:
  12.             case QueueConstants.JobState.Sleeping:
  13.             case QueueConstants.JobState.ReadyForLaunch:
  14.                 Thread.Sleep(new TimeSpan(0, 0, 2));
  15.                 return true;
  16.         }
  17.     state = QueueConstants.JobState.Success;
  18.     return false;
  19. }
  20.  
  21. public static QueueConstants.JobState WaitToFinish(this QueueJob job, int timeoutSeconds)
  22. {
  23.     QueueConstants.JobState state = QueueConstants.JobState.Unknown;
  24.     while ((timeoutSeconds > 0) && job.IsPending(out state))
  25.     {
  26.         timeoutSeconds -= 2;
  27.     }
  28.     return state;
  29. }

As you can see, instead of extending the PSContext object with a WaitForQueue method, I decided to extend the QueueJob object itself with a WaitToFinish method. It seems to me simply more appropriate.

Note: As you probably know, and as I mentioned in my former posts, the server-side object model is based on the PSI infrastructure. It means, that the extra wait time mentioned above may apply to this solution as well.

Note 2: To be able to use the JobState enumeration value in your code, you should reference the Microsoft.Office.Project.Server.Library assembly in your project.

The modified logic in our application is displayed below:

  1. var timeOutInSec = 60; // wait max a minute
  2. bool canCheckOut = !proj.IsCheckedOut;
  3. if (!canCheckOut)
  4. {
  5.     // Project is checked out. Forcing check-in.
  6.     var job = proj.Draft.CheckIn(true);
  7.     var jobState = job.WaitToFinish(timeOutInSec);
  8.     if (jobState == QueueConstants.JobState.Success)
  9.     {
  10.         canCheckOut = true;
  11.     }
  12.     else
  13.     {
  14.         // WARNING Time-out on project check-in, or job failed
  15.     }
  16. }
  17.  
  18. if (canCheckOut)
  19. {
  20.     DraftProject draftProj = proj.CheckOut();
  21.     // set some custom field values
  22.     draftProj.SetCustomFieldValue("customFieldName", "customFieldValue");
  23.     draftProj.Update();
  24.  
  25.     // Publishing project (incl. check-in!)
  26.     var job = draftProj.Publish(true);
  27.     var jobState = job.WaitToFinish(Constants.DefaultPSJobTimeOut);
  28.     if (jobState == QueueConstants.JobState.Success)
  29.     {
  30.         // Project checked-in + published.
  31.     }
  32.     else
  33.     {
  34.         // WARNING Time-out on project publish / check-in or job failed
  35.     }
  36. }
  37. else
  38. {
  39.     // WARNING Project can not be checked-out / processed
  40. }

Displaying Notifications and Status Messages from a SharePoint-based AngularJS Application Including a FormController

$
0
0

Assume the following requirements: We should create a Single Page Application (SPA) (no SharePoint App!) that reads data from SharePoint using the JavaScript client object model, allows the user to edit the values, displays if a field value was changed (if it is “dirty” vs. “pristine”), performs data validations (if it is “valid” vs. “invalid”) and lets the user to save the changes. AngularJS was selected as the presentation framework for the SPA. On data save, we should give the users feedback on the progress (like “Saving changes…”, “Save completed” or “Error during the save operation”) via standard SharePoint notifications and status messages.

Challenge 1:  The FormController of the AngularJS framework is based on the form HTML element. That means, if we would like to use the features of the FormController, like dirty / pristine, validation, etc., we should include a form element in our HTML application. However, our SharePoint page is an ASP.NET page, that already contains a form element, and it does not like to include multiple ones.

Solution 1: Although there are tricks to bypass this limitation (like this or this one), I chose another way to go. I’ve included a Page Viewer Web Part that displays a “pure” HTML page that is stored in a document library in SharePoint, as well as any other non-standard artifacts of the application (.js and .css files, etc.). This HTML page – displayed in an IFRAME by the Page Viewer Web Part – contains the form element, that does not interfere with the form element on the ASP.NET page.

You can display a notification by calling the SP.UI.Notify.addNotification method, similarly a status message is displayed via the SP.UI.Status.addStatus method. Both of these Notify and Status classes are defined in the SP.UI namespace in the sp.js (and its debug version in sp.debug.js). This JavaScript file is typically referenced in the standard SharePoint pages, however you should add a reference to it in your custom pages, like in the case of our HTML page. If you forget to add the reference, you will get an error like this one:

TypeError: Unable to get property ‘addNotification’ of undefined or null reference

Challenge 2:  There is no notification / status message displayed, even if you add the reference to the sp.js. The reason of the problem is, that the HTML elements required by these methods are defined in the master page of the standard SharePoint sites. Obviously, these elements are not found in our custom page in the IFRAME, so the messages are not displayed.

Solution 2: I’ve found two similar blog posts (this one and this one) describing a similar issue with IFRAME and notification messages in the case of Client App Parts. The first of this two posts states that the problem is the IFRAME itself, that prohibits the communication between the parent page and the IFRAME. Of course, that is wrong. The real reason is the different domain names in the URL of the app part (IFRAME) and the host page, as correctly stated in the second post. If we have the same domain name (and we do have in this case), we do not need the rather complex approach described by the posts(that is still valid for the Client App Parts).  Displaying a notification / status message from the script included in the HTML page in the IFRAME in our case is so simple as to prepend the text ‘parent.’ before the method invocation, for example:

var notifyId = parent.SP.UI.Notify.addNotification("Saving…", true);

Of course, in this case you are using the JavaScript and HTML objects on the parent page, so you don’t need to reference the sp.js in your HTML page.


NullReferenceException in the Content Editor Web Part when Trying to Upload Images

$
0
0

Recently a user complained, that when he tries to upload a photo in a Content Editor Web Part (CEWP)…

image

… an error is displayed.

image

We found the following relevant line in the ULS logs:

11/16/2015 12:46:45.29     w3wp.exe (0x21FFC)                          0x2185C    SharePoint Foundation             Runtime                           tkau    Unexpected    System.NullReferenceException: Object reference not set to an instance of an object.    at ASP._layouts_15_upload_aspx.__Render__control29(HtmlTextWriter __w, Control parameterContainer) in c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\upload.aspx:line 81     at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)     at Microsoft.SharePoint.WebControls.ScriptBlock.Render(HtmlTextWriter writer)     at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)     at ASP._layouts_15_upload_aspx.__Render__control27(HtmlTextWriter __w, Control parameterContainer) in c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\upload.aspx:line 61     at Sy…    b324429d-5597-507f-40b1-b3a2b68192e5

In upload.aspx at line 81 there is a reference for CurrentList.ID:

function LaunchOpenInExplorer()
{
    vCurrentListID = "<%= CurrentList.ID %>";
    vCurrentListUrlAsHTML = "<%= Web.Url + "/" + (CurrentList.RootFolder.Url.Length > 0 ? CurrentList.RootFolder.Url + "/" : "") %>";

}

We got the NullReferenceException, because the CurrentList object is null.

In our case, the CurrentList was null, because somebody has deleted all document libraries from the site where the page with the CEWP was located, so there was no place the user could upload the image.

However, we should not delete all of the libraries if you want to reproduce the issue. The GetActionableDocumentLibraries method of the Microsoft.SharePoint.ApplicationPages.UploadPage class is responsible for populating the list of document libraries of the upload page that are candidates for image upload. We can find the following loop and conditions in this method:

List<SPDocumentLibrary> list = new List<SPDocumentLibrary>();
foreach (SPList list2 in web.Lists)
{
  …
  SPDocumentLibrary item = list2 as SPDocumentLibrary;
  if ((((item != null) && !item.Hidden) && ((SPBaseType.DocumentLibrary == item.BaseType) && (SPListTemplateType.WebPageLibrary != item.BaseTemplate))) && (!item.IsCatalog && (!requireAddListItemsPermissions || (requireAddListItemsPermissions && list2.DoesUserHavePermissions(SPBasePermissions.AddListItems, false)))))
  {
    list.Add(item);
  }

}

As you can see, the library should not be a hidden one, should not be the Site Pages library or any of the standard catalog libraries (like Web Part Catalog), and the user must have the permission to add items to the list. For example, if there are no document libraries the user is allowed to add items to, we have the same issue with the CEWP.

Re-creating the deleted document library solved the issue for the user.



Recovering Passwords for SharePoint Managed Accounts

$
0
0

We have a SharePoint 2013 farm on a Windows 2008 R2 server. Recently we found this error in the Windows event logs in relation with the web application pool account:

Event ID 1511 – Windows cannot find the local profile and is logging you on with a temporary profile. Changes you make to this profile will be lost when you log off.

We tried to solve the issue based on the information we found in this post, but at the step below we faced the problem, that the password stored for the web application pool account (in this case we assumed domain\wa_pool_account) in our password repository does not work any more.

runas /u:domain\wa_pool_account /profile cmd

The web application pool account is registered as a managed account in SharePoint, at the original password has been already automatically changed by the system.

We could reset the password for the managed account as described in this article, but before changing the password I wanted to be sure there is no way to recover the current password from the system. I found a blog post and the related PowerShell code in TechNet Gallery, but I found the method described there (creating a new web application, and using an external tool, appcmd.exe) a bit overkill.

Instead of this I came up with an alternative solution that query the password directly from the SPManagedAccount object, via its private m_Password field (of type SPEncryptedString) that we can access by using Reflection. The public SecureStringValue property of the SPEncryptedString class returns an instance of the System.Security.SecureString class, and as illustrated here, we can “decode” its value to a simple string via Marshaling.

Using this approach, recovering the managed account password is so simple:

$ma = Get-SPManagedAccount domain\wa_pool_account
$maType = $ma.GetType()

$bindingFlags = [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Instance

$m_Password = $maType.GetField("m_Password", $bindingFlags)
$pwdEnc = $m_Password.GetValue($ma)

$ssv = $pwdEnc.SecureStringValue
$ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($ssv)
[System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)


Delete a SharePoint List Template Programmatically

$
0
0

Last week I had to “reproduce” a custom list having a rather complex structure from a root web site into a sub web site. I mean, I had to create another list having the same fields and views like the original one, but located in another web site.

I fulfilled the requirements via a PowerShell script by creating a temporary list template (without including content), creating a list based on the template, and finally deleting the list template (as the title of this post promises), since we didn’t want to allow others to create the same list in the webs they have write access to.

Note: There are several alternative ways to copy a list from one site to another, for example via the SharePoint Content Deployment and the Migration API (see samples here, here and here), or via the Export-SPWeb and Import-SPWeb PowerShell Cmdlets. as illustrated in this example. Of course, each of these solutions has its own pros and cons, and it really depends on the exact requirements (do you need to copy the content, or just the structure; do you need to copy security or not; do you need to copy the versions or not, etc.) which way we may or should choose. The comparison of these methods is beyond the scope of the current post, but it is useful to know these alternatives.

But back to the deletion of the list template. The solutions I found on the web after a quick search were IMHO a bit complicated as it should be (see this and this one for example), as they iterate through the list items in the List Template Gallery list to find the file / list item that corresponds to our template.

Based on my experience, we can find the template directly via the indexer of the SPListTemplateCollection by name. It results in solution that is so simple:

$listTemplates = $web.Site.GetCustomListTemplates($web)
$listTemplate = $listTemplates[$listName]
$listTemplateFile = $web.GetFile("_catalogs/lt/" + $listTemplate.InternalName)
$listTemplateFile.Delete()

If you need the full code we used to copy the list, it is here as well:

$url = "http://YourSharePointSite&quot;

$targetWebUrl = "/SubSite"

$listName = "CustomListName"

$site = Get-SPSite $url
$sourceWeb = $site.RootWeb
$targetWeb = Get-SPWeb ($url + $targetWebUrl)

$list = $sourceWeb.Lists[$listName]
# create a temporary list template
# we copy the list without data, so the last param is 0
$list.SaveAsTemplate($listName, $listName, $listName, 0)

$listTemplates = $site.GetCustomListTemplates($sourceWeb)
$listTemplate = $listTemplates[$listName]
# create the list in the target web
# get the list description from the source list
$targetWeb.Lists.Add($listName, $list.Description, $listTemplate)

# delete the temporary list template, we don’t need it anymore
$listTemplateFile = $sourceWeb.GetFile("_catalogs/lt/" + $listTemplate.InternalName)
$listTemplateFile.Delete()


PowerShell equivalents of Stsadm sync operations

$
0
0

Recently we had an issue with the synchronization of user profile data to the user list of a SharePoint site collection. Although all of the user e-mail addresses were synchronized from the Active Directory to the user profile service (UPS), in the case of a few, seemingly random users this piece of information remained empty in the SharePoint user list.

As written in the referenced post above, there are two timer jobs that should keep user information in sync between UPS and the user list.

  • User Profile to SharePoint Full Synchronization
  • User Profile to SharePoint Quick Synchronization

We did not find any problems with any of these timer jobs. After researching the web for a solution I found these post that suggests using the sync operation of the stsadm command to resolve UPS to SharePoint synchronization issues.

As I wrote here last year, one of the most important drawbacks of stsadm (beside of not being trendy any more), that you should have local administrator rights on the server where you run this commands, otherwise you receive an “Access denied.” error message. The reason, that for most of the stsadm operations, the following security check is performed in the entry method (public static int Main) of the Microsoft.SharePoint.StsAdmin.SPStsAdmin class:

if (!SPAdministrationServiceUtilities.IsCurrentUserMachineAdmin())
{
  Console.WriteLine(SPResource.GetString("AccessDenied", new object[0]));
  Console.WriteLine();
  return -2147024891;
}

In our case we have no local admin permissions on the server, so I decided to use Reflector to see, what the sync operation of ststadm exactly does, and rewrite its functionality using PowerShell.

I found that processing both of the listolddatabases and deleteolddatabases parameters we need are implemented in the ProcessStaleDataParams method of the Microsoft.Office.Server.CommandLine.SyncCommands class (Microsoft.Office.Server.UserProfiles assembly)

If the value of the listolddatabases parameter is not null (it is an integer, let’s say X), the command iterates through all of the content databases of all of the web applications in the farm, and displays the values of the Id and the LastProfileSyncTime properties of the content database if the difference (in days) between now (UTC value) and the value of the LastProfileSyncTime property is greater than or equals to X.

Without any kind of filtering, we can rewrite this in PowerShell as:

Get-SPContentDatabase | Select-Object -Property ID, LastProfileSyncTime

If you wish, you can extend it easily to filter, for example on the value of the LastProfileSyncTime property.

If the value of the deleteolddatabases parameter is not null (it is an integer, let’s say X), the command (similarly to the listolddatabases parameter described above) iterates through all of the content databases of all of the web applications in the farm, and if the difference (in days) between now (UTC value) and the value of the LastProfileSyncTime property is greater than or equals to X, it invokes the static ClearSyncDataForContentDatabase method of the WSSProfileSynch class (Microsoft.Office.Server.UserProfiles namespace and Microsoft.Office.Server.UserProfiles assembly), passing the content database (whose LastProfileSyncTime property is greater than or equals to X) and the UPS proxy assigned to the web application that belongs to the content database.

The only problem with this ClearSyncDataForContentDatabase method (having two parameters) is that it is declared an internal. Of course, we could invoke it using Reflection, however it is not necessary, since there is an other, public overload of the same ClearSyncDataForContentDatabase method, having a single, content database parameter, that determines the UPS proxy itself instead of expecting it as a parameter. So we are going with this public method, and assume, you already know, on which database you would like to reset the LastProfileSyncTime property.

$cdb = Get-SPContentDatabase e0ab0f2f-8f31-4e0f-82f4-7f7c48912d07 # ID the content DB
# or if you already know the name of the content DB you could use the name as well
# $cdb = Get-SPContentDatabase NameOfYourContentDB
[Microsoft.Office.Server.UserProfiles.WSSProfileSynch]::ClearSyncDataForContentDatabase($cdb)

Although using this PowerShell-based approach we were able to reset the LastProfileSyncTime property, it has not solved our original problem, but it is already an other story. Despite of it, I hope the PowerShell samples above may help others to override the security limitation of the stsadm command in the case of the sync operation.


Display Closed Risks Using Strikethrough Text on Project Server

$
0
0

As you probably have already seen, the name of the closed tasks (the ones having 100% completeness) is displayed using a strikethrough text in the All Tasks view of a task list in SharePoint. For example, from the tasks below, only Task 2 is 100 % complete.

image

Our users would like to have the same functionality in the Risks list on Project Server project sites, but out of the box, the title of the closed risks (ones having Status = "(3) Closed") is displayed without strikethrough, using the same formatting as any other risks:

image

Note: I assume you are familiar with client-side rendering. If not, and would like to understand how our solution works, I suggest you to read about it first, for example in the excellent post of Chris O’Brien.

After a short research, I found that this functionality of the Task lists is implemented in 15\TEMPLATE\LAYOUTS\hierarchytaskslist.debug.js. See the CompletedTitleTemplate template in that .js file.

Based on that template it was easy to implement the required functionality:

  1. (function () {
  2.     if (typeof window.CompletedRiskTitleTemplate == "object") {
  3.         return;
  4.     }
  5.     window.CompletedRiskTitleTemplate = {
  6.         RenderTitleField: function (inCtx, field, listItem, listSchema) {
  7.             var titleHtml = ComputedFieldWorker[field.Name](inCtx, field, listItem, listSchema);
  8.  
  9.             var result = (listItem["Status"] == "(3) Closed") ?
  10.                  '<span style="text-decoration: line-through">' + titleHtml + '</span>' :
  11.                  titleHtml;
  12.  
  13.             return result;
  14.         }
  15.     };
  16.     function _registerCompletedRiskTitleTemplate() {
  17.         var TitleFieldContext = {
  18.             Templates: {
  19.                 Fields: {
  20.                     'LinkTitle': {
  21.                         'View': window.CompletedRiskTitleTemplate.RenderTitleField
  22.                     }
  23.                 },
  24.                 ListTemplateType: 1101
  25.             }
  26.         };
  27.  
  28.         SPClientTemplates.TemplateManager.RegisterTemplateOverrides(TitleFieldContext);
  29.     }
  30.     ExecuteOrDelayUntilScriptLoaded(_registerCompletedRiskTitleTemplate, 'clienttemplates.js');
  31. })();

Note, that in this case we are using ListTemplateType 1101 for the Risks list instead of the value 171 for the original Task list type (Tasks with Timeline and Hierarchy to be exact). We get this list template value using the BaseTemplate property of our Risks list.

To ensure that the script is loaded on all views that include the Title field, we should set the JSLink property of the field with InternalName LinkTitle”.

Assuming you deployed your .js file to a path under the layout folder as /YourHive/js/strikeThroughClosedRisks.js, you can register your script using the following PowerShell code:

$web = Get-SPWeb http://YourProjServer/PWA/Proj1
$list = $web.Lists["Risks"]

$field = $list.Fields.GetFieldByInternalName("LinkTitle")
$field.JSLink = "~sitecollectionlayouts/YourHive/js/strikeThroughClosedRisks.js"
$field.Update()

Of course, this script affects only the web site of the project Proj1. If you would like to deploy it to all of your projects, you should iterate through the project web sites, but even better, you can prepare a project web site template based on this PWS in advance as described in my post last year, and use this template for your projects.

After successfully deploying our script, the text of the Title field of the closed risk is display using a strikethrough:

image


Highlighting Closed Risks by Setting Their Color

$
0
0

Just after I implemented a solution to display closed risks using strikethrough text, the users suddenly had a new idea. They meant it would be even better, if the whole row, I mean the text of each fields would be displayed grayed out for the closed risks in the list view.

First I thought it should be rather easy, as in the case we should set the background color of the row depending of a field value as described here and here, but I was wrong. So what’s the difference between setting the background color vs. setting the text color, what makes it to be a bit more complicated the latter one?

The problem is, that the cells of the table (the TD HTML elements) that build up the list view already have typically a color defined in the CSS classes they are decorated with (like ms-vb2, ms-vb-title for the Title field, ms-vb-user for the field type Person or Group, etc.). And since the color defined in the style of the elements itself (in this case TD) has a priority, we cannot override it simply by defining a style in the parent (in this case TR) element. In the case of the background color samples referenced above, the background color defined on the row level (TR) was simply inherited to the cell level (TD), as it was not explicitly defined on the TD level.

The following screenshot illustrates the list view before the customizations:

image

How to solve the situation? We could still define the CSS rules necessary to override the default colors for the selected rows. Let’s see how to achieve that!

In this case I utilized jQuery to simplify DOM search and manipulation.

I decorated the rows representing closed risk items with a custom class closedRiskRow:

if (rows[i]["Status"] == "(3) Closed") {
    var rowId = GenerateIIDForListItem(ctx, rows[i]);
    jQuery(jq(rowId)).addClass("closedRiskRow");
}

In the above code I use the jq helper function (see description here), since the Ids of the TR elements contain commas, that is interpreted as CSS notation:

function jq(myid) {
    return "#" + myid.replace(/(:|\.|\[|\]|,)/g, \\$1);
}

I injected the styles dynamically on document load:

jQuery(document).ready(function () {
    jQuery("<style>" + closedRiskRowStyle + "</style>").appendTo("head");

});

The styles are defined using the MultiString helper function, borrowed from Muawiyah Shannak, see this example.

var MultiString = function (f) {
    return f.toString().split(‘\n’).slice(1, -1).join(‘\n’);
}

var closedRiskRowStyle = MultiString(function () {/**
.closedRiskRow > td {
    color: lightgray; 
}

.closedRiskRow > td a.ms-subtleLink {
    color: lightgray; 
}

.closedRiskRow > td a.ms-listlink {
    color: lightgray;
    text-decoration: line-through;  
}
**/
});

The first CSS rule above formats the standard cells, the second one the field type Person or Group, in this case Assigned To field, and the last one is responsible for the Title field, including the strikethrough formatting we already had in the last post.

The full code is included here for your convenience:

  1. var MultiString = function (f) {
  2.     return f.toString().split('\n').slice(1, -1).join('\n');
  3. }
  4.  
  5. var closedRiskRowStyle = MultiString(function () {/**
  6. .closedRiskRow > td {
  7.     color: lightgray;  
  8. }
  9.  
  10. .closedRiskRow > td a.ms-subtleLink {
  11.     color: lightgray;  
  12. }
  13.  
  14. .closedRiskRow > td a.ms-listlink {
  15.     color: lightgray;
  16.     text-decoration: line-through;   
  17. }
  18. **/
  19. });
  20.  
  21. function jq(myid) {
  22.     return "#" + myid.replace(/(:|\.|\[|\]|,)/g, "\\$1");
  23. }
  24.  
  25. (function () {
  26.     if (typeof window.CompletedRiskTemplate == "object") {
  27.         return;
  28.     }
  29.  
  30.     window.CompletedRiskTemplate = {
  31.         HighLightRow: function (inCtx) {
  32.             jQuery(document).ready(function () {
  33.                 jQuery("<style>" + closedRiskRowStyle + "</style>").appendTo("head");
  34.  
  35.                 var rows = inCtx.ListData.Row;
  36.                 if (rows) {
  37.                     for (var i = 0; i < rows.length; i++) {
  38.                         if (rows[i]["Status"] == "(3) Closed") {
  39.                             var rowId = GenerateIIDForListItem(ctx, rows[i]);
  40.                             jQuery(jq(rowId)).addClass("closedRiskRow");
  41.                         }
  42.                     }
  43.                 }
  44.             });
  45.         }
  46.     };
  47.     function _registerCompletedRiskHighLightRowTemplate() {
  48.         var HighLightRowContext = {
  49.             "OnPostRender": window.CompletedRiskTemplate.HighLightRow
  50.         };
  51.  
  52.         SPClientTemplates.TemplateManager.RegisterTemplateOverrides(HighLightRowContext);
  53.     }
  54.     ExecuteOrDelayUntilScriptLoaded(_registerCompletedRiskHighLightRowTemplate, 'clienttemplates.js');
  55. })();

I deployed the .js file to a path under the layout folder as /YourHive/js/highlightClosedRisks.js, and the jquery-1.9.1.min.js to the same folder, then registered the scripts using the following PowerShell code:

$web = Get-SPWeb http://YourProjServer/PWA/Proj1
$list = $web.Lists["Risks"]

$field = $list.Fields.GetFieldByInternalName("LinkTitle")
$field.JSLink = "~sitecollectionlayouts/YourHive/js/jquery-1.9.1.min.js|~sitecollectionlayouts/YourHive/js/highlightClosedRisks.js"
$field.Update()

After the customizations the closed risk is displayed in gray, the title of the risk is with a strikethrough:

image


How to Set a SharePoint Document Library to Read-Only Mode

$
0
0

Recently we had to archive a few document libraries on our SharePoint farm. The document libraries may have they own permission setting on the library level, and / or at the folder, and / or the document levels either. We do not have any custom permission level defined in our sites.

We defined “archive” as this: all users that have read / write permission to an item, should keep the access on the item, but it has to be restricted to read permission in the future. The users having permission to the documents should be able to download them, and work on them locally (if they wish) but are not allowed to save them back to the library.

I wrote a PowerShell script that processes the permissions, and replaces write permissions with read permissions on demand.

Note: the solution implemented in this post is a one-way street. It’s not a read-only switch you can turn on or off as you can do for example in the case of a site collection (like Set-SPSite http://YourSharePointSite -LockState ReadOnly). You won’t be able to reproduce the original permissions once you run the script.

  1. $url = "http://YourSharePointSite/SubSite&quot;
  2. $docLibName = "Documents"
  3.  
  4. $web = Get-SPWeb $url
  5. $docLib = $web.Lists[$docLibName]
  6.  
  7. $limitedAccess = $web.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Guest)
  8. $readAccess = $web.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Reader)
  9.  
  10. $allowedAccess = $limitedAccess.Name, $readAccess.Name
  11.  
  12. function Replace-Permissions($securable)  
  13. {
  14.   $securable.RoleAssignments | ? { $_.RoleDefinitionBindings | ? { $allowedAccess -notcontains $_.Name }} | % {
  15.     $_.RoleDefinitionBindings.RemoveAll()
  16.     $_.RoleDefinitionBindings.Add($readAccess)
  17.     $_.Update()
  18.   }
  19. }
  20.  
  21. # if the doc. lib. inherits the permissions and if there is any role assignments that contains an access level beyond read only
  22. # we should break the inheritance first, to be able to change the permissions on the library level
  23. If (($doclib.HasUniqueRoleAssignments) -and (($docLib.RoleAssignments | ? { $_.RoleDefinitionBindings | ? { $allowedAccess -notcontains $_.Name }}).Count -gt 0))
  24. {
  25.   $doclib.BreakRoleInheritance($true);
  26. }
  27.  
  28. # set permissions on the doc. lib level
  29. Replace-Permissions($docLib)
  30. # set permissions on all folders having its own role assignment
  31. $doclib.Folders | ? { $_.HasUniqueRoleAssignments } | % { Replace-Permissions $_ }
  32. # set permissions on all documents having its own role assignment
  33. $doclib.Items | ? { $_.HasUniqueRoleAssignments } | % { Replace-Permissions $_ }

In the Replace-Permissions function I replace any permissions  on a securable object other than Read or Limited Access (Guest) permissions with Read permissions.

Note: If you remove a Limited Access permission using the web UI, or the corresponding role assignment from code, you will loose the permissions set explicitly for that user anywhere in the hierarchy below that level. As described here, you can call the RemoveAll method on the RoleDefinitionBindings without such side effects. Then we can add the read permissions in place of the removed permissions.

I invoke the Replace-Permissions function once for the document library, then once for each folder and document having its own unique role assignments.


List View Threshold Causing Project Server Synchronization Failures

$
0
0

Yesterday we found a lot of errors in the Project Server queue job history (PWA Settings / Manage Queue Jobs). The Job State was Failed But Not Blocking Correlation, the Job Type was of type Reporting (Project Sync).

image

To understand the reason, you should know that Project Server synchronizes changes in several lists from the project web sites (PWS) to its reporting database. This kind of synchronization is performed by the Project Server Queue Service (Microsoft.Office.Project.Server.Queuing.exe).

By clicking on the Click to view the error details link we became more information about the background of the problem.

Queue Job Error Details
General
Reporting Wss list sync failed
ReportingWssSyncListFailed (24018) – 101. Details: id=’24018′ name=’ReportingWssSyncListFailed’ uid=’5b39b9cd-bad4-e511-8c5c-005056b45654′ SPListType=’0e2d89a0-e57a-e411-9568-005056b45654′ Error=’101′.
Reporting message processor failed
ReportingWSSSyncMessageFailed (24016) – RDS failed while trying to sync one or more SP lists. The RDS queue message will be retried.. Details: id=’24016′ name=’ReportingWSSSyncMessageFailed’ uid=’6239b9cd-bad4-e511-8c5c-005056b45654′ QueueMessageBody=’ProjectUID=’0e2d89a0-e57a-e411-9568-005056b45654′. ForceFullSync=’False’. SynchronizationType=’Documents” Error=’RDS failed while trying to sync one or more SP lists. The RDS queue message will be retried.’.
Queue
GeneralQueueJobFailed (26000) – ReportingWSSSync.WSSSyncMessageEx. Details: id=’26000′ name=’GeneralQueueJobFailed’ uid=’ade7bdd3-bad4-e511-8c5c-005056b45654′ JobUID=’485a0869-b8d4-e511-8c5c-005056b45654′ ComputerName=’d30c825e-996d-4a92-a60d-1d66f5340b1b’ GroupType=’ReportingWSSSync’ MessageType=’WSSSyncMessageEx’ MessageId=’1′ Stage=” CorrelationUID=’0fca5f9d-75e8-507f-40b1-bf88f739c79d’. For more details, check the ULS logs on machine d30c825e-996d-4a92-a60d-1d66f5340b1b for entries with JobUID 485a0869-b8d4-e511-8c5c-005056b45654.

The value 101 in the Error property and the value Documents in the SynchronizationType suggest that it is something wrong with the synchronization of the document library list (101 is the ID of the document library list template).

The relevant part from the ULS logs:

02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Server                 General                           a2tj    Medium      RowLimit unset…using default RowLimit for query: <Where><Geq><FieldRef Name=’Modified’/><Value Type=’DateTime’ IncludeTimeValue=’TRUE’ StorageTZ=’TRUE’>2016-02-16T14:29:19</Value></Geq></Where>    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x125EC    Project Server                    Project Server Database           ah91z    Medium      Successfully got the connection string (database name=[YourProjectServerDB], id=ae38d4f0-3d16-430b-8cec-c76290a640df, type=Consolidated). Requested access level=ReadWrite: Data Source=YourProjectServerServiceApp;Initial Catalog=YourProjectServerDB;Integrated Security=True;Enlist=False;Pooling=True;Min Pool Size=0;Max Pool Size=100;Connect Timeout=15    
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             Health                            46ri    High        Throttled:Big list slow query. List item query elapsed time: 0 milliseconds, Additional data (if available): Query HRESULT: 80070024 List internal name, flags, and URL: {7903E344-47BC-42B5-A1BF-14E3E58D817F}, flags=0x0080000000001008, URL="
http://YourProjectServer/PWA/Proj1&quot; Current User: 1073741823 Query XML: "<Query><Where><Geq><FieldRef Name="Modified"/><Value Type="DateTime" IncludeTimeValue="TRUE" StorageTZ="TRUE">2016-02-16T14:29:19</Value></Geq></Where></Query>" SQL Query: "N/A"     0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           xxpm    High        Unable to execute query: Error 0x80070024    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           8kh7    High        The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator.<nativehr>0x80070024</nativehr><nativestack></nativestack>    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           aix9j    High        SPRequest.GetListItemDataWithCallback2: UserPrincipalName=i:0).w|s-1-5-21-3634847118-1559816030-2180994487-3302, AppPrincipalName= ,pSqlClient=<null> ,bstrUrl=
http://YourProjectServer/PWA/Proj1 ,bstrListName={7903E344-47BC-42B5-A1BF-14E3E58D817F} ,bstrViewName=<null> ,bstrViewXml=<View Scope=’RecursiveAll’><Query><Where><Geq><FieldRef Name=’Modified’/><Value Type=’DateTime’ IncludeTimeValue=’TRUE’ StorageTZ=’TRUE’>2016-02-16T14:29:19</Value></Geq></Where></Query><ViewFields><FieldRef Name=’ID’/><FieldRef Name=’UniqueId’/><FieldRef Name=’FileRef’/><FieldRef Name=’FSObjType’/> ,fSafeArrayFlags=SAFEARRAYFLAG_NONE    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      System.Runtime.InteropServices.COMException: The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator.<nativehr>0x80070024</nativehr><nativestack></nativestack>, StackTrace:    at Microsoft.SharePoint.SPListItemCollection.EnsureListItemsData()     at Microsoft.SharePoint.SPListItemCollection.get_Count()     at Microsoft.Office.Server.Utilities.ContentIterator.ProcessListItems(SPList list, SPQuery query, ItemsProcessor itemsProcessor, ItemsProcessorErrorCallout errorCallout)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.WSSDataTransfer.BuildIncrementalTransfer(Guid projectUid, SPList splist, SPChangeToken previousChangeToken, WSSListMetadata wssListMetadata, Int32& syncItemsCount)     at Microsoft.Office.Project.Ser…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      …ver.BusinessLayer.ReportingLayer.WSSDataTransfer.BuildTransferData(Guid projectUid, SPList splist, WSSListMetadata wssListMetadata, Boolean forceFullSync, Boolean& syncWasIncremental, Int32& syncItemsCount)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.RdsWssSyncProcessor.TransferWSSListData(Guid projectUid, SPList splist, WSSListMetadata wssListMetadata, DateTime dtStartSyncTime, Boolean forceFullSync, SPChangeToken currentChangeToken)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.RdsWssSyncProcessor.SyncWSSList(Guid projectUid, SPWeb spweb, WSSListMetadata wssListMetadata, Boolean forceFullSync)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.RdsWssSyncProcessor.SyncWSSData(Guid projectUid, SPWeb spweb, WSSTransferMetadata w…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      …ssTransferMetadata, Boolean forceFullSync)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.RdsWssSyncProcessor.runRDSTransformation(WSSSyncMessageEx wssSyncMessage)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.RdsWssSyncProcessor.HandleMessage(Message msg, Group messageGroup, JobTicket jobTicket, MessageContext mContext)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.ProcessMessageThread.ThreadEntry()     at Microsoft.Office.Project.Server.BusinessLayer.Queue.MessageProcessor.ProcessMessage(Message msg, MessageContext mContext, ProcessMessageThread processMessageThread)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.MessageProcessor.HandleMessages()     at Microsoft.Office.Project.Server.BusinessLayer.Queue.MessageProcessor…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      ….ThreadEntry()     at Microsoft.Office.Project.Server.BusinessLayer.Queue.MessageProcessor.<>c__DisplayClassb.<Execute>b__a()     at Microsoft.Office.Project.Server.Library.SafeCallbackManager.RunCallbackAction(Action action, Func`1 safePredicate)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.MessageProcessor.Execute(Group& nextGroupInCorrelation, JobTicket& nextGroupJobTicket)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.Receiver.ProcessMessageGroup(CancellationToken schedulerCancellationToken, Group messageGroup, JobTicket jobTicket, Group& nextGroupInCorrelation, JobTicket& nextGroupJobTicket)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.Receiver.ExecuteJobInternal(CancellationToken schedulerCancellationToken, Group& messageGroup)     at Microsof…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      …t.Office.Project.Server.BusinessLayer.Queue.Receiver.<>c__DisplayClass1d.<>c__DisplayClass21.<ExecuteJob>b__1b()     at Microsoft.Office.Project.Server.BusinessLayer.Queue.QueueUtils.RunActionWithIgnoreExceptionPredicate(Action action, Predicate`1 ignoreExceptionPredicate, Boolean& isIgnorableException)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.Receiver.<>c__DisplayClass1d.<ExecuteJob>b__1a()     at Microsoft.Office.Project.Server.Library.SafeCallbackManager.RunCallbackAction(Action action, Func`1 safePredicate)     at Microsoft.Office.Project.Server.BusinessLayer.Queue.Receiver.ExecuteJob(CancellationToken schedulerCancellationToken, Int32 siteJobCount)     at Microsoft.Office.Project.Server.Services.SiteQueue.<>c__DisplayClass2.<ExecuteJob>b__1()     at Microsoft.Office….    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      …Project.Server.Library.SafeCallbackManager.RunCallbackAction(Action action, Func`1 safePredicate)     at Microsoft.Office.Project.Server.Services.SiteQueue.ExecuteJob(CancellationToken ct, Int32 siteJobCount)     at Microsoft.Office.Project.Server.Services.QueueJobRunner.ExecuteSiteJob(Object data)     at System.Threading.Tasks.Task.Execute()     at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)     at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)     at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)     at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecut…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             General                           ai1wu    Medium      …ion)     at System.Threading.ThreadPoolWorkQueue.Dispatch()      0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    SharePoint Foundation             Health                            46ri    High        big list slow query, list:{7903E344-47BC-42B5-A1BF-14E3E58D817F}  viewname: viewxml: <View Scope=’RecursiveAll’><Query><Where><Geq><FieldRef Name=’Modified’/><Value Type=’DateTime’ IncludeTimeValue=’TRUE’ StorageTZ=’TRUE’>2016-02-16T14:29:19</Value></Geq></Where></Query><ViewFields><FieldRef Name=’ID’/><FieldRef Name=’UniqueId’/><FieldRef Name=’FileRef’/><FieldRef Name=’FSObjType’/><FieldRef Name=’Author’/><FieldRef Name=’Created’/><FieldRef Name=’Editor’/><FieldRef Name=’Modified’/><FieldRef Name=’Title’/></ViewFields><RowLimit Paged="TRUE">2000</RowLimit></View>    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    Project Server                    Reporting                         auos    Warning     Standard Information:PSI Entry Point:   Project User: PROJECTSERVER\system  Correlation Id: 495a0869-b8d4-e511-8c5c-005056b45654  PWA Site URL:
http://YourProjectServer/PWA/Proj1  SA Name: ProjectServerApplication  PSError: NoError (0) RDS: Problem transferring SharePoint data to the Project Server. Unable to prepare the incremental transfer data for the SharePoint list  ‘101’ associated with the project ‘0e2d89a0-e57a-e411-9568-005056b45654’. Error: The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator..  Full transfer will be used.    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19     Microsoft.Office.Project.Server (0x136D8)    0x16F88    Project Server                    Reporting                         auos    High        PWA:
http://YourProjectServer/PWA/Proj1, ServiceApp:ProjectServerApplication, User:PROJECTSERVER\system, PSI: ULS Event: WSSIncrementalTransferFailed, tag 1635086195 was associated with exception: Microsoft.SharePoint.SPQueryThrottledException: The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator. —> System.Runtime.InteropServices.COMException: The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator.<nativehr>0x80070024</nativehr><nativestack></nativestack>     at Microsoft.SharePoint.Library.SPRequestInternalClass.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeAr…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    Project Server                    Reporting                         auos    High        …rayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)     at Microsoft.SharePoint.Library.SPRequest.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeArrayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)     — End of inner exception stack trace —     at Microsoft.SharePoint.SPGlobal.HandleThrottleException(…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    Project Server                    Reporting                         auos    High        …COMException comEx)     at Microsoft.SharePoint.Library.SPRequest.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeArrayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)     at Microsoft.SharePoint.SPListItemCollection.EnsureListItemsData()     at Microsoft.SharePoint.SPListItemCollection.get_Count()     at Microsoft.Office.Server.Utilities.ContentIterator.ProcessListItems(SPList list, SPQuery query, ItemsProcessor itemsProcessor, ItemsProcessorErrorCallout errorCallout)     at Microsoft.Office.P…    0fca5f9d-75e8-507f-40b1-bf88f739c79d
02/16/2016 15:37:35.19*    Microsoft.Office.Project.Server (0x136D8)    0x16F88    Project Server                    Reporting                         auos    High        …roject.Server.BusinessLayer.ReportingLayer.WSSDataTransfer.BuildIncrementalTransfer(Guid projectUid, SPList splist, SPChangeToken previousChangeToken, WSSListMetadata wssListMetadata, Int32& syncItemsCount)     at Microsoft.Office.Project.Server.BusinessLayer.ReportingLayer.WSSDataTransfer.BuildTransferData(Guid projectUid, SPList splist, WSSListMetadata wssListMetadata, Boolean forceFullSync, Boolean& syncWasIncremental, Int32& syncItemsCount), LogLevelManager Warning-ulsID:0x61756F73 has no entities explicitly specified.    0fca5f9d-75e8-507f-40b1-bf88f739c79d

The most important message from the logs:

The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator

That means, the value set for the List View Threshold (configurable in Resource Throttling in Web Application Management site of the Central Administration, defaults to 5000) is less than the document count in the library. You should increase the value to re-enable synchronization, using PowerShell, for example:

$webApp = Get-SPWebApplication http://YourProjectServer
$webApp.MaxItemsPerThrottledOperation = 10000
$webApp.Update()

Or alternatively convince your users not to store so many files in the document library.



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

$
0
0

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.


Getting the Item Count of all Lists of all Sub-Sites via a Single Request from Client Code

$
0
0

Recently I had a task to get the item count of all lists of all Project Web Sites (PWS) of a Project Server instance from a client-side application. Note, that the PWSs are located directly under the Project Web Access (PWA) site, so there is no deeper site structure in this task to deal with, so I was pretty sure that it can be achieved in a single request. Although in my case the task was Project Server related, one can use the same method in the case of SharePoint Server as well, it is only important, that you should not have a multiple level site structure, for a deeper site structure this method simply does not work.

I show you both the REST (OData) and the managed client object model approach. Let’s start with the client OM sample:

  1. string siteUrl = "http://YourProjectServer/PWA&quot;;
  2. using (var clientContext = new ClientContext(siteUrl))
  3. {
  4.     var rootWeb = clientContext.Web;
  5.  
  6.     clientContext.Load(rootWeb, rw => rw.Webs.Include(w => w.Title, w => w.ServerRelativeUrl, w => w.Lists.Include(l => l.Title, l => l.ItemCount)));
  7.     clientContext.ExecuteQuery();
  8.  
  9.     foreach(var web in rootWeb.Webs)
  10.     {
  11.         if (web.Lists.Any())
  12.         {
  13.             Console.WriteLine("Lists of web '{0}' [{1}]", web.Title, web.ServerRelativeUrl);
  14.             foreach (var list in web.Lists)
  15.             {
  16.                 Console.WriteLine("'{0}' [Item count: {1}]", list.Title, list.ItemCount);
  17.             }
  18.         }
  19.     }
  20. }

The corresponding REST query can be submitted as a GET request sent to this URL:

http://YourProjectServer/PWA/_api/web/webs?$expand=Lists&$select=ServerRelativeUrl,Title,Lists/ItemCount,Lists/Title

If you need the item count only from a specific list (for example, the lists with title ‘Risks’) for all subsites, you can easily achieve that in the client OM sample by including a Where clause in the query:

clientContext.Load(rootWeb, rw => rw.Webs.Include(w => w.Title, w => w.ServerRelativeUrl, w => w.Lists.Include(l => l.Title, l => l.ItemCount).Where(l => l.Title == "Risks")));

The corresponding REST query would be:

http://YourProjectServer/PWA/_api/web/webs?$expand=Lists&$filter=Lists/Title eq ‘Risks’&$select=ServerRelativeUrl,Title,Lists/ItemCount,Lists/Title

However, when submitting this request I get a response with status HTTP 400 and the message: The field or property ‘Title’ does not exist.

I’m working on a solution and update this post as soon as I found one. Feel free to help me by sending it as a comment. Winking smile


Test On Demand PDF Conversion without Document Libraries via PowerShell

$
0
0

If you had to convert Word documents to PDFs using the Word Automation Services in SharePoint 2010, you had to store the files at least temporary in document libraries. That was true, even if you have created the documents dynamically, for example based on SharePoint list item data using the Open XML SDK and sent as response to a web request (as illustrated in this example). Typically you could solve this requirement using streams or byte arrays otherwise, but the ConversionJob class supported in SharePoint 2010 only working with files stored in document libraries via the AddFile method. A further limitation of that only asynchronous conversation was supported, if you needed a synchronous operation, you should have to find workaround, like this one.

In SharePoint 2013 we have the SyncConverter class, that – as its name already suggests – provides the synchronous conversion as well stream and byte array support via its Convert method.

Recently we are working on a document conversation solution again (similar to the SharePoint 2010 example I referred to above), but we did not want to deploy the SharePoint solution each time we change the logic in the Word document creation, but still wanted to have the ability to test:

  • If the WAS is able to convert the word document to PDF.
  • Does the output of the conversation fulfill our expectations.
  • How much time the conversion takes.

So we called the appropriate methods of our Word document creation assembly from PoweShell, and stored the result in a byte array. This step is specific to the current customer project and as such out of the scope of this post. Instead of this we simulate the step simply by reading the static content of a .docx file in the script below. Next we got a reference to the Word Automation Services Proxy, and converted the Word document from the byte array into another byte array, holding the content of the PDF conversion. In the original SharePoint solution this byte array would be sent back in the web server response to the client. In the script we simply save it into a file into the file system. If you run this script, don’t forget, that if the target file already exists, it is overwritten without any confirmation request!

  1. $url = "http://YourSharePointSite&quot;
  2. $inputFilePath = "C:\Data\input.docx"
  3. $outputFilePath = "C:\Data\output.pdf"
  4.  
  5. [byte[]]$inputContentBytes = Get-Content $inputFilePath -Encoding Byte
  6. [byte[]]$outputContentBytes = $null
  7.  
  8. # get the WAS proxy assigned to the site
  9. $site = Get-SPSite $url
  10. $serviceContext = [Microsoft.SharePoint.SPServiceContext]::GetContext($site)
  11. $wasAppProxy = $serviceContext.GetDefaultProxy([Microsoft.Office.Word.Server.Service.WordServiceApplicationProxy])
  12. # or if you have a single WAS proxy in your farm you can use this as well:
  13. #$wasAppProxy = Get-SPServiceApplicationProxy | ? { $_.TypeName -eq "Word Automation Services Proxy" }
  14.  
  15. [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Word.Server")
  16. $job = New-Object Microsoft.Office.Word.Server.Conversions.SyncConverter($wasAppProxy)
  17. $job.Settings.OutputFormat = "PDF"
  18. $job.Settings.UpdateFields = $true
  19. $job.UserToken = ($site.RootWeb).CurrentUser.UserToken
  20.  
  21. $jobInfo = $job.Convert($inputContentBytes, [ref] $outputContentBytes)
  22. [System.IO.File]::WriteAllBytes($outputFilePath, $outputContentBytes)
  23.  
  24. If ($jobInfo.Succeeded)
  25. {
  26.   $duration = ($jobInfo.CompleteTime $jobInfo.StartTime).TotalSeconds
  27.   Write-Host Job completed in $duration seconds
  28. }
  29. Else
  30. {
  31.   Write-Host Job completed with error $jobInfo.ErrorCode
  32. }

You should have the Word Automation Services infrastructure (I mean the service application and the related services) up and running. I was sure, that it is OK in our case, but wanted to keep the script simple, so I did not check these things in the script above. If you are not sure that your farm was set up correctly, you should have a look at this script and extend my version with the test steps if you wish.


Access Denied when Setting Group Properties as Group Owner

$
0
0

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.


How to get the value of a Project Server Enterprise Custom Field via the Project Server Managed Client Object Model

$
0
0

About two years ago I posted a code about how to set the value of a Project Server Enterprise Field via the managed client OM. Again and again I get the question how to get the value, once it is set already.

In the first case I assume, you already know the ID of your project and the internal name of the field you would like to query. In this case, you need only send a single request to the server, as shown in this code:

  1. var url = @"http://YourProjectServer/pwa&quot;;
  2. var projectContext = new ProjectContext(url);
  3.  
  4. var projId = new Guid("98138ffd-d0fa-e311-83c6-005056b45654");
  5. var cfInternalName = "Custom_b278fdf35d16e4119568005056b45654";
  6.  
  7. var proj = projectContext.Projects.GetByGuid(projId);
  8. projectContext.Load(proj, p => p[cfInternalName], p => p.Name);
  9.  
  10. projectContext.ExecuteQuery();
  11.  
  12. Console.WriteLine(proj.Name, proj.FieldValues[cfInternalName]);

If either the ID of your project or the internal name of the field is unknown, you need an extra round-trip before the query shown in the previous code to determine their value. In the code below I assume you know none of these values:

  1. var url = @"http://YourProjectServer/pwa&quot;;
  2. var projectContext = new ProjectContext(url);
  3. var projName = "Your Project Name";
  4. var fieldName = "NameOfTheField";
  5.  
  6. projectContext.Load(projectContext.Projects, ps => ps.Include(p => p.Id, p => p.Name));
  7. projectContext.Load(projectContext.CustomFields, cfs => cfs.Include(cf => cf.InternalName, cf => cf.Name));
  8. projectContext.ExecuteQuery();
  9.  
  10. var projId = projectContext.Projects.First(p => p.Name == projName).Id;
  11. var cfInternalName = projectContext.CustomFields.First(cf => cf.Name == fieldName).InternalName;
  12.  
  13. var proj = projectContext.Projects.GetByGuid(projId);
  14. projectContext.Load(proj, p => p[cfInternalName], p => p.Name);
  15.  
  16. projectContext.ExecuteQuery();
  17.  
  18. Console.WriteLine(proj.Name, proj.FieldValues[cfInternalName]);

I hope it helps to read the custom field values, for example the values set by the code in the former post.


Viewing all 206 articles
Browse latest View live