Monday, 29 October 2007

STSADM export, Content Deployment, Content Migration API, Features/Solutions - deployment options compared

Back in May I wrote a post titled SharePoint deployment options : Features or Content Deployment?, which discussed some thoughts on what was the "right" way to move assets from development to production (and perhaps environments in between) during the site development process in SharePoint. Having now worked on other projects and consciously used different deployment methods on each, I'm rapidly coming to the conclusion that the "right" way to do deployment varies according to circumstances. So I thought what might be useful is an analysis of the whole range of deployment options, with information which might help you decide more easily on how you will complete this crucial step of the process.

So let's run through the options and their characteristics. Note by the way, that none of the options use 'destructive synchronization', where all content is first deleted before import.

Using STSADM export/import

Description:

Uses STSADM commands to generate a file (export) which can then be transferred to the target for import. One of the simplest ways of moving content from one place to another, although unlikely to be suitable as a continuous deployment mechanism. Examples:

stsadm.exe -o export -url http://localhost -filename C:\Export.cab -includeusersecurity -versions 4 -overwrite

stsadm.exe -o import -url http://localhost/sites/newsite -filename C:\Export.cab -includeusersecurity

Good for:

  • Moving an entire site/web as a one off
  • Quick deployment tests
  • Reparenting webs (can be into a different site collection)

Considerations:

  • Content on target will be overwritten if already exists
  • Granularity down to web only
  • Object GUIDs are not preserved (so some things will need to be 'fixed up' e.g. anything that references a list by GUID - ListViewWebPart, using lists with InfoPath forms)
  • Not a backup/restore tool - although it's the option which is most like backup/restore, things like alerts, audit trail, recycle bin items, security state, workflow tasks/state are not exported
  • Not transactional

Using Content Deployment via Central Admin *

Description:

Configured via 'Content Deployment paths and jobs' in Central Admin ('/_admin/deployment.aspx'). A path defines the source/target and authentication details, specific jobs define exactly which content should be deployed and how often. Quick deploy functionality allows users with permissions to specify important content which should be deployed more regularly than existing job schedules configured by administrators (quick deploy items are deployed every 15 mins).

Good for:

  • Moving entire site collections/webs on a scheduled basis e.g. in an authoring/production or authoring/staging/production topology
  • Deploying only incremental changes, e-mail notifications of success/failures
  • Allowing site owners to have some control over content deployment via Quick Deploy
  • Automatically deploying dependencies of content selected for deployment, even if in different site (e.g. page layouts/content types/site columns/referenced images etc.)
  • Automatically transferring the deployment package to the target environment (via HTTP[S])
  • Not transactional

