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.

Wednesday, 10 April 2013

SharePoint 2013 - Configuring PowerShell

Powershell is great! Love it or hate it, it's become an essentially and awesomely powerful tool for SharePoint admins everywhere. But getting it up and running on a new server still presents the usual problems wtih permissiosns, configurations, version conflicts and a few wee hacks.

There's a lot to be done before you can run the Add-PsSnapIn command.

Step 1 - Installation

If you didn't run up the server yourself, then the first thing you'll want to do is make sure that Powershell, PowerShell ISE and the SharePoint 2013 Management Shell are all available.

Step 2 - Set-ExecutionPolicy

You will then want to set the script execution policy for the local machine to something useful like RemoteSigned, on your dev box anyway.

You can check this using:

> Get-ExecutionPolicy -Scope LocalMachine

And set it with:

> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

Lovely. At this point if u fired up the SP2013 Management Shell you'd likely receive the error below.

Add-PsSnapin : Cannot load Windows PowerShell snap-in Microsoft.SharePoint.Powe
rShell because of the following error: Could not load file or assembly 'file://
/C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.SharePoint.PowerShell\v4.
0_15.0.0.0__71E9BCE111E9429C\Microsoft.SharePoint.PowerShell.dll' or one of its
 dependencies. This assembly is built by a runtime newer than the currently loa
ded runtime and cannot be loaded.
At C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\CONF
IG\POWERSHELL\Registration\SharePoint.ps1:3 char:13
+ Add-PsSnapin <<<<  Microsoft.SharePoint.PowerShell
    + CategoryInfo          : InvalidArgument: (Microsoft.SharePoint.PowerShel
   l:String) [Add-PSSnapin], PSSnapInException
    + FullyQualifiedErrorId : AddPSSnapInRead,Microsoft.PowerShell.Commands.Ad
   dPSSnapinCommand

Yes, even after all this time there are still version conflicts to overcome.

So I created a powershell.exe.config file and saved it to the PS home directory in \Windows\System32\WindowsPowerShell\v1.0. Don't ask me why, but on native 64-bit systems, the 32-bit version is still used and the directory for PS v3 is still v1.0.

<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0.30319"/>
        <supportedRuntime version="v2.0.50727"/>
    </startup>
</configuration>

Don't evern try to make these changes with the 64-bit version or you will run into a world of new problems with system directory redirects back to the 32-bit locations.

Terrific! Try to add the SharePoint Powerhsell Snap-In again and you're now likely to receive this.

The local farm is not accessible. Cmdlets with FeatureDependencyId are not registered.

Hmmm.... I ran off the usual check-list:

  • I'm a local admin and farm admin.
  • I ran the shortcut as Administrator.
  • I modified the shortcut to add the -version 2 flag as per SP 2010 fix (and then removed it).

All to no avail.

The culprit in this case was a SQL Admin who had failed to add the SharePoint farm admins group to the securityadmin and db_owner roles. :\

Hope this helps someone else.

Friday, 22 March 2013

SharePoint Designer 2013 - No Design View But a Solution Exists

Yes, it's true, people. The Design View has been removed in Microsoft's latest release of SPD - and there's no plans to bring it back. The developer team offer the following explanation:

With SharePoint Server 2013 embracing new web standards for client side rendering of pages such as JavaScript, JSON, and OData, there is no longer a need to support a visual web page editor within SharePoint Designer.

Excerpt from Changes to the Design View in SharePoint Designer 2013

When you consider that SPD was the ONLY product that EVER understood and could show design-time visualisation of SharePoint components, this decision simply beggers belief. And it's not like this has made the application more stable or stopped it from randomly re-writing your source code either!

The Bad News

So front-end designers no longer need a design view, huh? We'll just hand-code our dataview webparts I suppose. :\ But wait. It gets worse. Gone too are the ability to add connections, change query scope, and add conditional formatting via the GUI! Embracing new web standards while ignoring the XML/XSLT transformation and CAML query pain us poor end users have to go through. Thanks - not!

The Good News

So what's the solution? I'm glad you asked.

