Tuesday, 28 February 2017

Bulk Undeclare Records the CSOM Way

Microsoft's latest release of the New SharePoint CSOM version released for SharePoint Online includes many requested new capabilities that developers have been begging for. Key updates include the ability to bulk edit User Profiles, access the Recycle Bin, reading content from older documents versions - and much more.

But for me at least, the most exciting inclusion was the Records class. FINALLY!!!

The Records Class

This includes three simple methods which should be self-explanatory.

  • IsRecord: Checks whether the specified item is a record.
  • DeclareItemAsRecord: Declares the specified item as a record.
  • UndeclareItemAsRecord Undeclares the specified item.

The View

In order to get an idea of how many records I was dealing with, I first created a view on the target library which included the Declared Record and Item is a Record system fields, and showed totals.

I also enabled Metadata navigation on the library and specified the Declared Record field as a key filter. This is pretty much mandatory for large libraries in order to overcome potential List View Threshold errors. It also has the added benefit of removing folders once the filter is applied.

Interestingly, Item is a Record always displayed No, despite the padlocked icon and presence of a Declared Record date. Seems this is tied to the IsRecord method problem explained later.

The Console App

Firing up VS, I created a new console app project and added it to my ever-growing solution of "Handy apps that every SP admin needs but never knew existed". ;)

