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. ;)


Jimmy said...

great post,
any tips on how to add web part to an existing wikipage

Paul Noone said...

Hi Jimmy, I just gave two examples. Can u be more clear as to what you're trying to do?

Get your page ref and then simply add the web part as required, tweaking the the "Add CEWP with Content" code. Each web part may require its own parent reference.