While it may not be supported - and probably won't be possible with future versions of SharePoint - you can still connect to an SP 2013 site using SPD 2010. Yup, you heard me. What's more, you can still visually add and modify your DVWPs in the manner to which we've all grown accustomed. There is one trick though.

If you try to open a list form directly, you'll get a blank design window. The trick is to create an aspx page first and then add your Data View(s) to that. Bingo!

I prefer to add an Empty Data View and then connect to the required Data Source. This gives you more control over what to insert and how to insert it. Configure your web parts as you usually do, then export and upload them to your wp gallery for re-use across your site.

In my next post I'll walk through an Online Learning site that I built which takes advantage of many customised list forms and multiple form submissions within modal dialogs.

Wednesday, 27 February 2013

SharePoint 2013 - Branding Master and Layout Pages [ in Dreamweaver! :D ]

SharePoint 2013 offers a radically different approach to creating Master and Layout Pages for your sites. It introduces the new Design Manager in what I can only guess is an attempt to "dumb things down" for the new wave of web designers out there who no longer cut HTML by hand.

This new experience promises to make things vastly simpler by letting you use the HTML editor of your choice to develop your pages. Promises....but falls well short.

It does this by allowing you to import your design files in order to produce a master page which you then painstakingly have to shred in order to make anything happen. A distinct lack of documentation on how to achieve this caused additional headaches but...after failing several times to do it the right way, I decided to go down the path of least resistance and do it in reverse.

Creating your master page

I initially considering taking one of the OOTB publishing masters and duplicating this as my base but SP2013 won't let you do this. Custom masterpages are reliant on two files which are connected: the .master and the design-time .html file. The latter is the file you edit in order to influence the .master file.

So let's get started!

Go to the Design Manager and let's do things a little differently. I'll explain why as we go along.

Design Manager

  1. Skip steps 1 and 2 and move straight to 3 where you're asked to map a network drive to your site's masterpage gallery. When mapping the drive omit the &qout;_catalogs/masterpage/&qout; from the end and just go straight to your site's root. The reason for this is that you will want access to your entire site when we set up the remote site in Dreamweaver (or any other design tool).
  2. Now go to step 4 and select the Create a minimal master page link. Name it accordingly and once created, click the link to display the page in the browser. Bland, huh? But believe me, if you know how to mark-up and style HTML, this is FAR easier than working the other way around.
  3. Now open the Snippets gallery at top-right. This is where you can start adding the global elements you want for your page.
A word about Snippets

Upon opening the Snippets gallery you should be presented with the Top Navigation preview and mark-up. The preview window looks decidely bare. One of the reasons for all the bloated and mostly redundant markup is that SharePoint tries to include READ ONLY preview code so that the placeholder looks vaguely useful when viewed in your third-party design tool.

However, for top and vertical navigation elements this placeholder content is created using your existing site structure and navigation settings. If you haven't created any sites or pages then this is more than useless.

This leaves you with two options. Either go ahead and create some susbites and pages in advance to get a reliable preview of your final site. Or ignore it. I'd suggest the former. Despite all the bloated mark-up it's worth keeping in mind that this code is destined for our HTML file, which is used for preview purposes only.

Create your Site Structure and Navigation

So go ahead and do that now. Create some pages and subsites as per your final site structure just to flesh these snippets out. And also apply your site navigation settings. Then head back to Snippets so you'r eready for the next stage.

Create a Dreamweaver Site

If you're following best practice then the site you mapped a drive to will be on your development server. If not, it doesn't really matter but the ultimate goal behind all this branding work is that we will be creating a design package for distribution to production sites once we're done.

  1. If you haven't already authenticated a connection to your mapped drive. Do that now.
  2. Then create a new site. Name it and create a local site path.
  3. Then click Servers and add your mapped drive letter e.g. Z:\ in the server folder, and add the URL to your site.
  4. Click OK and connect to your site, showing the remote server files and you may notice something interesting. There's no _catalogs directory in which to draw down our masterpage! But never fear, just because Dreamweaver can't see this virtual directory, doesn't mean it can't connect to it.
  5. So open up your local site and create the folder structure we'll need for our branding files and also to fool SharePoint.
  6. Now copy your master HTML file across to this location. You can get to the masterpage gallery by typing _catalogs\masterpage after your drive letter.
  7. Switch back to Dreamweaver, select your master and Get this file. We've just created our virtual connection to this location - even though we still can't see it in the remote files!
  8. Now let's open our masterpage in preparation of adding our snippets.