After defining a suitable CAML query with enough indexed columns (please tell me there's a better way to query HUGE lists!), I then decided to perform a test against each item in the set to satisfy myself that it actually was a record. And this is where I hit my first snag. The IsRecord method returned false for every item.

Hmmm....I'm wondering if there's any correlation between the returned Boolean from this method and the value displayed in the Item is a Record field in my view. In any case, I had my CAML query and view to confirm the items were indeed records. So I just removed the check and went ahead with undeclaring them.

I'm very happy to say that the exercise was a success and saved many days of manual work.

Friday, 20 November 2015

SharePoint Designer - Test if List Item Exists

One of the most important aspects of programming and workflows is error checking, which is why SharePoint's lack of transparent support for "checks" can be so damn frustrating. Being able to compare a known value to a field in a list item is great. But if that check fails, the workflow will error out.

An Error Occurred...

Try it yourself by attempting to update an item with an invalid ID (or some other comparison that doesn't exist).

SharePoint will not allow you to directly check for the desired value using an if/else branch - and neither will it provide any more information beyond informing you that "An Error occurred".

So What's The Solution?

Aside from using a third party product which will perform such simple comparisons, or building a custom workflow action (ugh!), there is only one sure-fire method I have found.

When you try to test for a specific field value that doesn't exist, you will receive an error. However, if you test for the existence of a specific field value within a compound string, the comparison does not produce an error and the workflow continues on its merry way. This same trick will also work when checking for the existence of a List.

To achieve this:

  1. Create a new Local Variable called, say, TargetItemCheck.
  2. Add a Set Workflow Variable action.
  3. Set the value for the variable by clicking the ellipsis (…) to open the string builder window.
  4. Add some meaningful text such as "Target Item Lookup: " and then add a lookup to the field in the target item.
  5. You can now perform a safe check for the item by adding an If any value equals any value condition and checking if our variable contains the value we want to check for.

Voila! Another workaround found for a common problem.

Thursday, 18 December 2014

SharePoint Designer 2013 - Common Errors with Simple Solutions

There are any number of generic and frustrating errors that one may encounter when working with SPD and workflows. The most infuriating are those that prevent publishing the workflow and are not caused by the operator. This post lists a few of my favourites - and the workarounds I've applied to overcome them. I expect it to grow over time. ;-)

"Errors were found when compiling the workflow... Unexpected error on server associating the workflow"

This is without doubt the most common and generic error facing all workflow designers. It often occurs after trying to publish an error-free workflow that then throws an - (X, X) Activity 'IDXX' validation failed: ... - type error.

While there are many proposed solutions to this -- IIS resets, app pool recycling, a read-only text file in the app pool's temp directory, even web.config modifications -- none of them reliably worked for me.

The simplest and most effective solution has been to simply delete the contents of the ProxyAssemblyCache directory for the user on the machine you're running SPD from.

This directory can be found in the following location:

C:\Users\USER\AppData\Roaming\Microsoft\SharePoint Designer\ProxyAssemblyCache

Property 'DurationUnit' has invalid value

This error occurs periodically when attempting to publish a workflow with an approval action. I'm not sure why it occurs or why it picks the DurationUnit variable but this is the only one I've ever seen affected.

The complete error is:

(0, 0) Activity 'ID4' validation failed: Property 'DurationUnit' has invalid value. Field type 'System.String' does not match with the expected type 'Microsoft.Office.Workflow.Actions.DurationUnit'.)

Like others who've encountered this I was perplexed when I couldn't find 'DurationUnit' in the workflow parameters or variables. That's because it's sneakily hidden (along with many others) in the Properties for the approval action, which you can get to by right-clicking the action.

To resolve the error, just click the ƒx symbol next to the parameter in question, ensure that field from source is Parameter: Duration Units and click OK.

Approval Workflow Error on Rejection

This occurs with the OOTB approval workflows and the Start Approval Process action (which uses the same core task processes. When a user clicks Reject on the task, the workflow errors out without any real indication as to why. If you check the ULS logs (and your diagnostic log settings are set appropriately) you may see something like this:

Workflow Infrastructure 98d4 Unexpected System.InvalidOperationException: CompositeActivity cannot transition to 'Closed' status when there are active child context still exist for child activity.

Ignoring the horrific grammar, the key clue here is "active child context still exist for child activity". This led me to believe the task process was not properly exiting for whatever reason. Some claim it's a long-standing bug in the Microsoft.Office.Workflow.Actions assembly. Reverting this to an older version was not something I was going to entertain.

After scrutinising every last aspect of the task processes and nearly giving up, I finally found a solution. Once again the fix turned out to be a simple one.

  • Click the approval action to open the task process screen.
  • Click the Change the behavior of the overall task process link in the Customisation section.
  • Scroll your way down to the When The Task Process Completes event.
  • Within the "Else" clause you should see an End Task Process action. Remove it.

Please feel free to comment on any other errors you come across often and I'll do my best to find solutions.

Wednesday, 10 December 2014

People Picker field not displaying in New/Edit forms

I recently ran into a perplexing problem with a list on which I had created a new People & Groups field, restricted it to a particular group, added it to the InfoPath form and republished.

Everything worked as expected during my tests but UAT picked up something interesting. The field was not being rendered in New and Edit forms.

After cursing InfoPath and SharePoint generally for an hour or so, it came to light that only people in the Site Owners could modify the value for the field.

I decided to remove the group picker restriction and, sure enough, the field was suddenly available. Hmmmmm....but this is not the solution I was after so I reapplied the restriction to the group I wanted and then changed the groups settings so that everyone could see the members. That did it!

TO be honest, SharePoint and InfoPath were actually doing the right thing by applying the group's security settings to the field.

Now that I'm aware of this particular quirk I can see some valuable uses for it in future. If only group level permissions could be applied for every field we'd really be cooking with gas. :D

Monday, 28 July 2014

SharePoint Virtual Directory Mapping Mayhem

I had updated a 2010 VS solution which contained references to a custom CSS file and several image resources. Upon deploying it to my 2013 farm I was surprised to see that all these links were broken.

All my references used the traditional tilde followed by "/_layouts/" and then the end path - i.e. ˜\_layouts\SolutionFolder\myfile.ext - which looked just fine and matched the same format I've always used. In addition, the deployment location for Layouts and Images directory within VS was using the {SharePointRoot} token and correctly placing everything within its correct location on the server.

So I changed the URL in the open browser window to point to a few OOTB application pages and images and noticed hit and miss results with these too! What was going on??

I carefully checked and compared file permissions, toyed with different file formats and extensions... There was no pattern to this madness!

I dug a little deeper and noticed that the files which were successfully loading also existed in the 14 directory. Hmmm... This led me to take a look at the virtual directory mappings in IIS and this is what I found:

The _layouts virtual directory actually maps to SharePoint 2010’s 14 hive!

One can only assume that this was done for backwards compatibility with 2010 solutions, which can also be deployed to the same farm.

Lesson learnt. Always include the hive version in the path when referencing server-side resources.

Friday, 2 May 2014

Get Web Part Usage and List Instances [A PowerShell Journey continued]

In my last post I dealt with listing all the features associated with deployed solutions to the farm, and then showing where they were activated. Armed with that information and what I found in the manifest for each feature, I now have what I need to attempt locating all the pesky instances of these custom web parts and lists.

You may be wondering why I'm going to all this trouble. Well, in the absence of source code, these sites will not upgrade. So my intention is to identify all the customisations so that I can either remove (or rebuild) them safely, before deactivating the features they belong to. And also, I just LOVE tinkering with PowerShell. :D

Get WebPart Usage

I wrote this as a function to make it more re-usable. Pass in the webapp URL and webpart name, and it will go through every ASPX document - Publishing, Wiki and Web Part page - and return the page URL, webpart DisplayTitle and webpart Name for each found. The reason I return the name is that I used the -like comparison operator to allow for loose (wildcard) searching. This is extremely handy when dealing with multiple webparts using the same canonical naming syntax.

I went down the webfolder/files collection path, as opposed to hitting the list/item collection, because I needed to recurse through the Forms subfolders to find any list form templates that had been customised.

The function first runs through every folder and subfolder and adds the (ASPX) files to an array called $AllPages. It then proceeds to get the SPLimitedWebPartManager for every file and interrogates the SPLimitedWebPartCollection for a matching webpart name.

The output proved invaluable to me as it showed that not only were some webparts present in customized list forms, but also in master and layout pages. I see a re-branding project in my near future. :\

Get All Lists by TemplateID

Next I wanted to get a list of all the web templates for my root sites. As I already knew where the list template features were activated, and at what scope, I didn't need to iterate through every webapp, site and web again.

The code below grabs the list templates for each root web in the $webs array that I'm interested in. It then outputs a GridView for each web's SPListTemplateCollection.

The grid is sorted by template ID (Type_Client). As you may know, best practice dictates that any custom list template have an ID of 10000 or higher, so this make identifying all custom templates a simple matter. You could even specify a Where clause to return just those templates but I find this list to be a handy reference anyway.

I absolutely love GridViews. In fact I could write several articles on them alone. Few people realise their power. Built-in column sorting and filtering, and selective row selection, can be passed through to the next pipeline object using the new -PassThru function in PowerShell 3. The GridView actually performs all those complex and fidgety sorting and filtering tasks on your behalf. If your next pipeline object is a CSV file then this essentially means you can re-use the same grid to generate custom reports without having to alter your code. [end rant]

So now I've got my list template IDs I want to locate every list instance that's been created using that template in the target webapp(s). The script itself is pretty straightforward. It prompts for a webapp URL and template ID and again it iterates through every site and web.

It took my a little while to discover that the list template ID was stored in List -> Rootfolder -> Properties in a property named vti_listservertemplate. But once I had that the rest was easy.

The output simply provides the list title and URL for each match.

I've modified the code so that both scripts can be both be run consecutively. In this way you can reference your list template ID and then add it at the prompt.

Wrap Up

I hope these last two posts help someone else trying to achieve the same or similar goals. I was fortunate that I only had to deal with list templates and web parts. I can only imagine the headache if custom field types, event receivers and other elements had been included. In my next post I'll likely be detailing the steps taken to eradicate all the deployed customisations found so that I can attempt to deactivate the features and retract the solutions. Unless the source code decides to suddenly materialise...

Thursday, 1 May 2014

List all SharePoint Solutions, their Features and where they're Activated [A PowerShell Journey]

I recently inherited a farm with a lot of deployed customisations and no source code or documentation for any of it. In my experience this happens all to often and trying to work out what's been deployed and where can be very laborious indeed. Enter PowerShell!

Get a list of all solutions, then just the deployed solutions.

That's a start but: Where were they deployed? Do they contain features? If so, what is their scope and where are they activated?

Thanks to Google I found a great piece of scripting by Régis Boussion on TechNet which really got me moving. [cf. How to get all site features and custom features from sharepoint sitecollection?]

What this returns is a list of EVERY Feature in the farm, grouped by its Solution. I have modified this to filter the output to only deployed solutions.

So now I have a list of deployed solutions and the count, name and scope of each feature it contains. But I want to know more.

Features - Scope, Activation, Location

The code below iterates through every web app, every site and every web so use it with caution. The end result returns output for each solution: solution name, its features and scope, and the URLs where it's activated. I say activated because that's what's returned by the Features property. And for my purpose I was only interested in activated features.

my.sharepoint.webparts.solution.wsp (2)
my.sharepoint.webparts Some Web Part (Web)
 > http://url
 > http://url/subsite
my.sharepoint.webparts Another Web Part (Site)
 > http://url

And the code:

I was hoping to take this one step further by drilling into the feature object to list any associated web parts, lists and so on but sadly this isn't possible.

In my next post I'll be dealing with locating web part usage and list instances for these active features.