Considerations:

  • Content on target will be overwritten if already exists
  • Granularity down to web only
  • No differentiation between site content (e.g. pages/images) and site 'infrastructure' (e.g. master pages, page layouts)
  • Object GUIDs are preserved
  • Blank site template should be used for source and destination site collection (see http://support.microsoft.com/kb/923592)
  • Also not a backup/restore tool (see above)

Using the content migration API *

Involves writing code which uses the content migration API (known as PRIME) to export then import content - the API is easy to use.

Good for:

  • Complete flexibility over deployment options
  • Granular control over what gets deployed (down to item level)
  • Ability to preserve object GUIDs (so that list GUIDs do not need to fixed-up)
  • Ability to select options for security, versioning and user roles

Considerations:

  • Blank site template should be used for source and destination site collection (see http://support.microsoft.com/kb/923592)
  • Not transactional
  • Also not a backup/restore tool (see above)
  • Need development skills to write code

Using Features/Solutions

The focus of this blog for several articles. Involves defining XML configuration files which SharePoint uses to add artifacts in the correct way on the target. This can be significantly more complex than simply developing in SharePoint Designer but can allow for better management throughout a solution's lifecycle.

Good for:

  • Iterative development/deployment
  • Deployment of assemblies and filesystem files (none of the other methods deal with this)
  • Ability to deploy assemblies/filesystem files to all servers in a farm with Solution packages
  • Possibilities for continuous integration

Considerations:

  • Developer is responsible for evaluating and deploying dependencies (e.g. underlying content types).
  • Updates to content types, list definitions, site columns etc. deployed via a Feature must be done with the API - modifying original Feature files and then reprovisioning is not supported
  • Can be very time-consuming due to lack of assistance from current tools

* Some additional notes on using Content Deployment or content migration APIs:

- appropriate Features will automatically be activated on the target, but they must be present (i.e. installed) for content deployment to work (N.B. publishing feature should not be enabled on target for first deployment)

- using Content Deployment or content migration API with RetainObjectIdentity option should not be combined with STSADM -export/import, since the latter will allocate new IDs!

So clearly there can be a few aspects to consider in choosing how to go about deployment for your project. In many scenarios where Features/Solutions aren't the most appropriate option, I favor using the content migration API, mainly due to the flexibility which isn't provided in any of the other options. Of course it does mean writing code, but as I mentioned last time, I'll soon share the mini-app I wrote so you don't have to!

Some useful references:

Sunday, 14 October 2007

Deployment using STSADM export or content migration API

Having focused on deployment using Features for several articles, back in May I wrote an article titled SharePoint deployment options : Features or Content Deployment?, which explored some of the decisions around deployment strategies for SharePoint projects. There are a variety of methods which can be used to move SharePoint artifacts and content from one place to another, and I think it's fair to say there's still a certain amount of confusion around deployment for many SharePoint developers. I certainly wouldn't claim to have all the answers, but after delivering another project last week, it seems like a good time to go over some of the experiences and reflect on the different approaches.

Needless to say, as far as deployment strategies go in general, the best idea is to have one! I see many newsgroup posts from people approaching the end of the development phase asking how they should move their work to the live servers. The problem I find with leaving deployment until the end of the project, is that none of the approaches are completely straightforward (particularly depending on what your solution consists of), and so if your project is to be delivered on time, it's important to know what steps you might need to go through.

As a sidenote, let's clarify some potentially confusing terminology here:

  • Content Deployment - the "paths and jobs" functionality which can be used to move content, surfaced by screens in Central Admin
  • Content Migration API - the underlying API (sometimes referred to as PRIME) which actually is used for both STSADM export and Content Deployment (in slightly different ways), in addition to the 'Manage Content and Structure' tool and in migrations from CMS2002

This time round I had decided to use the content migration API to deploy our solution, and it worked well for our circumstances. This is a contrast to developing with Features which I've done in the past, and the main reasons for choosing this approach were: 

  • no need for iterative deployment - although our overall project is phased, for this component we were able to develop the solution and then deploy everything from our development environment. (This approach will not work for subsequent deployments since content our client has generated on the live site would be overwritten on each deployment - more on this in an upcoming post.)
  • ability to retain object GUIDs - this simplified deployment significantly for our project, since if our lists were allocated new GUIDs on deployment (as happens with STSADM export/import), our components which referenced these lists (ListViewWebPart, InfoPath forms etc.) would not hook up properly on the deployment target. This would add a lot of "fix-up" steps to the deployment process if we were to use STSADM export.
  • no direct HTTP access from source Central Admin to target Central Admin - this is a prerequisite to use the Content Deployment functionality (paths and jobs) in Central Admin, but what we needed was a file we could copy to the live server. The content migration API provides this ability and also gives a compression option for large amounts of data.
  • automatic inclusion of database dependencies - as with STSADM export, (but not with Features), SharePoint will analyze and collect all dependencies such as fields, content types, master pages etc. for us.

The API is fairly simple to use and you may have seen Stefan's series of excellent articles on the subject - these serve as a good companion to the MSDN documentation.

It's important to remember that any non-database assets (e.g. user controls, assemblies etc.) need to be deployed manually to the target environment - these will not be included by use of something like STSADM export or the content migration API. In our case, since the live environment was a single server (and versioning would be handled by our main source control system), these were deployed by XCOPY since deployment using Solution packages did not offer any compelling advantages here. 

Whilst we're talking about filesystem files, it's useful to be aware that if you see 404 errors on the target after performing (e.g.) an STSADM import, chances are you've forgotten to deploy something like a user control. The 404 is actually coming from the referenced file rather than the actual page, so don't assume something has gone wrong with the import - a check on 'View all site content' and the import log will probably confirm all the site pages are present!

Hopefully this has given some food for thought on an approach you may not have considered. I guess my main message here is that whilst STSADM export is extremely simple, it may not provide the complete answer to all your deployment challenges due to changing GUIDs. In upcoming posts I'll provide a more direct comparison of deployment strategies (extending my 'Features or content deployment' post), and also share my mini-app which provides a front-end onto the content migration API.

[P.S. Sincere apologies to people who left comments whilst I was on holiday which are still not published - I'll publish these and respond over the next few days.

C.]

Wednesday, 12 September 2007

Annual holiday

Just a note to regular readers to say it's time for my annual holiday, so no posts or replies to comments for 2 or 3 weeks. I'm off to San Francisco (which is where Suzanne is from), so it should be a nice break.  I am taking a SharePoint virtual machine, but am under strict instructions the only times I'm allowed to use it are on the plane journeys! Fine by me actually.

More posts when I get back..

C.

Sunday, 9 September 2007

Blending publishing/collaboration functionality in SharePoint

Most often when creating SharePoint solutions, the requirements often map fairly well to one of the out-of-the-box site definitions which can be used to create new sites. If we're creating heavily-branded internet/intranet sites (WCM sites), we'll probably start with the 'publishing site' template. If we are deploying SharePoint in a document management/collaboration scenario, we'll probably start with the 'team site' template, and so on. Where it gets interesting it when the project requirements effectively have a mix of this functionality. Characteristics of such a site might include:

  • site has completely bespoke look and feel/navigation
  • users will work with files stored in document libraries
  • site templates or definitions are used to create several sites with the same content/functionality
  • custom workflow is used to support a business process (other than standard content publishing), perhaps with InfoPath forms

Such requirements present a few challenges, and a current project of mine fits into this category. At a high level, one consideration is that site users will also use 'system' pages provided by SharePoint in many scenarios (e.g. working with document libraries/lists, workflow etc.) and this doesn't happen in most WCM sites. This can lead to situations where there is a disparity between the look and feel of the 'published view' of the website and the 'system' areas. I don't intend to provide answers to all the issues here, but I do want to discuss a few as some food for thought. I'll probably revisit this post at the end of the project and provide a better insight into the issues and solutions, but for now let's cover some high-level decisions:

 

Approach for master page development

Options for starting development here include:

  • Using a 'minimal master page' from MSDN or Heather Solomon
  • Modifying a copy of default.master (good starting point for customized team sites)
  • Modifying a copy of blueband.master (good starting point for WCM sites)

Partly this decision depends on where you are heading. Since the aim in my project is for formatting to be controlled by CSS rather than layout tables, starting with a minimal master page makes more sense (the shipped page layouts use tables). This is an interesting area since there's a lot of rework to be done to eliminate tables in a mixed publishing/collab site (and in fact it often won't be possible to eliminate them completely), and for me the benefit is debatable. Certainly all the 'system' pages which site users will be exposed to use layout tables, so I'm not sure how much is gained by only having some pages using CSS for layout.

Other things to consider here are the usual questions of how to factor responsibility of content items between the master page and page layouts, how to define content types etc., but these are standard decisions in WCM site development so I won't cover them here.

 

Use of Content Editor web parts vs publishing RichHtmlField controls

Most folks in WCM development know there is an overlap in functionality provided by the Content Editor web part and the RichHtmlField control in the Microsoft.SharePoint.Publishing.WebControls namespace, i.e. they can both be used to enter page content such as text/images. However it's important to consider the differences - the RichHtmlField control stores it's content in a column of the list item for the page, whereas the CEWP is a web part and thus stores content in the web part storage architecture. This is important, since if deployment to a different environment is in your project plan or ongoing architecture, things will likely be simpler if you use the field control, since this content will then travel with the page properly.

Additionally, there are some URL fix-up issues with using the CEWP across different environments, as documented in the HawaiianAir.com write up.

In summary, I'd recommend considering the CEWP as a means of entering content in non-publishing SharePoint sites only. 

 

Use of collaboration web parts - in layouts or in WebPartZones?

In a similar vein, since we are mixing the collaboration features into our site we are likely to need to use certain web parts which we wouldn't in a straight WCM site. In our situation the ListViewWebPart is fairly key to some areas, and is used as a means of allowing users to work with different lists from one page. The first decision here is whether the page layouts should include web parts directly (by adding them in SharePoint Designer), or just web part zones to which the individual parts would be added later through the browser. In most WCM scenarios I prefer to add web parts directly to page layouts since they will not be customized/personalized by end users (the main usage scenario for web part zones), and when web part zones are used, again the web part config is not stored in the page which can make deployment more complex. Using the other approach of adding directly from SPD, config is stored in the actual HTML markup of the page and so travels with the page layout itself.

However! The ListViewWebPart has some quirks which means it isn't always possible to use directly from the page layout. Specifically, it is only possible to configure the part to consume a list from the current web, and in the case of a publishing page layout, this means the root web since this is where the master page gallery is stored. Since our lists are stored in a child web, this is problematic - the other solution of using a DataView also had issues. Additionally, the ListViewWebPart configuration stores values specific to it's location, meaning the config XML is not very portable (i.e. export web part definition, modify, use). I'd like to think it would be possible with time to work out exactly which IDs do need to be changed, but alas we don't have time on this project.

As a result, using the ListViewWebPart in a web part zone is actually the best solution in these circumstances as far as I can see. We'll have an extra few steps at deployment, but this will take less time than the alternatives it appears.

 

Look and feel of system pages

As mentioned earlier, for a mixed WCM/collaboration site there can be a disparity of the look and feel of the main pages of the site and the 'system' pages users will see, i.e. pages from the '_layouts' directory. Note this happens even if both the site and system master page is set to point at your custom master page, since these pages are set to use 'application.master' (also on the filesystem in '_layouts') which neither of these properties affect. Sure it would be possible to simply replace 'application.master' with your own version, but that's not an elegant solution and would probably be unsupported. Unfortunately it seems that the architecture doesn't provide an easy way to change the master page used by '_layouts' pages - you have to go a level deeper to explore ways of doing this. Many .Net 2.0 developers will know it's possible to switch a master page dynamically in .Net, and to be fair this is what SharePoint does with the maser pages stored in the master page gallery anyway. I'm not aware of a truly elegant solution to this problem, but this discussion on Serge van den Oever's blog presents a viable approach using this technique. 

 

So those are some of the issues to consider. There are certainly others, including navigation, CSS customization of standard styles (to ensure collaboration web parts integrate well with your look and feel), and possibly the choice of authentication mechanism. I'll cover these and any others which arise in an upcoming post.

Monday, 3 September 2007

VSeWSS 1.1. CTP - a look at the nuts and bolts

Regular readers of this blog will know that I've been a reasonably keen advocate of Microsoft's Visual Studio Extensions for Windows SharePoint Services (VSeWSS) for certain SharePoint development tasks. If you read more than a couple of SharePoint blogs, you won't have avoided the news that MS have just released version 1.1 of this tool, as announced here by Alex Malek over on the SharePoint Designer blog. At the time of writing, this is a CTP (Community Technology Preview) meaning it is pre-beta, so bugs can be expected. So today I wanted to run through the changes, so folks who aren't familiar with the tool or haven't had a chance to take a look themselves can quickly get a sense of what's in there.

In essence, VSeWSS helps by simplifying the process of developing Features for SharePoint - this approach is generally regarded as the way to do SharePoint development in such a way that assets can be more easily deployed to other environments. In previous articles, I detailed how to create and deploy lists and how to create and deploy web parts with VSeWSS. For me, these are probably the scenarios where VSeWSS comes in the most useful, though there definitely are others depending on how you work. However, the most common gripe of developers who used the tool was that since VSeWSS 1.0 regenerated Feature files with each change, it wasn't possible to manually amend the files. This was often necessary to add things not directly supported by the tool, such as Feature receivers.

So let's run through the different areas where the tool has been improved.


WSP View

The big change with this release is that it is now possible to amend Feature files in the development process. Instead of hiding the generation of these files behind the scenes, the tool now makes these bona fide VS project items which can be edited before the Solution package is built. Certainly auto-generation of the final files still happens (that's the point) and there are some files lurking which are needed to support the tool, but VSeWSS now does a good job in presenting to you what you can modify through the WSP View (View > Other windows > WSP View). This is shown below for a project with some different artifacts:



Some nice things which can be done here are that entire Features can be deleted with a single click (all files will be removed), and the Feature activation order can be changed. Toolbar buttons are provided which modify the sequence of FeatureManifest elements in the manifest.xml file.


Feature file editing

So the WSP View provides the mask onto the editable files, but I wanted to drill into why this is useful. Some examples would be:

  • ability to add Feature receivers to a particular Feature
  • ability to rename properties of the Solution package, e.g. to bring VSeWSS-generated Solutions into line with a naming convention
  • ability to refactor Feature elements into a single Feature. This can be useful because by default the tool will create a new Feature each time you use Project > Add new item > Some SharePoint item (e.g. Content type). Often you will want several Feature elements to comprise a single Feature rather than split over several Features.

Event receivers

VSeWSS 1.1 also provides some enhancements around event receivers (handlers for events raised by lists). Version 1.0 also provided some support in this area, but the main scenario was adding a list/item receiver at the time the list was being created (i.e. "create list with receiver"). Version 1.1 now makes it easy to add an event receiver to an existing list, which simplifies those scenarios.

My favorite improvement in this area however, has to go to event receivers on content types. This effectively means that the event-handler code travels with the content type - so your code will run on all lists the content type is associated with. This is interesting as I had no idea this was possible in the WSS 3.0 platform! So I did some digging. I haven't yet done any testing of the results, but what VSeWSS does to support this is add some custom attributes to a standard list event receiver. For those interested, these are defined in the SPDevTools namespace in a file which is added to your project (out of the way in the Properties folder, alongside AssemblyInfo.cs) by the tool. I've not yet worked out exactly what picks up these flags in the Feature activation process, but it's clear that standard list event receivers are actually being used as the generated class derives from SPListEventReceiver. The image below (click to enlarge) highlights the new attribute being applied which supports all this:




Web parts

This was where I thought VSeWSS was a winner, and it's good to see the support for web part deployment has been extended further still. With 1.1, a default web part definition file (.webpart) is now generated for you to edit before deployment to add custom property settings. This is useful, since it means that developers can avoid the "deploy web part, add to page, configure, export web part" process which is otherwise required to obtain a configured definition.


Performance

One of the first things VSeWSS 1.0 users will notice is that a full IISReset is no longer performed on each deployment. This was something of a pain with version 1.0, since it meant the "edit, deploy, get feedback" cycle took longer than was necessary. So having been used to the IISResets, personally I was pretty pleased to see the message below in the Visual Studio status bar during the first deployment!



 
What could be better
 

So those are the good bits, and it's a great step in the right direction. However I'd still like to see the following:

  • fully-featured Solution Generator. I didn't have time to look at this in detail, but the download page specifies that certain items still aren't covered. This ties in with what I remember Alex Malek saying at this year's Tech Ed, that this still wouldn't be "full fidelity".
  • ability to easily add a Feature receiver to a Feature I'm working with. I just want right-click > Add Receiver - this should generate the class and add the attributes to the Feature definition with default values, or perhaps infer the generated type.
  • ability to easily refactor Feature elements in Features, since I often don't want the default of a Feature per element
  • no GUIDs in Feature names (though apparently this will be sorted by final release)
  • ability to deploy a _layouts file (again, this should be there in final release)

On a "bigger picture" note, I'm reminded of a common view in the SharePoint community which made complete sense when I heard it first from Ted Pattison. Unlike .Net, where Microsoft has gone to great lengths to ensure the tools are in sync with the platform (same team [Scott Guthrie], same timeframe for development phase, same ship date), a different thing is happening with SharePoint. Effectively the tools are 18 months behind the platform, so SharePoint development can be pretty painful at the moment -  however, this shouldn't be taken as a reflection of the platform.

This release of VSeWSS should help though, and it'll be interesting to see what comes in the future.

The download link is http://www.microsoft.com/downloads/details.aspx?FamilyID=3e1dcccd-1cca-433a-bb4d-97b96bf7ab63&displaylang=en.

Monday, 27 August 2007

Adding custom help pages to SharePoint is complex!

Something I've been curious about for a while now is how to extend the SharePoint help system. A while back I wrote about how to modify 'system' pages in SharePoint by effectively adding new pages - the examples I used were:

  • a custom Recycle Bin page which displays only items deleted by the current user
  • a custom Central Admin page which has a message specific to my fictional organization's administrators

When implementing customizations like this, I thought it would be useful to be able to add custom help to accompany the new functionality. So today I set about digging around the SharePoint help system to see how to do this. Unfortunately my answer so far is that it's pretty difficult and I haven't figured out all the pieces! However, I thought I'd detail what I found in the hope that either it's still useful to somebody, or that someone who knows can perhaps leave a comment or link and complete the jigsaw.

So to be clear, I'm talking about how to add custom application help pages here - nothing to do with the 2 .chm files which are the WSS and MOSS SDK references. In site or central admin pages, the application help pages are linked to from help icon on the top bar:

As you'd expect, this icon (and the link behind it) is provided by the master page for the page. Clicking the link calls a JavaScript function called TopHelpButtonClick() in core.js to open the help window - on most pages a parameter of 'NavBarHelpHome' is passed, though notably pages which link to a page other than the default help page override this value. Since all pages in say, Central Admin share the same master page (and the JS call is in the master page, not the actual page) the code in core.js checks to see if the page itself has specified an override. Individual pages can therefore override the help location specified in the master page using a script block such as:

<script type="text/javascript" language="JavaScript">

       var navBarHelpOverrideKey = "OSSCentralAdmin";

</script>


Core.js will then build a link with this override parameter in rather than 'NavBarHelpHome', meaning that pages can easily link to a custom page rather than the default. In order to link to a custom help page, your page will need to override this value.

The value used here links to a value in a set of mapping files stored in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\HELP. In the case of the value shown above, we find that one of the mapping files, 'OssSearchAdmin_HelpKeymap.xml', contains the following:

 

<helpmap>

    <key>OSSCentralAdmin</key>

    <collectionId>MS.SEARCH.ADMIN.manifest</collectionId>

    <contextId>MS.SEARCH.ADMIN.OSSCentralAdmin</contextId>

</helpmap>


The set of mapping files in this directory all contain similar entries, i.e. a corresponding entry for each page which specifies an override to the default help page. Since these files don't appear to follow any naming convention, I'd venture that any XML file with the correct schema can be dropped in here and the help system will pick it up. When it comes to finding what these references point to, things get interesting! An initial search across the '12' directory using Visual Studio 'find in files' yields no results, but as with most of these things it's worth digging further. In the '\12\HCCab\1033\' directory are a set of .cab files which store the help files used in the SharePoint help system. In the example above, the 'MS.Search.Admin.HC.cab' file is the file which is referenced. Looking inside, we see a number of files, one of which is named 'MS.SEARCH.ADMIN.manifest.xml', which you'll notice is the value for the <collectionId> in the mapping above. The contents of one such .cab file look like:



So a quick recap of so far - we've found how to override the initial help page which is displayed, and how the key specified links to a set of help files packaged as a cab file in the 'HCCab\<localeId>' directory.

The manifest file is obviously key to how the index of the files in the .cab are linked. Incidentally, for those familiar with building SharePoint solution packages, the schema used here is completely unrelated to the 'manifest.xml' file used there. Looking at the contents of one of these manifest files, the first thing that strikes me is it looks like a system-generated file - for one thing there are many parent/child and link relationships which would be complex to document by hand. An extract looks like:

<?xml version="1.0"?>

<helpCollection>

  <name>SearchCentralAdmin</name>

  <id>MS.SEARCH.ADMIN.manifest</id>

  <changedDate>2006-10-13 21:53:57Z</changedDate>

  <createdDate>2006-10-13 21:53:57Z</createdDate>

  <author>Microsoft</author>

  <version>11.0.9413.2</version>

  <lcid>1033</lcid>

  <defaultHelpItem>MS.SEARCH.ADMIN.HA10175815</defaultHelpItem>

  <brandingImage />

  <rootCategory>MS.SEARCH.ADMIN.CH10176169</rootCategory>

  <feedback show="False" />

  <helpItems>

    <helpItem>

      <id>MS.SEARCH.ADMIN.HA10047848</id>

      <parents>

        <parent sortOrder="4" primary="true">MS.SEARCH.ADMIN.CH10176336</parent>

        <parent sortOrder="4" primary="true">MS.SEARCH.ADMIN.CH10176336</parent>

      </parents>

      <relatedItemsPointingToMe>

        <item sortOrder="1">MS.SEARCH.ADMIN.HA10047848</item>

        <item sortOrder="1">MS.SEARCH.ADMIN.ManageSearchService</item>

        <item sortOrder="1">MS.SEARCH.ADMIN.SearchServerSettings</item>

      </relatedItemsPointingToMe>

    </helpItem>

    <helpItem>

      <id>MS.SEARCH.ADMIN.HA10047852</id>

      <parents>

        <parent sortOrder="6" primary="true">MS.SEARCH.ADMIN.CH10176338</parent>

      </parents>

      <relatedItemsPointingToMe>

        <item sortOrder="1">MS.SEARCH.ADMIN.LogSummary</item>

        <item sortOrder="1">MS.SEARCH.ADMIN.LogViewer</item>

      </relatedItemsPointingToMe>

    </helpItem>


However, one thing which does check out is that the file referenced in the <defaultHelpItem> is indeed the page which is loaded initially when the help window opens. So that's something!

 

I can't help thinking though, that a help file generator has been used and it would probably be necessary to use the same approach to add custom help pages. When I started delving I mainly expected to find some .chm files generated by nDoc or Sandcastle somewhere, but it does seem like something I'm not familar with has been used. Robohelp perhaps?

 

If anybody can shed any light on this I'd be interested to hear. In the meantime, I note that others have found alternative ways around the problem, by passing a custom parameter and then modifying core.js to intercept this and open up a completely custom help window, rather than plugging into the existing help system as such. Ragav Jagannathan's post on his approach can be found here - http://ragavj.blogspot.com/2007/04/custom-help-window-can-be-opened-by.html. As Ragav rightly points out, customizing the core.js file is unsupported so you should probably think carefully before going down this route.

[Update - Nick Swan MSN'd me to point out that there is a document library in the Central Admin website which contains help files. Interestingly, it's the same set of files stored in the HCCab folder on the filesystem! It's difficult to tell from the IIS logs which set of files are actually pulled in, as they seem to be dynamically loaded through the '/_layouts/helpContent.aspx' and '/_layouts/help.aspx' pages. Interesting!]

 

Sunday, 19 August 2007

Automatically setting custom permissions on new sites

This is the third and final article in a series of three, where I demonstrate how how to perform custom processing in the site creation process. See 'Article series - custom permissions with a site definition' for the full run down on the article series. Specifically, I wanted to show how to use code to modify sites as they are created, in order to do things which aren't normally possible with site definitions/site templates. In the example I'm using, I'm setting custom permissions on the created sites. A scenario where this might be useful is if say, your organization is using SharePoint in a collaboration sense and users are creating sites themselves, but certain sites need to be secured so that access is restricted to specific users. Often end users might not understand the details of the SharePoint security model, so it would be nice if we could take care of this automatically for them.

The solution

In the last article 'Site definitions - custom code in the site creation process', I showed how it's possible to use a Feature receiver in conjunction with the site definition to do pretty much anything you might want to do as sites are created. Based on this approach, my solution is based around the following:



  • Custom list which stores the list of authorized users in the site collection's root web. This list stores a mapping of users to the permissions they should have in the created site.
  • Custom site definition, created by copying an existing definition as described in the SDK.
  • Feature which doesn't have any Feature elements defined, but is attached to a Feature receiver. A property is defined to pass in the name of the permissions list.
  • Feature receiver code which uses the object model to iterate the permission list and grant appropriate permissions to each user listed.

So let's break down each element of the solution. Note that all the code etc. is available for download and is linked to at the end of the article. First of all, we create a list which looks like this (click to enlarge):



Looking at the the edit view for the list (shown below), we see that the two key columns are:

  • 'User' - Person or Group data type
  • 'Permission level' - choice data type, with allowable values 'Owner', 'Contributor' and 'Viewer'




If we are creating sites which are restricted we would probably want to secure the list so that curious users cannot add themselves, and then gain access to any future restricted sites which are created.


So that's the list. The site definition in my example doesn't do anything special - it's just a copy of the 'BLANKINTERNET' definition to keep the example simple. However, 'Creating, deploying and updating custom site definitions' has more information on the kinds of customizations you can make with your site definitions.

The Feature is defined to reference the Feature receiver class we are creating. This ensures our custom code will run when the Feature is activated.

<Feature  Title="SiteProvisioning" Id="7C020FFF-FF42-4fe2-8A9B-9BCA0D5F8001" Description="" Version="1.0.0.0" Scope="Web"

          Hidden="TRUE" DefaultResourceFile="core"

          ReceiverAssembly="COB.Demos.SiteDefinition, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cd9b418c14cff42e"

          ReceiverClass="COB.Demos.ProjectXSiteDefinition.SiteProvisioning" xmlns="http://schemas.microsoft.com/sharepoint/">

  <Properties>

    <!-- could also retrieve list by GUID by passing in as property and amending code slightly -->

    <Property Key="PermissionsListName" Value="Project X Permissions" />

  </Properties>

</Feature>


The 'ReceiverAssembly' and 'ReceiverClass' attributes have the values which point to our Feature receiver class which contains our custom code. Note also we are passing in a value which can be retrieved in the code by using a Feature property. This can be used as a more flexible alternative to hardcoding values in the class - in this case we are using it pass in the name of the 'authorized users' list, meaning that this Feature can be reused across different requirements (which would use different lists). A minor tweak to the code and property will allow you to use the list GUID if you prefer, though note that list GUIDs will be different if the list is recreated in another SharePoint environment.

So that's all great but what ensures the Feature gets activated? Ah you've lost the thread since the last article haven't you?! The Feature is activated automatically when sites are created using the definition courtesy of this line in the WebFeatures section of the onet.xml file which specifies what the site definition consists of:


<Feature ID="7C020FFF-FF42-4fe2-8A9B-9BCA0D5F8001">


And so finally, onto the code which we have written in our Feature receiver class:

using System;

using Microsoft.SharePoint;

 

namespace COB.Demos.ProjectXSiteDefinition

{

    class SiteProvisioning : SPFeatureReceiver

    {

        public override void FeatureActivated(SPFeatureReceiverProperties properties)

        {

            SPWeb currentWeb = null;

            SPSite currentSite = null;

            object oParent = properties.Feature.Parent;

 

            // retrieve the permissions list by name..

            string sPermsListName = properties.Definition.Properties["PermissionsListName"].Value;

 

            // only perform processing if the site definition is being used to create a web within the expected site collection..

            if (properties.Feature.Parent is SPWeb)

            {

                currentWeb = (SPWeb)properties.Feature.Parent;

                currentSite = currentWeb.Site;

 

                SPList permsList = currentSite.RootWeb.Lists[sPermsListName];

 

                // ensure the web is set to use unique permissions, we won't copy existing permissions from parent site..

                if (!currentWeb.HasUniqueRoleAssignments)

                {

                    currentWeb.BreakRoleInheritance(false);

                }

 

                foreach (SPListItem perm in permsList.Items)

                {

                    string sPermLevel = (string)perm["Permission level"];

 

                    SPFieldUserValue userValue = (SPFieldUserValue)perm.Fields["User"].GetFieldValue(perm["User"].ToString());

                    SPUser user = userValue.User;

                    setPermission(currentWeb, user, sPermLevel);

                }

 

                currentWeb.Update();

            }

        }

 

        private void setPermission(SPWeb currentWeb, SPUser user, string sPermLevel)

        {

            SPGroup permissionsGroup = null;

 

            switch (sPermLevel)

            {

                case "Owner":

                    permissionsGroup = currentWeb.AssociatedOwnerGroup;

                    break;

                case "Visitor":

                    permissionsGroup = currentWeb.AssociatedVisitorGroup;

                    break;

                case "Member":

                    permissionsGroup = currentWeb.AssociatedMemberGroup;

                    break;

                default:

                    throw new NotImplementedException(string.Format("Group '{0}' not yet implemented.", sPermLevel));

                    break;

            }

 

            permissionsGroup.AddUser(user);

      }

 

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

        {

        }

 

        public override void FeatureInstalled(SPFeatureReceiverProperties properties)

        {

        }

 

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)

        {

        }

    }

}


Stepping through the code, we first find the 'authorized users' list, tell SharePoint we don't want to inherit permissions for the web being created, and then iterate through the list adding each user to the appropriate security group for the web as we find them. Note the SPWeb object has properties to allow you to easily reference the 'Owners', 'Visitors' groups etc. - these will be named in the form '[My site name] Owners' so this avoids you having to do any nasty string concatenation here.

In terms of how where this class fits alongside the rest of the files, I just store it in the same VS project. In this example I'm not using VSeWSS to create the Solution, but as I mentioned last time this can make things much simpler. I'm choosing not to here because I wanted to pass the list name using a Feature property, and in the current version VSeWSS does not have the flexibility to support this. In any case, having the class in the same VS project means that when the project is compiled, the receiver assembly is built and is output to the same project's bin directory. My .ddf file which is passed to makecab.exe then adds this dll and all the other files to the Solution package (.wsp) which is built for deployment. You may choose to use a post-build type solution (MSBuild, post-build script etc.) to automatically deploy this to your local environment on every compile, either by straight XCOPY or using the STSADM commands - my 'Building and deploying SharePoint Solution packages' article has more information on this. So my overall project structure looks like this:




So for deployment this means everything is in one package - on deployment the assembly hits the GAC before the Feature activation process runs, meaning the Feature receiver code is in place and will execute successfully. Once deployed, the site definition is available for use and new sites can be created from it. If we go ahead and create a site, if we look at the different security groups we see the appropriate users have been added according the configuration data we stored in the 'authorized users' list.

Owners group:



Visitors group:


So that's it! We now have our solution which enables us to 'package up' custom permissions with a site definition. Clearly we could store the permissions mappings in some other store such as a database table or XML file, but all things being equal I'm a big advocate of using SharePoint lists to store such data. The user interface is provided for you, and security can be applied to ensure standard users are unaware of the list's presence.

All the files I used can be downloaded from http://sharepointchris.googlepages.com/sitedefinitionwithcustompermissions.

In terms of using the files, you can follow this process to use the technique:

  • create the authorized users list from the list template I supply - 'PermissionsListTemplate.stp'
  • add your users and permission mappings to the list
  • add your site definition files to the appropriate places in the VS project, and modify the onet.xml file etc. as necessary
  • if files have been added, amend the .ddf file to include these and rebuild the Solution package
  • deploy the package using STSADM (a .bat file is included in the zip) and create sites from the definition!

Hopefully this series has been some use. While the approach is certainly useful in my scenario of rolling out a site definition used to create automatically secured sites from, in general terms you can use the technique to do any custom processing you want in the site creation process. If there are any queries please leave a comment!

Sunday, 12 August 2007

Site definitions - custom code in the site creation process

This is the second article in a series of three, where I aim to show how to customize the site creation process (known as site provisioning) with your own API code. The full introduction and series contents can be found at http://sharepointnutsandbolts.blogspot.com/2007/07/article-series-custom-permissions-with.html. The example customization I'm using is as follows: any sites created with the definition should use a specific set of permissions, and not simply follow the default behavior of inheriting the parent site's permissions. Since this can't be done with a standard site definition (like many other things you might want to do), use of the API is required.

However, today the focus is less on the permission specifics of my example, and more on how generally to add your own code which runs in the site provisioning process. And the best thing is, it's actually very simple if you understand SharePoint Features.

There are many reasons why you might have cause to use the API in the site provisioning process. Essentially, if you can't find a way to do what you want using CAML schema in the onet.xml file, chances are you'll have to write code. Hence, it's almost easier to think of what you can do in the onet.xml file and reverse the list in order to work out scenarios which require code, but some examples which spring to mind anyhow are:

  • changing the custom master page of a site
  • creating a site column which gets it's data from a list (see my post on my Feature receiver which does this at Feature to create lookup fields on Codeplex)
  • adding custom unique permissions to a site (the example in this article series)
  • set a site property from any kind of dynamic lookup

In short, there are many scenarios.


Creating site definitions with VSeWSS

If you've ever created a site definition with Visual Studio Extensions for Windows SharePoint Services, you'll notice that the VS project it gives you contains a file called SiteProvisioning.cs. Inside is an event-handler method, where you can add your custom code which will execute when a site is created from the definition. The class looks like this:

namespace COB.Demos.SiteDefinition

{

    public partial class ProjectXSiteDefinition

    {

        /// <summary>

        ///  Define your own feature activation action code here

        /// </summary>

        public void OnActivated(SPFeatureReceiverProperties properties)

        {

            // my code here..

        }

    }

}

 

The plumbing behind all this is interesting. At first glance, the method signature looks like a Feature receiver, but it's actually not. However, examining the VS project (you'll need to build the project with F5 at least once to generate the files) reveals that VSeWSS has in fact created some Features in the background. These files can be found under the bin\Debug\solution folder in your VS project (hidden by default - you'll need to do a 'Show All Files' in Visual Studio Solution Explorer). If you do some more delving around to see exactly what VSeWSS is doing, you'll find the following:

  • 2 hidden Features have been created - 1 deploys the 'default.aspx' file, the other has no 'elements' file but is hooked up to a Feature receiver - this is a class in an assembly named the same as your VS project. If you check the GAC, you will indeed find this assembly there.
  • a line similar to the following has been added to the onet.xml file under the 'WebFeatures' element:

    <Feature ID="67b2507c-8822-41dc-b939-3d8f34b5ad13" />


    Notably, this is the ID of the Feature which is hooked up to the Feature receiver.
  • Using Reflector on the assembly containing the Feature receiver shows that the main event-handler method performs some processing and then calls into the OnActivated method shown above, i.e. the place where VSeWSS provides for you to add your own code to execute when sites are created. This code is actually contained in the SiteProvisioning.Internal.cs file within the VS project. (If you're curious as to what on earth all the code in here is doing, the answer as far as I can tell is nothing when site definitions are created with the VSeWSS project template. However, this code is also found when Solution Generator is used to extract a site definition - in that case there are some fixups which need to be done, and this is the code which is used.)

So in summary, VSeWSS creates a hidden Feature is added to the 'WebFeatures' section of the onet.xml so that it is automatically activated when the definition is used to create a web*. The Feature is hooked up to a Feature receiver which calls the OnActivated method where your custom code lives.

*(Note that if the definition is used to create a site definition, the root web is also created automatically so the Feature would also be activated then. Also note the feature needs to be already installed in the farm for it to be activated in this way).

What we can derive from this is that there's no 'special place' in the site provisioning process to inject custom code, but it can be accomplished by use of a Feature receiver. So if you don't want to use VSeWSS to create site definitions, this is the technique to use to add your custom code to the site creation process.

In terms of what that code might look like, a 'Hello World' example could be:

public void OnActivated(SPFeatureReceiverProperties properties)

{

     SPWeb currentWeb = null;

     SPSite currentSite = null;

     object oParent = properties.Feature.Parent;

 

     if (properties.Feature.Parent is SPWeb)

     {

         currentWeb = (SPWeb)oParent;

         currentSite = currentWeb.Site;

     }

     else

     {

         currentSite = (SPSite)oParent;

         currentWeb = currentSite.RootWeb;

     }

 

     currentWeb.Title = "Set from provisioning code at " +  DateTime.Now.ToString();

     currentWeb.Update();

}


Hopefully this illustrates that it's quite simple to write code which sets properties on sites created from the definition. Generally the SPWeb object is the entry point, and any property which can be modified can be modified using the API. So, this is a pretty powerful technique which can be used in many scenarios.

If you have this type of requirement, I'd definitely recommend using VSeWSS to simplify the process. It's certainly possible to hook everything up manually and package it into a Solution, but the tool does save a large amount of hassle. However as usual with VSeWSS, the price of this is some flexibility. As my sample code in the final article will show, it's sometimes useful to pass data into Features by using Feature properties, and this unfortunately is not supported by VSeWSS. So in case it's useful, the following link provides a zip file containing a Solution/Feature which uses the above technique, without using VSeWSS:

http://sharepointchris.googlepages.com/customcodewithsitedefinitions

In the next and final article, I'll cover the specifics of using the API to modify site permissions as sites are created. As is hopefully clear, this is in conjunction with the technique detailed here so the net result is that the specific permissions are set 'automatically', courtesy of the Feature which is automatically activated against a site when it is created.