Adding Snippets

Let's do some additional preparation by adding some DIV elements into the page in order to accept the snippets we'll be adding. Notably your site header for Site Logo, Site Title, Sign In, Search and Top Navigation snippets - assuming that's where you want them to go.

I'd also suggest creating a new css file for custom branding and style overrides. And also get all your image resources ready.

Now that we've done all the prep work it's time to start adding our snippets.

  1. Start with the Top Navigation and take a moment to look at some of the customisation settings. There's a lot you can do here to affect the appearance and functioanlity of this control. I prefer to leave the appearance settings alone and craft my own css styles to handle this.
  2. A couple of settings you may want to adjust, however, are under the Customization - Top Navigation ( AspMenu ) section.
    • StaticDisplayLevels: 2 [shows first-level of subsites as static items
    • MaximumDynamicDisplayLevels: 0 [default - no limit. Will show all levels of subsites and pages.]
  3. Click Update to apply your changes. Then copy the HTML snippet and paste it into the relevant container in your HTML master.
  4. Continue to do the same with the remaining placeholders and your page should start to take shape.

Web Parts and Other Elements

You'll notice that the Snippets gallery also allows you to add web part instances to your masterpage (and layouts). This is very powerful and a vast improvement when compared with the developer hell you had to go through to do this in previous versions.

Adding web parts to your master is completely optional and will depend on your requirements. Regardless, I would suggest leaving this for your layout pages, where they will then become editable via the UI.

You can also add Security Trimmed elemtns, Edit Mode panels and Device Channel specific content areas. Again, this will depend on your own requirements and I won't delve into any detail here.

Who > Ate > My > Breadcrumbs??

Looking at your page, you may be wondering why there is no Breadcrumb snippet available either here, or in the Layouts page Snippet gallery. This drove me batty for quite some time until I realised that a broken form of breadcrumb control was in fact on the page and embedded within the DeltaPlaceHolderPageTitleInTitleArea placeholder. Why? I have no idea.

Far too much time was spent trying to work out which SharePoint control was need to properly render these in the manner we're all used to - i.e. You are here: Home > Subsite > Subsite

Google relieved me somewhat by letting me know I was not alone with this issue and I eventually found a solution using the asp sitemap provider below. So just go ahead and add that to your page, or save it for your layouts if you choose not to have this globally visible.

Quick Launch - Vertical Navigation

Another oddity which I've yet to crack is the Vertical Navigation snippet. In the OOTB masterpages this displays your site menu as you would expect. But on appplication pages (such as People and Groups) it hides the site menu and either displays nothing, or the custom nav provider for those pages.

I tried all manner of tricks and additional placeholders to get this to work, even copying from the Oslo master, but still couldn't achieve this. I have to assume it's achieved with javascript but couldn't be bothered trying to work out how. If anyone does, please leave a comment.

In the end I crafted some javascript of my own that allowed the end user to hide/show this container as required on my system.master.

You can jQuery enable your site by simply adding a script reference after the customJSUrl tag.

Stylin'!

Now it's time to go ahead and and start adding styles to your elements. This is not a CSS tutorial but I would at least hope that you have applied a unique ID to all your DIVs so you're in a position to do this in a logical and structured manner.

I can save you some pain by at least showing you where you can safely reference your custom CSS files though. Inside the SharePoint:CssLink block is not the place. :) Add them just after this in order to give them priority. Tis avoids having to use the !important attribute all over the pace in your style overrides.

Wrapping up...

OK by now your masterpage should have taken shape. Of course it'll take quite a few previews in the browser before you get things just right. Upload the page to the server and then refresh the master preview in your browser.

When you're happy it's time to move onto creating layout pages. A word of warning here: get your masterpage right before creating your layout pages. If you have to re-edit your master for any reason, SharePoint forces you to re-attach your layout pages via the UI. This can have undesirable results and even break them due to server-side rewriting of your code. Yes...really.

Creating your Layout Page(s)

Back in Design Manager, skip past Edit Display Templates (another powerful feature which you can explore later) and move onto step 6. I won't spend a lot of time re-iterating what's already been said. You're essentially following the same method we used for creating the masterpage.

The only difference is that you will now be adding all your publishing page placeholders to the page, many of which are not present (for whatever reason) in the newly created layout page. There are also Edit Mode panels, Security Trimmed elements and web parts which you can use at your leisure.

Custom Content Types

If you have custom content types with additional fields, these may not be available in the snippets gallery. You can, hoever still reference them in your pages. You just need to know the field type and name (or ID preferably) and then add it to your page.

The example below assumes a Single Line of Text field.

Edit Mode Panels

These panels display only when the page is in Edit mode and can be very helpful to content authors by allowing them to add content that may otherwise only be added or modified via the Edit Properties form.

Good examples of this are the page Title and Rollup Image fields. I've provided my source below as a guide to help you get started.

Web Part Zones

WPZs are great. They provide wads of functionality to your pages and you can put them all over the place. There is really only one thing to be aware of here. SharePoint creats a unique ID for every web part zone you create. You can rename this to whatever you like but the important thing to note is that if you're creating more than one layout page (which you no doubt will) these IDs should map the the relevant zone(s) in each one. Otherwise your web parts will disappear when you change layout!

For example, if you have a Top and Bottom web part zone in one layout, you will want to make sure you use the same IDs in your other layout pages.

When you're done styling and layig out your new pages, go ahead and upload these too.

Conclusion

As you can see, there is a world of difference when it comes to creating and branding your sites in SP2013. In many ways I think Microsoft did a great job. Hopefully documentation will be provided soon to make it easier for people to add the myriad controls and other functionality to both master and layout pages.

An example of this is adding Rating and Comments fields to a publishing page layout. In my previous post I explained just how easy it is to do that...once you know how.

I hope you found this article useful and, as ever, I am open to any comments, suggestions, or corrections you may have to offer.

SharePoint 2013 - Add Ratings and Comments to Publishing Pages

Ok, I'm probably putting the horse before the cart here, as branding master and layout pages in SP2013 is a massive leap from what we were all used to in previous versions. But I just wanted to show how simple it is to get some of the new social features happening in publishing pages.

SP2013 offers a whole new way of branding. You can add page content fields and other elements to your layouts via the Snippets gallery in the new Design Manager. There are a couple of issues I have with this though:

  1. The generated code includes wads of redundant markup.
  2. The available fields are limited to what's in the default publishing page content type.

That being said, once you work out which controls are what and the prefix required, it's a breeze to just add these yourself. I'm stocking up my own gallery with trimmed snippets of my own which I may publish wholesale in another post.

Add Snippets

If you're familiar with SP2010 branding then adding these elements to 2013 is not significantly different. You just need to wrap your tags in the new comment format. First add SharePointPortalControls tag prefix.

<!--SPM:<%@ Register TagPrefix="SharePointPortalControls" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>-->

Then add our controls that use this prefix and namespace.

<!--MS:<SharePointPortalControls:AverageRatingFieldControl ID="PageRatingControl" FieldName="AverageRating" runat="server"/>-->
<!--MS:<SharePointPortalControls:SocialCommentControl ID="CommentControl" runat="server"/>-->

It's as simple as that. Drop them in a div, add a touch of css styling and voila!

Monday, 17 December 2012

Fun With PowerShell and SharePoint 2010

I recently had a project to create a repository for staff records, requiring a new site that required hundreds of SharePoint groups and individualy permissioned wiki pages with custom content and provisioned web parts. The idea of doing this manually seemed crazy and, as it was a one-off task, I decided to tackle it in PowerShell.
This post is broken into two parts, one for each script. The first deals with the creation and population of the SharePoint groups. And the second creates and customises the permissions and content for the wiki pages. I also provision a ListViewWebPart with custom view, and add a ContentEditorWebPart with content.

Creating and Populating SharePoint Groups

Once I had the group and member names (and verified them), this task was actually surprisingly simple. I simply created two arrays as shown below.
You can provide one or more users per group using a semi-colon as the delimiter.
Next we add the users to the site. This is important. I originally tried provisioning them directly to each group as it was created but this had the curious side effect of adding the users as group owners and not adding them to the group itself.
We then create an indexer for the users array and run a foreach loop against the groups array.
And that's it. Update the web and dispose.

Create Wiki pages with Custom Content

This script took quite a bit more work. SP2010's wiki pages have a few interesteing quirks that separate them from their Publishing or Web Part Page cousins. Firstly, they have no visible web part zone. They're treated more like documents and the content field accepts HTML as content.
Let's start by loading the SharePoint assembly and getting our required objects.

Customise List Permissions

I then needed to break permissions inheritance on the list and remove the site visitors role assignment to prevent access to the pages.
If you've ever worked with SharePoint permissions in code you know just how convoluted they can be. Is use the awesome PowerGui to interrogate the site objects and then drill down to find the required values. It's agreat learning tool and removes hours of guesswork and frustration.

Create the Pages

Now I was ready to create my pages. I used the same groups array I used earlier but you could just as easily create a new array and an indexer to pair it up against your group names. In my case, the the group names already represented suitable page names.
Note the use of the rootfolder path and SPTemplateFileType. I also used the new page's Item property to get a reference to my pages for later.

Create Item-Level Permissions

I then needed to break inheritance for each page and modify it's permissions by adding the required access group, while also reducing the Owners and Members groups access to Read in order to prevent tampering.
I pass $false to the first param because I don't want to copy role assignments. This was also important from a looping perspective to avoid having all the new groups added to subsequent pages. And finally remove the current user (me) by ID just for clarity.

Add Web Parts to Wiki Pages

As stated earlier, this is slightly different to adding web parts to other page types. Wiki pages have a hidden web part zone called WPZ which efeectively lives inside the pages content field. In addition, you're required to add the HTML container markup for the webparts to this field!
This had me chasing my tail for hours because I couldn't work out why the web part maintenance view for my pages showed the web parts but no content was being rendered to my page.
As with adding any web part, we first need to get the LimitedWebPartManager for the page.
Add a ListViewWebPart with Custom View
Once of the tricker requirements was providing a custom view for each page instance that was filtered by the page/group name so users would only see items relevant to them.
While adding a listview web part is quite straightforward, trying to specify or modify a custom view is far less so. As soon as you add a LVWP to a page, a hidden view instance is created on the source list. I found precious few references online as to how to modify this list instance.
The approach I took was to customise the original view's Query property and update it each time. This was fine for my purposes because I then simply deleted the view from the list after implementation.
Also note the format of the GUID for wiki page webparts. For easons unknown this is different the standard GUID format we've come to expect for anything SharePoint. The replace method below shows how to convert it to the correct format.
But of course nothing ever goes as planned. It turned out that the views needed tweaking AFTER page creation. I could have deleted all the pages and started again but I was curious to see if there wasn't a way to somehow get a reference to this elusive hidden view and update it directly.
I again used PowerGui to drill down into the webparts properties to grab what I needed. After getting a reference to the web part I wnated by its Title property, I simply grabbed it's ViewGuid and passed this to my list to get the hidden instance before updating the Query and the view.
Add CEWP with Content
One final requirements was to hide the unrequired Recently Updated list from the Wiki page's QuickLaunch. I'd initially hoped I could embed the required CSS to the page's content field but SharePoint prepends the style with a new class that prevented this.
A little investigation revealed that this web part has a Content property of type XmlElement. So you first need to define a root element and then add your markup to its InnerText node.

Add Content to Wiki Pages

The final step was to add some instructional text to the pages. For this we can simply pass a raw HTML string to the page's WikiField. Th eonly problem I had here was with the quirky syntax used by PowerShell's here-string. The beauty of here-strings is that you don't need to escape them. Just be sure to use the $($myparam) format for any PS vars or they will be interpretted literally.
You can also see the markup required for the web part containers and the less than obvious way to pass in the required GUIDs.
We can finally close our loop and update the page, before updating and disposing of the web. And we're done!
What's that you say? You found a typo in the page content? Oh dear... :{
Never fear. Just as we update the WikiField's value, we can also grab it. Just get a reference to it, then re-provide your new content string and set it again. You can also append new content in the same manner, while leaving the existing content alone.

Wrapping Up

When you put it all together you can see that it all makes perfect sense. Well, mostly. But there were many little catches and caveats along the way that made this exercise take far longer than it should have. A distinct lack of documentation being the usual issue.
That being said, including development time, I knocked out 147 custom pages in less then 3 hours. Esitmate how long it would have taken to do all this through the UI. ;)

Friday, 16 November 2012

SharePoint Page View Counter

In my last post [SharePoint Hit Counter] we looked at a solution to add an ad hoc hit counter to selected lists. While this solution could be tweaked to work with any kind of list, I wanted to look at a more global solution that could track unique visits and store them in a more centralised location.
This solution uses a web control which is referenced in the site's masterpage. When the masterpage is hit, the URL, logged in user and today's date are written to a centralised list. The control itself just renders the number of visits to the current page. The code behind perofrms a check to see if our Sats list exists and creates it if it doesn't.
If your site uses two master pages - one for Site and one for System pages - then you can apply it to one or both. In may case, I only referenced the control in my custom master. The reason for this is that I only wanted to target Publishing Pages. For starters, the URLs cleaner and I already had a separate solution in place for these. Feel free to modify the solution to suit your needs.

The Masterpage

If you already have a master page feature then you can just make the required changes and add the control where you like.
This requires two additions to be made. A tagprefix at the top of your page which registers our tagprefix name and associates it to our assembly code. I used the same namespace as my previous list stats solution for consistency.
And then a reference to your control where you would like to see it appear. I chose to place mine to the right of the breadcrumbs but it would go just as well in the footer.

The List

In this case I chose to add the list creation code into my control class. There are many (possibly better) ways to do this, such as via feature receiver or list template. The benefit to this method is that if the list is ever re-named or deleted (by accident or design), it will be recreated automatically on the next page view.

The Web Control

The web control itself is quite simpe and has no properties or controltemplate for redering. Add the appropriate references and using statements to your project, and a brief description of what it does.
Then can then begin defining our control class. The first thing we need to do in this case is to make sure our list exists and, if not, create it. The following is a useful means of getting a list by name, which allows you to pass in the SPListCOllection of your choice.
We then override the OnLoad method and begin by getting our current context items. Then open our elevated priviliges block, open a new SPWeb object and grab the values we want to capture for the Stats list.
Then we check to see if our list exists and create it otherwise. I also decided to create a custom view and make it the default. I chose to group by Title (URL) and display Page name, Date of view and the User.
And if it does exist, we query it and update the list only if the current user has not hit the page today.
Now all that's left is to override the control's render method and display the view count on the page. The query simply gets all items from the Stats list that match the current URL.

Conclusion

The final result is simple but effective. as in my previous post, you could take this a step further by creating a stats viewer web part. But in my case the custom list view which I added a Total for to the Date field, provided everything I needed.
As usual, all feedback on how you might improve this solution is welcomed.

Thursday, 15 November 2012

SharePoint Hit Counter

If you've ever enabled and viewed the Usage Analysis reports for SharePoint, you'll already know that they're...well, lacking. I was recently requested to provide hit counts for annoucements on a coporate Intranet and rose to the challenge, of which their were many. :)
Now while there are many ways to skin this cat, my requirements were to provide an ad hoc solution that can could be enabled at the list level by anyone with the Manage List permission level.
I have described a different approach [SharePoint Page View Counter] for Publishing Pages that uses a dedicated list, gathers more information, and comprises a custom webcontrol that is then referenced in the site's custom master page.
The following describes a MOSS 2007 solution comprising of two web parts - a Hit Counter and a Hit Viewer - as well as a site column definition, and is deployed as a site level feature. This could be easily modified for 2010.
The end result will look something like this.
Fig.1 - Hit Viewer web part
Fig.2 - Hit Counter web part
I use WSPBuilder for convenience when creating MOSS solutions, so some of these steps will vary.
Start by adding a web part feature to your project called CounterWP. Then add a second web part called HitViewerWP.
Open up your elements.xml file and add the following field definition for the site column.
There are several advantages to deploying the site column. Firstly, it removes any guess work or room for error on behalf of the content author who is adding it to the target list. Secondly, we're able to hide the field from any of the list form views; something you can't really do via the UI.

HitCounter Web Part

Now let's create a class file for the Counter web part. I'm inheriting from System.Web...WebPart class just to reduce the using footprint. First thing I want to do is create my global booleans for an ID key/pair value in the current URL and the presence of the custom Counter field.
The rest of the code is commented pretty heavily but I'll break it up anyway. As we only want the code to run on DispForm pages with an valid ID present, I'm going to check for the ID first in the OnInit event. No point continuing otherwise.
If the ID is there we can then grab the current context in the OnLoad event. Because we're elevating privileges, we want to get our contexts before trying any update methods.
We don't want the counter field to increment when the page is in Edit mode, or the field doesn't exist, so we'll check for both.
Now we can create our elevated privileges block and open a new SPSite instance to re-grab our needed objects. I do an additional (redundant) check for the Counter field because that's how I roll. You could/should just as easily use a try/cach block.
Then check for a null value on Counter and set it to zero. This is important, because if we add the field to a list with potentially hundreds of existing items, we don't want to have to set a value for all of them. New items get the default value of zero, as defined in the field definition.
Notice that I check the field value's object for null. Also important. Checking for counterStr as a null or empty string does not yield the correct result. Don't ask me why.
We then increment the current value by 1 to count the current hit.
Now we're ready to perform our updates. As we're wanting to update the database from a GET request, we need to set allowunsafeupdates to true on the SPWeb.
We're also going to do an additional check for Content Approval on the list object and approve the item at the same time, otherwise we'll leave the updated item in a Pending state.
Here's the rest of our OnLoad code.
Now let's render the results to screen to provide feedback for the user. This is optional but I provide it here for completeness. I used the RenderContents method for future proofing.
And that's it for our CounterWP. Build your project and let's move on to our HitViewerWP.

HitViewer Web Part

Again, I inherit from System.Web.UI and define a couple of globals for error checking and to get/set the user-selected query for the report viewer.
I chose to check and set a few web part properties during initialising. Again, this is optional.
Now add the CreateChildControls method which will call our render code. As I'm rendering multiple controls this method is preferable to the RenderContents method in this scenario.
Our LoadLists function firstly gets the list collection for the current SPWeb and adds the user controls for the view selector dropdown list.
I then chose to add a script block with a little jQuery goodness to collapse the lists on page load, and an onclick event to toggle the items display. If you don't already have a jQuery reference in your masterpage, you will need to add a reference to the library here as well.
").AppendLine(); ]]>
Then begin rendering the container and grab only those lists whose field collection contains our Counter field. No point querying lists that don't and causing performance issues.
Now let's try to grab the list titles and define the queries for the view dropdown list. I provided two options. The default query includes a filter that grabs only those items modified in the last 30 days. The second doesn't discriminate. It will grab all items.
In both cases I've restricted the queries to the first ten items matching the criteria. I also specify only the ViewFields required to render my items. This is again for performance.
I use SPQuery over other methods (such as SPSiteDataQuery and CrossListQueryInfo) because I already have my filtered list collection, and because it offers me more control.
Now we can define the render method for our list items, catch any exceptions, and add our literal control to the page.
Finish off by adding our exception handling function and we're done!

In Closing...

Because we're updating the list item, any list views that order by modified date will be affected. There are pros and cons to this. The pro, is that your list will now be effectively sorting by popularity (most recently viewed items.
If this isn't what you want, or you have the need to keep some items 'sticky', then you have a number of options. Sort the view by Created date, create a new Yes/No column to keep important items at the top...the choice is yours. In my case I did both.
As a final touch you could use a little jQuery to check for a 'yes' value in your 'sticky' field and append a ! to the item's title to make it clear why some items are at the top.
Enjoy! And as usual, I look forward to any feedback.